Skip to content

Commit 68daf11

Browse files
authored
Merge pull request #10680 from chrip/fix/7009-installer-cleanup-on-migration
fix(updater): remove leftover installer during config version migration
2 parents 5745792 + d53bb2d commit 68daf11

4 files changed

Lines changed: 83 additions & 26 deletions

File tree

‎src/gui/updater/ocupdater.cpp‎

Lines changed: 6 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ const auto updateAvailableC = QStringLiteral("Updater/updateAvailable");
2626
const auto updateTargetVersionC = QStringLiteral("Updater/updateTargetVersion");
2727
const auto updateTargetVersionStringC = QStringLiteral("Updater/updateTargetVersionString");
2828
const auto autoUpdateAttemptedC = QStringLiteral("Updater/autoUpdateAttempted");
29-
const auto msiLogFileNameC = QStringLiteral("msi.log");
3029
}
3130

3231
UpdaterScheduler::UpdaterScheduler(QObject *parent)
@@ -223,7 +222,7 @@ void OCUpdater::slotStartInstaller()
223222
return QDir::toNativeSeparators(path);
224223
};
225224

226-
QString msiLogFile = cfg.configPath() + msiLogFileNameC;
225+
const auto msiLogFile = cfg.msiLogFilePath();
227226
QString command = QStringLiteral("&{msiexec /i '%1' /L*V '%2'| Out-Null ; &'%3'}")
228227
.arg(preparePathForPowershell(updateFile))
229228
.arg(preparePathForPowershell(msiLogFile))
@@ -305,30 +304,11 @@ void NSISUpdater::slotWriteFile()
305304

306305
void NSISUpdater::wipeUpdateData()
307306
{
308-
ConfigFile cfg;
309-
QSettings settings(cfg.configFile(), QSettings::IniFormat);
310-
QString updateFileName = settings.value(updateAvailableC).toString();
311-
if (!updateFileName.isEmpty()) {
312-
if (QFile::remove(updateFileName)) {
313-
qCInfo(lcUpdater) << "Removed updater file:" << updateFileName;
314-
} else {
315-
qCWarning(lcUpdater) << "Failed to remove updater file:" << updateFileName;
316-
}
317-
}
318-
// Also try to remove the msi log file (created when running msiexec)
319-
const auto msiLogFileName = QString{cfg.configPath() + msiLogFileNameC};
320-
if (QFile::exists(msiLogFileName)) {
321-
if (QFile::remove(msiLogFileName)) {
322-
qCInfo(lcUpdater) << "Removed msi log file:" << msiLogFileName;
323-
} else {
324-
qCWarning(lcUpdater) << "Failed to remove msi log file:" << msiLogFileName;
325-
}
326-
}
327-
328-
settings.remove(updateAvailableC);
329-
settings.remove(updateTargetVersionC);
330-
settings.remove(updateTargetVersionStringC);
331-
settings.remove(autoUpdateAttemptedC);
307+
// Deliberately delegated: ConfigFile::cleanUpdaterConfiguration() is also
308+
// reached from Application::configVersionMigration() on the first start of
309+
// a newly installed version, and both paths must remove the installer, not
310+
// just the keys that point at it.
311+
ConfigFile().cleanUpdaterConfiguration();
332312
}
333313

334314
void NSISUpdater::slotDownloadFinished()

‎src/libsync/configfile.cpp‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,10 +434,38 @@ QString ConfigFile::excludeFileFromSystem()
434434
return fi.absoluteFilePath();
435435
}
436436

