Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 6 additions & 26 deletions src/gui/updater/ocupdater.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* SPDX-License-Identifier: GPL-2.0-or-later
*/

#include "theme.h"

Check failure on line 7 in src/gui/updater/ocupdater.cpp

View workflow job for this annotation

GitHub Actions / build

src/gui/updater/ocupdater.cpp:7:10 [clang-diagnostic-error]

'theme.h' file not found
#include "configfile.h"
#include "common/utility.h"
#include "accessmanager.h"
Expand All @@ -26,7 +26,6 @@
const auto updateTargetVersionC = QStringLiteral("Updater/updateTargetVersion");
const auto updateTargetVersionStringC = QStringLiteral("Updater/updateTargetVersionString");
const auto autoUpdateAttemptedC = QStringLiteral("Updater/autoUpdateAttempted");
const auto msiLogFileNameC = QStringLiteral("msi.log");
}

UpdaterScheduler::UpdaterScheduler(QObject *parent)
Expand Down Expand Up @@ -223,7 +222,7 @@
return QDir::toNativeSeparators(path);
};

QString msiLogFile = cfg.configPath() + msiLogFileNameC;
QString msiLogFile = cfg.msiLogFilePath();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
QString msiLogFile = cfg.msiLogFilePath();
const auto msiLogFile = cfg.msiLogFilePath();

QString command = QStringLiteral("&{msiexec /i '%1' /L*V '%2'| Out-Null ; &'%3'}")
.arg(preparePathForPowershell(updateFile))
.arg(preparePathForPowershell(msiLogFile))
Expand Down Expand Up @@ -305,30 +304,11 @@

void NSISUpdater::wipeUpdateData()
{
ConfigFile cfg;
QSettings settings(cfg.configFile(), QSettings::IniFormat);
QString updateFileName = settings.value(updateAvailableC).toString();
if (!updateFileName.isEmpty()) {
if (QFile::remove(updateFileName)) {
qCInfo(lcUpdater) << "Removed updater file:" << updateFileName;
} else {
qCWarning(lcUpdater) << "Failed to remove updater file:" << updateFileName;
}
}
// Also try to remove the msi log file (created when running msiexec)
const auto msiLogFileName = QString{cfg.configPath() + msiLogFileNameC};
if (QFile::exists(msiLogFileName)) {
if (QFile::remove(msiLogFileName)) {
qCInfo(lcUpdater) << "Removed msi log file:" << msiLogFileName;
} else {
qCWarning(lcUpdater) << "Failed to remove msi log file:" << msiLogFileName;
}
}

settings.remove(updateAvailableC);
settings.remove(updateTargetVersionC);
settings.remove(updateTargetVersionStringC);
settings.remove(autoUpdateAttemptedC);
// Deliberately delegated: ConfigFile::cleanUpdaterConfiguration() is also
// reached from Application::configVersionMigration() on the first start of
// a newly installed version, and both paths must remove the installer, not
// just the keys that point at it.
ConfigFile().cleanUpdaterConfiguration();
}

void NSISUpdater::slotDownloadFinished()
Expand Down
28 changes: 28 additions & 0 deletions src/libsync/configfile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -434,10 +434,38 @@
return fi.absoluteFilePath();
}

namespace {
void removeUpdaterArtifact(const QString &path)
{
if (path.isEmpty() || !QFile::exists(path)) {
return;
}

if (QFile::remove(path)) {

Check warning on line 444 in src/libsync/configfile.cpp

View workflow job for this annotation

GitHub Actions / build

src/libsync/configfile.cpp:444:5 [bugprone-branch-clone]

if with identical then and else branches
qCInfo(lcConfigFile) << "Removed leftover updater file:" << path;
} else {
qCWarning(lcConfigFile) << "Failed to remove leftover updater file:" << path;
}
}
}

QString ConfigFile::msiLogFilePath() const
{
return configPath() + QStringLiteral("msi.log");
}

