From 69be23c5f629884465dec98efcb9d5a2678b4df5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Fri, 18 Nov 2016 16:04:08 +0100 Subject: GH-1726 better failure detection for updates Instead of just checking if the new version started, make sure it is able to write its IPC key to a file and then use the key to connect to the process. --- application/MultiMC.cpp | 340 ++++-------------------------------------------- 1 file changed, 27 insertions(+), 313 deletions(-) (limited to 'application/MultiMC.cpp') diff --git a/application/MultiMC.cpp b/application/MultiMC.cpp index c102e198..64c380ec 100644 --- a/application/MultiMC.cpp +++ b/application/MultiMC.cpp @@ -73,6 +73,8 @@ #include #endif +static const QLatin1String liveCheckFile("live.check"); + using namespace Commandline; MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv) @@ -132,6 +134,9 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv) parser.addOption("launch"); parser.addShortOpt("launch", 'l'); parser.addDocumentation("launch", "launch the specified instance (by instance ID)"); + // --alive + parser.addSwitch("alive"); + parser.addDocumentation("alive", "write a small '" + liveCheckFile + "' file after MultiMC starts"); // parse the arguments try @@ -165,6 +170,7 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv) } } m_instanceIdToLaunch = args["launch"].toString(); + m_liveCheck = args["alive"].toBool(); QString origcwdPath = QDir::currentPath(); QString binPath = applicationDirPath(); @@ -242,6 +248,27 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv) qDebug() << "ID of instance to launch : " << m_instanceIdToLaunch; } + do // once + { + if(m_liveCheck) + { + QFile check(liveCheckFile); + if(!check.open(QIODevice::WriteOnly | QIODevice::Truncate)) + { + qWarning() << "Could not open" << liveCheckFile << "for writing!"; + break; + } + auto payload = appID.toString().toUtf8(); + if(check.write(payload) != payload.size()) + { + qWarning() << "Could not write into" << liveCheckFile; + check.remove(); + break; + } + check.close(); + } + } while(false); + // load settings initGlobalSettings(); @@ -697,319 +724,6 @@ std::shared_ptr MultiMC::javalist() return m_javalist; } -// from -#ifndef S_IRUSR -#define __S_IREAD 0400 /* Read by owner. */ -#define __S_IWRITE 0200 /* Write by owner. */ -#define __S_IEXEC 0100 /* Execute by owner. */ -#define S_IRUSR __S_IREAD /* Read by owner. */ -#define S_IWUSR __S_IWRITE /* Write by owner. */ -#define S_IXUSR __S_IEXEC /* Execute by owner. */ - -#define S_IRGRP (S_IRUSR >> 3) /* Read by group. */ -#define S_IWGRP (S_IWUSR >> 3) /* Write by group. */ -#define S_IXGRP (S_IXUSR >> 3) /* Execute by group. */ - -#define S_IROTH (S_IRGRP >> 3) /* Read by others. */ -#define S_IWOTH (S_IWGRP >> 3) /* Write by others. */ -#define S_IXOTH (S_IXGRP >> 3) /* Execute by others. */ -#endif -static QFile::Permissions unixModeToPermissions(const int mode) -{ - QFile::Permissions perms; - - if (mode & S_IRUSR) - { - perms |= QFile::ReadUser; - } - if (mode & S_IWUSR) - { - perms |= QFile::WriteUser; - } - if (mode & S_IXUSR) - { - perms |= QFile::ExeUser; - } - - if (mode & S_IRGRP) - { - perms |= QFile::ReadGroup; - } - if (mode & S_IWGRP) - { - perms |= QFile::WriteGroup; - } - if (mode & S_IXGRP) - { - perms |= QFile::ExeGroup; - } - - if (mode & S_IROTH) - { - perms |= QFile::ReadOther; - } - if (mode & S_IWOTH) - { - perms |= QFile::WriteOther; - } - if (mode & S_IXOTH) - { - perms |= QFile::ExeOther; - } - return perms; -} - -void MultiMC::installUpdates(const QString updateFilesDir, GoUpdate::OperationList operations) -{ - qint64 pid = -1; - QStringList args; - bool started = false; - - qDebug() << "Installing updates."; -#ifdef Q_OS_WIN - QString finishCmd = applicationFilePath(); -#elif defined Q_OS_LINUX - QString finishCmd = FS::PathCombine(root(), "MultiMC"); -#elif defined Q_OS_MAC - QString finishCmd = applicationFilePath(); -#else -#error Unsupported operating system. -#endif - - QString backupPath = FS::PathCombine(root(), "update", "backup"); - QDir origin(root()); - - // clean up the backup folder. it should be empty before we start - if(!FS::deletePath(backupPath)) - { - qWarning() << "couldn't remove previous backup folder" << backupPath; - } - // and it should exist. - if(!FS::ensureFolderPathExists(backupPath)) - { - qWarning() << "couldn't create folder" << backupPath; - return; - } - - struct BackupEntry - { - QString orig; - QString backup; - }; - enum Failure - { - Replace, - Delete, - Start, - Nothing - } failedOperationType = Nothing; - QString failedFile; - - QList backups; - QList trashcan; - - bool useXPHack = false; - QString exePath; - QString exeOrigin; - QString exeBackup; - - // perform the update operations - for(auto op: operations) - { - switch(op.type) - { - // replace = move original out to backup, if it exists, move the new file in its place - case GoUpdate::Operation::OP_REPLACE: - { -#ifdef Q_OS_WIN32 - // hack for people renaming the .exe because ... reasons :) - if(op.dest == "MultiMC.exe") - { - op.dest = QFileInfo(applicationFilePath()).fileName(); - } -#endif - QFileInfo replaced (FS::PathCombine(root(), op.dest)); -#ifdef Q_OS_WIN32 - if(QSysInfo::windowsVersion() < QSysInfo::WV_VISTA) - { - if(replaced.fileName() == "MultiMC.exe") - { - QDir rootDir(root()); - exeOrigin = rootDir.relativeFilePath(op.file); - exePath = rootDir.relativeFilePath(op.dest); - exeBackup = rootDir.relativeFilePath(FS::PathCombine(backupPath, replaced.fileName())); - useXPHack = true; - continue; - } - } -#endif - if(replaced.exists()) - { - QString backupName = op.dest; - backupName.replace('/', '_'); - QString backupFilePath = FS::PathCombine(backupPath, backupName); - if(!QFile::rename(replaced.absoluteFilePath(), backupFilePath)) - { - qWarning() << "Couldn't move:" << replaced.absoluteFilePath() << "to" << backupFilePath; - failedOperationType = Replace; - failedFile = op.dest; - goto FAILED; - } - BackupEntry be; - be.orig = replaced.absoluteFilePath(); - be.backup = backupFilePath; - backups.append(be); - } - // make sure the folder we are putting this into exists - if(!FS::ensureFilePathExists(replaced.absoluteFilePath())) - { - qWarning() << "REPLACE: Couldn't create folder:" << replaced.absoluteFilePath(); - failedOperationType = Replace; - failedFile = op.dest; - goto FAILED; - } - // now move the new file in - if(!QFile::rename(op.file, replaced.absoluteFilePath())) - { - qWarning() << "REPLACE: Couldn't move:" << op.file << "to" << replaced.absoluteFilePath(); - failedOperationType = Replace; - failedFile = op.dest; - goto FAILED; - } - QFile::setPermissions(replaced.absoluteFilePath(), unixModeToPermissions(op.mode)); - } - break; - // delete = move original to backup - case GoUpdate::Operation::OP_DELETE: - { - QString origFilePath = FS::PathCombine(root(), op.file); - if(QFile::exists(origFilePath)) - { - QString backupName = op.file; - backupName.replace('/', '_'); - QString trashFilePath = FS::PathCombine(backupPath, backupName); - - if(!QFile::rename(origFilePath, trashFilePath)) - { - qWarning() << "DELETE: Couldn't move:" << op.file << "to" << trashFilePath; - failedFile = op.file; - failedOperationType = Delete; - goto FAILED; - } - BackupEntry be; - be.orig = origFilePath; - be.backup = trashFilePath; - trashcan.append(be); - } - } - break; - } - } - - // try to start the new binary - args = qApp->arguments(); - args.removeFirst(); - - // on old Windows, do insane things... no error checking here, this is just to have something. - if(useXPHack) - { - QString script; - auto nativePath = QDir::toNativeSeparators(exePath); - auto nativeOriginPath = QDir::toNativeSeparators(exeOrigin); - auto nativeBackupPath = QDir::toNativeSeparators(exeBackup); - - // so we write this vbscript thing... - QTextStream out(&script); - out << "WScript.Sleep 1000\n"; - out << "Set fso=CreateObject(\"Scripting.FileSystemObject\")\n"; - out << "Set shell=CreateObject(\"WScript.Shell\")\n"; - out << "fso.MoveFile \"" << nativePath << "\", \"" << nativeBackupPath << "\"\n"; - out << "fso.MoveFile \"" << nativeOriginPath << "\", \"" << nativePath << "\"\n"; - out << "shell.Run \"" << nativePath << "\"\n"; - - QString scriptPath = FS::PathCombine(root(), "update", "update.vbs"); - - // we save it - QFile scriptFile(scriptPath); - scriptFile.open(QIODevice::WriteOnly); - scriptFile.write(script.toLocal8Bit().replace("\n", "\r\n")); - scriptFile.close(); - - // we run it - started = QProcess::startDetached("wscript", {scriptPath}, root()); - - // and we quit. conscious thought. - qApp->quit(); - return; - } - started = QProcess::startDetached(finishCmd, args, QDir::currentPath(), &pid); - // failed to start... ? - if(!started || pid == -1) - { - qWarning() << "Couldn't start new process properly!"; - failedOperationType = Start; - goto FAILED; - } - origin.rmdir(updateFilesDir); - qApp->quit(); - return; - -FAILED: - qWarning() << "Update failed!"; - bool revertOK = true; - // if the above failed, roll back changes - for(auto backup:backups) - { - qWarning() << "restoring" << backup.orig << "from" << backup.backup; - if(!QFile::remove(backup.orig)) - { - revertOK = false; - qWarning() << "removing new" << backup.orig << "failed!"; - continue; - } - - if(!QFile::rename(backup.backup, backup.orig)) - { - revertOK = false; - qWarning() << "restoring" << backup.orig << "failed!"; - } - } - for(auto backup:trashcan) - { - qWarning() << "restoring" << backup.orig << "from" << backup.backup; - if(!QFile::rename(backup.backup, backup.orig)) - { - revertOK = false; - qWarning() << "restoring" << backup.orig << "failed!"; - } - } - QString msg; - if(!revertOK) - { - msg = tr("The update failed and then the update revert failed too.\n" - "You will have to repair MultiMC manually.\n" - "Please let us know why and how this happened.").arg(failedFile); - } - else switch (failedOperationType) - { - case Replace: - msg = tr("Couldn't replace file %1. Changes were reverted.\n" - "See the MultiMC log file for details.").arg(failedFile); - break; - case Delete: - msg = tr("Couldn't remove file %1. Changes were reverted.\n" - "See the MultiMC log file for details.").arg(failedFile); - break; - case Start: - msg = tr("The new version didn't start and the update was rolled back."); - break; - case Nothing: - default: - return; - } - QMessageBox::critical(nullptr, tr("Update failed!"), msg); -} - std::vector MultiMC::getValidApplicationThemes() { std::vector ret; -- cgit v1.2.3