437+
namespace {
438+
void removeUpdaterArtifact(const QString &path)
439+
{
440+
if (path.isEmpty() || !QFile::exists(path)) {
441+
return;
442+
}
443+
444+
if (QFile::remove(path)) {
445+
qCInfo(lcConfigFile) << "Removed leftover updater file:" << path;
446+
} else {
447+
qCWarning(lcConfigFile) << "Failed to remove leftover updater file:" << path;
448+
}
449+
}
450+
}
451+
452+
QString ConfigFile::msiLogFilePath() const
453+
{
454+
return configPath() + QStringLiteral("msi.log");
455+
}
456+
437457
void OCC::ConfigFile::cleanUpdaterConfiguration()
438458
{
439459
QSettings settings(configFile(), QSettings::IniFormat);
440460
settings.beginGroup("Updater");
461+
462+
// The config is the only record of where the downloaded installer lives.
463+
// Delete it before dropping the keys, or every version change orphans
464+
// another installer in the config folder with nothing left to find it by.
465+
// See https://github.com/nextcloud/desktop/issues/7009
466+
removeUpdaterArtifact(settings.value("updateAvailable").toString());
467+
removeUpdaterArtifact(msiLogFilePath());
468+
441469
settings.remove("autoUpdateAttempted");
442470
settings.remove("updateTargetVersion");
443471
settings.remove("updateTargetVersionString");

‎src/libsync/configfile.h‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ class OWNCLOUDSYNC_EXPORT ConfigFile
4242
static QString excludeFileFromSystem(); // doesn't access config dir
4343

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

4749
/**

‎test/testupdater.cpp‎

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,53 @@ private Q_SLOTS:
5151
QVERIFY(currVersion < highVersion);
5252
}
5353

54+
// Reproduces #7009: on the first start of a newly installed version the
55+
// leftover installer must be gone. Application::configVersionMigration()
56+
// (src/gui/application.cpp:161-164) drops the Updater/* keys before
57+
// main() calls handleStartup() (src/gui/main.cpp:104 vs 142), so the
58+
// cleanup in NSISUpdater::handleStartup() never sees the artifact.
59+
void testLeftoverInstallerIsRemovedAfterVersionChange()
60+
{
61+
QTemporaryDir tempDir;
62+
QVERIFY(tempDir.isValid());
63+
QVERIFY(ConfigFile::setConfDir(tempDir.path()));
64+
65+
ConfigFile cfg;
66+
const auto writeFile = [](const QString &path, const QByteArray &contents) {
67+
QFile f(path);
68+
return f.open(QIODevice::WriteOnly) && f.write(contents) == contents.size();
69+
};
70+
71+
// a previous run downloaded an installer and msiexec left its log behind
72+
const auto installer = FileSystem::joinPath(cfg.configPath(), "Nextcloud-1.0.0-x64.msi"_L1);
73+
const auto msiLog = FileSystem::joinPath(cfg.configPath(), "msi.log"_L1);
74+
QVERIFY(writeFile(installer, "this would be the installer"_ba));
75+
QVERIFY(writeFile(msiLog, "this would be the msiexec log"_ba));
76+
77+
{
78+
QSettings settings(cfg.configFile(), QSettings::IniFormat);
79+
settings.setValue("Updater/updateAvailable"_L1, installer);
80+
settings.setValue("Updater/updateTargetVersion"_L1, "1.0.0"_L1);
81+
settings.setValue("Updater/updateTargetVersionString"_L1, "1.0.0"_L1);
82+
settings.setValue("Updater/autoUpdateAttempted"_L1, true);
83+
settings.sync();
84+
}
85+
86+
// the update was installed: the running binary is newer than the target
87+
QVERIFY(Updater::Helper::currentVersionToInt() > Updater::Helper::stringVersionToInt("1.0.0"_L1));
88+
89+
// what Application::configVersionMigration() does on that very first
90+
// start, before the updater gets a chance to look at its own state
91+
cfg.setClientVersionString("1.0.0"_L1);
92+
cfg.cleanUpdaterConfiguration();
93+
94+
NSISUpdater updater(QUrl("http://localhost:1/updateinfo.xml"_L1));
95+
updater.handleStartup();
96+
97+
QVERIFY(!QFileInfo::exists(installer));
98+
QVERIFY(!QFileInfo::exists(msiLog));
99+
}
100+
54101
#ifdef HAVE_QHTTPSERVER
55102
void testUpdaterDownloadRedirect()
56103
{

0 commit comments

Comments
 (0)