Why does QDir::move() fail where mv succeeds?



  • Given this code, QDir::move fails - but calling mv right after it as a process works:

                // FIXME don't hardcode name in case we want to change it
                QFileInfo fi("/tmp/Mudlet.AppImage");
                qDebug() << "gotta move " << fi.filePath() << "to" << QCoreApplication::applicationFilePath();
                if (!QDir().rename(fi.filePath(), QCoreApplication::applicationFilePath())) {
                    qDebug() << "replacing Mudlet with new version failed, original here:" << fi.exists();
    
                    QProcess mv;
                    mv.setProcessChannelMode(QProcess::MergedChannels);
                    mv.start("mv",  QStringList() << fi.filePath() << QCoreApplication::applicationFilePath());
                    if (!mv.waitForFinished()) {
                        qDebug() << "Moving failed:" << mv.errorString();
                    } else {
                        qDebug() << "moving file with mv worked!";
                    }
                }
    
    gotta move  "/tmp/Mudlet.AppImage" to "/home/vadi/Programs/Mudlet/mudlet/build-mudlet-Desktop_Qt_5_9_0_Clang_64bit-Debug/mudlet"
    replacing Mudlet with new version failed, original here: true
    moving file with mv worked!
    

    This is on Ubuntu 17.04. I'd rather use the built-in function than spawning a process here, any idea why it's not working?


  • Moderators

    See the documentation. It states that QDir::rename() will fail if destination file exists, which is true in your case (you are trying to replace the application executable if I am not mistaken).



  • It says "on most filesystems":

    On most file systems, rename() fails only if oldName does not exist, or if a file with the new name already exists

    I'm on a filesystem where this does not fail since mv works fine doing just the same!


  • Moderators

    @Vadi2 said in Why does QDir::move() fail where mv succeeds?:

    It says "on most filesystems":

    On most file systems, rename() fails only if oldName does not exist, or if a file with the new name already exists

    I'm on a filesystem where this does not fail since mv works fine doing just the same!

    If it didn't fail you wouldn't have created this thread, right? It does not matter if mv works or not. QDir does not work in this case (it doesn't use mv), and the documentation warns about it. You can try reporting this as a bug if you feel it is one. Or find another working solution (like running mv or removing the destination file before moving the updated one).


  • Lifetime Qt Champion

    Hi,

    To add to @sierdzio, mv just overwrites unless you added the -i where you'll be prompted if you try to overwrite something. Qt is on the safe side by not allowing you to overwrite files blindly.



  • Ah, yeah, thanks - deleting the file first worked.

    Is it possible to move and keep the executable bit?


  • Moderators

    @Vadi2 said in Why does QDir::move() fail where mv succeeds?:

    Ah, yeah, thanks - deleting the file first worked.

    Nice :)

    Is it possible to move and keep the executable bit?

    You can use QFile::setPermissions for that. To make sure you keep exactly the same permissions, you can read them from old file and then set the same on the new one.



  • Worked great. Thanks!



  • @Vadi2 said in Why does QDir::move() fail where mv succeeds?:

    It says "on most filesystems":

    On most file systems, rename() fails only if oldName does not exist, or if a file with the new name already exists

    I'm on a filesystem where this does not fail since mv works fine doing just the same!

    Just for the record: Linux mv is a program, not an operating system call, which can (and does) do whatever it likes.

    Given that all you seem to want to do is move a file to another (possibly pre-existing) file, you could just use the native OS call (much preferable to running the mv program) under Linux from your code instead of QDir()::rename():

    int rename(const char *oldpath, const char *newpath);
    

    Run man 2 rename:

    If newpath already exists, it will be atomically replaced

    P.S.
    @sierdzio said in Why does QDir::move() fail where mv succeeds?:

    @Vadi2 said in Why does QDir::move() fail where mv succeeds?:

    Ah, yeah, thanks - deleting the file first worked.

    Nice :)

    Is it possible to move and keep the executable bit?

    You can use QFile::setPermissions for that. To make sure you keep exactly the same permissions, you can read them from old file and then set the same on the new one.

    I'm afraid this is not a good thing to attempt to do! There are many reasons why attempting to place the same permissions/attributes on a file from those on another file will fail. If you wish to preserve permissions/attributes etc. , you are much better using OS call rename() than all of this.


  • Moderators

    @JNBarchan said in Why does QDir::move() fail where mv succeeds?:

    I'm afraid this is not a good thing to attempt to do! There are many reasons why attempting to place the same permissions/attributes on a file from those on another file will fail. If you wish to preserve permissions/attributes etc. , you are much better using OS call rename() than all of this.

    Rename does not preserve any permissions of the target file, it will preserve the perms of source file. I don't think that is what OP wants.

    Yes, there are ways in which it can fail.

    IMO using OS calls everywhere by hand is not necessary, at least in this case, Qt already does that behind the scenes - and in a cross-platform way.



  • @sierdzio

    @Vadi2 said in Why does QDir::move() fail where mv succeeds?:

    Is it possible to move and keep the executable bit?

    I read that as he wants to preserve the source's attributes/permissions (the source is an executable), surely? I don't know what QDir::move() does, since the docs don't say.... It does also talk about possibly implementing the rename via copy & delete in whatever circumstances, I don't know what that might do about attributes/permissions.


  • Moderators

    @JNBarchan said in Why does QDir::move() fail where mv succeeds?:

    I don't know what QDir::move() does, since the docs don't say....

    True.



  • Yeah, I want to keep the permissions of the original file. This operation will be specific to the Linux platform, so I'll try the system call - seems more efficient.



  • @Vadi2
    Just a couple of observations:

    • You are moving a file from /tmp to somewhere else. It's possible that is mounted on a different file system (e.g. run df) from your destination, in which case the rename will actually be a copy + delete instead of a simple move.

    • "I want to keep the permissions of the original file" Which do you mean by "original"? Depending, you may actually need code to copy across permissions/attributes after all.

    • You are replacing the executable file you are actually running, while it is running. This is a very strange thing to do! (I would have expected you would do this from a separate application.) Linux may let you do it, I doubt Windows would. Just saying.

    • qDebug() << "replacing Mudlet with new version failed, original here:" << fi.exists(); should be fi.filePath() in place of fi.exists() :)



  • Hello!

    • Okay, sounds good
    • I was thinking of the downloaded file in the tar, but after more thought, I just want it to be executable while not flagrantly violating security rules
    • I know, I don't want to deal with the hassle of another executable and I'm glad Linux allows you this. Windows doens't, so that will be a pain in the butt.
    • ah yeah, I have better code here now:
    void Updater::silentlyUpdate() const
    {
        QObject::connect(feed, &dblsqd::Feed::ready, [=]() { feed->downloadRelease(feed->getUpdates().first()); });
    
        QObject::connect(feed, &dblsqd::Feed::downloadFinished, [=]() {
            auto file = feed->getDownloadFile();
    
            QFuture<void> future = QtConcurrent::run(this, &Updater::untarOnLinux, file->fileName());
    
            // replace current binary with the unzipped one
            auto watcher = new QFutureWatcher<void>;
            connect(watcher, &QFutureWatcher<void>::finished, this, &Updater::updateBinaryOnLinux);
            watcher->setFuture(future);
        });
    }
    
    void Updater::untarOnLinux(const QString& fileName) const
    {
        QProcess tar;
        tar.setProcessChannelMode(QProcess::MergedChannels);
        // we can assume tar to be present on a Linux system. If it's not, it'd be rather broken.
        tar.start("tar",
                  QStringList() << "-xvf" << fileName << "-C"
                                << QStandardPaths::writableLocation(QStandardPaths::TempLocation) << "/");
        if (!tar.waitForFinished()) {
            qDebug() << "Untarring" << fileName << "failed:" << tar.errorString();
        } else {
            qDebug() << "Tar output:" << tar.readAll().trimmed();
        }
    }
    
    void Updater::updateBinaryOnLinux() const
    { // FIXME don't hardcode name in case we want to change it
        QFileInfo unzippedBinary(QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/Mudlet.AppImage");
        QString installedBinaryPath(QCoreApplication::applicationFilePath());
    
        auto executablePermissions = unzippedBinary.permissions();
        executablePermissions |= QFileDevice::ExeOwner | QFileDevice::ExeUser;
    
        QDir dir;
        if (!(dir.remove(installedBinaryPath) && QDir().rename(unzippedBinary.filePath(), installedBinaryPath))) {
            qDebug() << "updating" << installedBinaryPath << "with new version from" << unzippedBinary.filePath() << "failed";
            return;
        }
    
        QFile updatedBinary(QCoreApplication::applicationFilePath());
        if (!updatedBinary.setPermissions(executablePermissions)) {
            qDebug() << "couldn't executable permissions on updated Mudlet binary at" << installedBinaryPath;
            return;
        }
    
        qDebug() << "Successfully updated Mudlet to" << feed->getUpdates().first().getVersion();
    }
    

Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.