void OCC::ConfigFile::cleanUpdaterConfiguration()
{
QSettings settings(configFile(), QSettings::IniFormat);
settings.beginGroup("Updater");

// The config is the only record of where the downloaded installer lives.
// Delete it before dropping the keys, or every version change orphans
// another installer in the config folder with nothing left to find it by.
// See https://github.com/nextcloud/desktop/issues/7009
removeUpdaterArtifact(settings.value("updateAvailable").toString());
removeUpdaterArtifact(msiLogFilePath());

settings.remove("autoUpdateAttempted");
settings.remove("updateTargetVersion");
settings.remove("updateTargetVersionString");
Expand Down
2 changes: 2 additions & 0 deletions src/libsync/configfile.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
#ifndef CONFIGFILE_H
#define CONFIGFILE_H

#include "owncloudlib.h"

Check failure on line 10 in src/libsync/configfile.h

View workflow job for this annotation

GitHub Actions / build

src/libsync/configfile.h:10:10 [clang-diagnostic-error]

'owncloudlib.h' file not found
#include <memory>
#include <QSharedPointer>
#include <QSettings>
Expand Down Expand Up @@ -42,6 +42,8 @@
static QString excludeFileFromSystem(); // doesn't access config dir

void cleanUpdaterConfiguration();
/** Path of the log msiexec writes while installing an update (Windows). */
[[nodiscard]] QString msiLogFilePath() const;
void cleanupGlobalNetworkConfiguration();

/**
Expand Down
47 changes: 47 additions & 0 deletions test/testupdater.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* any purpose.
*/

#include <QtTest>

Check failure on line 11 in test/testupdater.cpp

View workflow job for this annotation

GitHub Actions / build

test/testupdater.cpp:11:10 [clang-diagnostic-error]

'QtTest' file not found

#include "common/filesystembase.h"
#include "updater/updater.h"
Expand Down Expand Up @@ -51,6 +51,53 @@
QVERIFY(currVersion < highVersion);
}

// Reproduces #7009: on the first start of a newly installed version the
// leftover installer must be gone. Application::configVersionMigration()
// (src/gui/application.cpp:161-164) drops the Updater/* keys before
// main() calls handleStartup() (src/gui/main.cpp:104 vs 142), so the
// cleanup in NSISUpdater::handleStartup() never sees the artifact.
void testLeftoverInstallerIsRemovedAfterVersionChange()
{
QTemporaryDir tempDir;
QVERIFY(tempDir.isValid());
QVERIFY(ConfigFile::setConfDir(tempDir.path()));

ConfigFile cfg;
const auto writeFile = [](const QString &path, const QByteArray &contents) {
QFile f(path);
return f.open(QIODevice::WriteOnly) && f.write(contents) == contents.size();
};

// a previous run downloaded an installer and msiexec left its log behind
const auto installer = FileSystem::joinPath(cfg.configPath(), "Nextcloud-1.0.0-x64.msi"_L1);
const auto msiLog = FileSystem::joinPath(cfg.configPath(), "msi.log"_L1);
QVERIFY(writeFile(installer, "this would be the installer"_ba));
QVERIFY(writeFile(msiLog, "this would be the msiexec log"_ba));

{
QSettings settings(cfg.configFile(), QSettings::IniFormat);
settings.setValue("Updater/updateAvailable"_L1, installer);
settings.setValue("Updater/updateTargetVersion"_L1, "1.0.0"_L1);
settings.setValue("Updater/updateTargetVersionString"_L1, "1.0.0"_L1);
settings.setValue("Updater/autoUpdateAttempted"_L1, true);
settings.sync();
}

// the update was installed: the running binary is newer than the target
QVERIFY(Updater::Helper::currentVersionToInt() > Updater::Helper::stringVersionToInt("1.0.0"_L1));

// what Application::configVersionMigration() does on that very first
// start, before the updater gets a chance to look at its own state
cfg.setClientVersionString("1.0.0"_L1);
cfg.cleanUpdaterConfiguration();

NSISUpdater updater(QUrl("http://localhost:1/updateinfo.xml"_L1));
updater.handleStartup();

QVERIFY(!QFileInfo::exists(installer));
QVERIFY(!QFileInfo::exists(msiLog));
}

#ifdef HAVE_QHTTPSERVER
void testUpdaterDownloadRedirect()
{
Expand Down
Loading