Qt: Improve updater error reporting

And swap from Qt file functions to our own.
This commit is contained in:
Stenzek 2024-11-27 17:48:05 +10:00
parent 57d3aa4850
commit b28ca2b78a
No known key found for this signature in database
1 changed files with 91 additions and 54 deletions

View File

@ -22,7 +22,7 @@
#include "fmt/format.h" #include "fmt/format.h"
#include <QtCore/QCoreApplication> #include <QtCore/QCoreApplication>
#include <QtCore/QFile> #include <QtCore/QFileInfo>
#include <QtCore/QJsonArray> #include <QtCore/QJsonArray>
#include <QtCore/QJsonDocument> #include <QtCore/QJsonDocument>
#include <QtCore/QJsonObject> #include <QtCore/QJsonObject>
@ -44,6 +44,8 @@ static constexpr u32 HTTP_POLL_INTERVAL = 10;
#include <shellapi.h> #include <shellapi.h>
#elif defined(__APPLE__) #elif defined(__APPLE__)
#include "common/cocoa_tools.h" #include "common/cocoa_tools.h"
#else
#include <sys/stat.h>
#endif #endif
// Logic to detect whether we can use the auto updater. // Logic to detect whether we can use the auto updater.
@ -602,8 +604,7 @@ static constexpr char UPDATER_ARCHIVE_NAME[] = "update.zip";
bool AutoUpdaterDialog::doesUpdaterNeedElevation(const std::string& application_dir) const bool AutoUpdaterDialog::doesUpdaterNeedElevation(const std::string& application_dir) const
{ {
// Try to create a dummy text file in the PCSX2 updater directory. If it fails, we probably won't have write // Try to create a dummy text file in the updater directory. If it fails, we probably won't have write permission.
// permission.
const std::string dummy_path = Path::Combine(application_dir, "update.txt"); const std::string dummy_path = Path::Combine(application_dir, "update.txt");
auto fp = FileSystem::OpenManagedCFile(dummy_path.c_str(), "wb"); auto fp = FileSystem::OpenManagedCFile(dummy_path.c_str(), "wb");
if (!fp) if (!fp)
@ -620,15 +621,16 @@ bool AutoUpdaterDialog::processUpdate(const std::vector<u8>& update_data)
const std::string update_zip_path = Path::Combine(EmuFolders::DataRoot, UPDATER_ARCHIVE_NAME); const std::string update_zip_path = Path::Combine(EmuFolders::DataRoot, UPDATER_ARCHIVE_NAME);
const std::string updater_path = Path::Combine(EmuFolders::DataRoot, UPDATER_EXECUTABLE); const std::string updater_path = Path::Combine(EmuFolders::DataRoot, UPDATER_EXECUTABLE);
if ((FileSystem::FileExists(update_zip_path.c_str()) && !FileSystem::DeleteFile(update_zip_path.c_str()))) Error error;
if ((FileSystem::FileExists(update_zip_path.c_str()) && !FileSystem::DeleteFile(update_zip_path.c_str(), &error)))
{ {
reportError("Removing existing update zip failed"); reportError(fmt::format("Removing existing update zip failed:\n{}", error.GetDescription()));
return false; return false;
} }
if (!FileSystem::WriteBinaryFile(update_zip_path.c_str(), update_data.data(), update_data.size())) if (!FileSystem::WriteAtomicRenamedFile(update_zip_path.c_str(), update_data, &error))
{ {
reportError(fmt::format("Writing update zip to '{}' failed", update_zip_path)); reportError(fmt::format("Writing update zip to '{}' failed:\n{}", update_zip_path, error.GetDescription()));
return false; return false;
} }
@ -686,7 +688,7 @@ bool AutoUpdaterDialog::extractUpdater(const std::string& zip_path, const std::s
if (std::fwrite(chunk, size, 1, fp.get()) != 1) if (std::fwrite(chunk, size, 1, fp.get()) != 1)
{ {
Error::SetString(error, "Failed to write updater exe"); Error::SetErrno(error, "Failed to write updater exe: fwrite() failed: ", errno);
unzClose(zf); unzClose(zf);
fp.reset(); fp.reset();
FileSystem::DeleteFile(destination_path.c_str()); FileSystem::DeleteFile(destination_path.c_str());
@ -742,9 +744,12 @@ void AutoUpdaterDialog::cleanupAfterUpdate()
if (!FileSystem::FileExists(updater_path.c_str())) if (!FileSystem::FileExists(updater_path.c_str()))
return; return;
if (!FileSystem::DeleteFile(updater_path.c_str())) Error error;
if (!FileSystem::DeleteFile(updater_path.c_str(), &error))
{ {
QMessageBox::critical(nullptr, tr("Updater Error"), tr("Failed to remove updater exe after update.")); QMessageBox::critical(
nullptr, tr("Updater Error"),
tr("Failed to remove updater exe after update:\n%1").arg(QString::fromStdString(error.GetDescription())));
return; return;
} }
} }
@ -784,24 +789,19 @@ bool AutoUpdaterDialog::processUpdate(const std::vector<u8>& update_data)
// We use the user data directory to temporarily store the update zip. // We use the user data directory to temporarily store the update zip.
const std::string zip_path = Path::Combine(EmuFolders::DataRoot, "update.zip"); const std::string zip_path = Path::Combine(EmuFolders::DataRoot, "update.zip");
const std::string staging_directory = Path::Combine(EmuFolders::DataRoot, "UPDATE_STAGING"); const std::string staging_directory = Path::Combine(EmuFolders::DataRoot, "UPDATE_STAGING");
if (FileSystem::FileExists(zip_path.c_str()) && !FileSystem::DeleteFile(zip_path.c_str())) Error error;
if (FileSystem::FileExists(zip_path.c_str()) && !FileSystem::DeleteFile(zip_path.c_str(), &error))
{ {
reportError("Failed to remove old update zip."); reportError(fmt::format("Failed to remove old update zip:\n{}", error.GetDescription()));
return false; return false;
} }
// Save update. // Save update.
if (!FileSystem::WriteAtomicRenamedFile(zip_path.c_str(), update_data, &error))
{ {
QFile zip_file(QString::fromStdString(zip_path)); reportError(fmt::format("Writing update zip to '{}' failed:\n{}", zip_path, error.GetDescription()));
if (!zip_file.open(QIODevice::WriteOnly) ||
zip_file.write(reinterpret_cast<const char*>(update_data.data()), static_cast<qint64>(update_data.size())) !=
static_cast<qint64>(update_data.size()))
{
reportError(fmt::format("Writing update zip to '{}' failed", zip_path));
return false; return false;
} }
zip_file.close();
}
INFO_LOG("Beginning update:\nUpdater path: {}\nZip path: {}\nStaging directory: {}\nOutput directory: {}", INFO_LOG("Beginning update:\nUpdater path: {}\nZip path: {}\nStaging directory: {}\nOutput directory: {}",
updater_app, zip_path, staging_directory, bundle_path.value()); updater_app, zip_path, staging_directory, bundle_path.value());
@ -832,66 +832,105 @@ bool AutoUpdaterDialog::processUpdate(const std::vector<u8>& update_data)
return false; return false;
} }
const QString qappimage_path(QString::fromUtf8(appimage_path)); if (!FileSystem::FileExists(appimage_path))
if (!QFile::exists(qappimage_path))
{ {
reportError(fmt::format("Current AppImage does not exist: {}", appimage_path)); reportError(fmt::format("Current AppImage does not exist: {}", appimage_path));
return false; return false;
} }
const QString new_appimage_path(qappimage_path + QStringLiteral(".new")); const std::string new_appimage_path = fmt::format("{}.new", appimage_path);
const QString backup_appimage_path(qappimage_path + QStringLiteral(".backup")); const std::string backup_appimage_path = fmt::format("{}.backup", appimage_path);
INFO_LOG("APPIMAGE = {}", appimage_path); INFO_LOG("APPIMAGE = {}", appimage_path);
INFO_LOG("Backup AppImage path = {}", backup_appimage_path.toStdString()); INFO_LOG("Backup AppImage path = {}", backup_appimage_path);
INFO_LOG("New AppImage path = {}", new_appimage_path.toStdString()); INFO_LOG("New AppImage path = {}", new_appimage_path);
// Remove old "new" appimage and existing backup appimage. // Remove old "new" appimage and existing backup appimage.
if (QFile::exists(new_appimage_path) && !QFile::remove(new_appimage_path)) Error error;
if (FileSystem::FileExists(new_appimage_path.c_str()) && !FileSystem::DeleteFile(new_appimage_path.c_str(), &error))
{ {
reportError(fmt::format("Failed to remove old destination AppImage: {}", new_appimage_path.toStdString())); reportError(
fmt::format("Failed to remove old destination AppImage: {}:\n{}", new_appimage_path, error.GetDescription()));
return false; return false;
} }
if (QFile::exists(backup_appimage_path) && !QFile::remove(backup_appimage_path)) if (FileSystem::FileExists(backup_appimage_path.c_str()) &&
!FileSystem::DeleteFile(backup_appimage_path.c_str(), &error))
{ {
reportError(fmt::format("Failed to remove old backup AppImage: {}", new_appimage_path.toStdString())); reportError(
fmt::format("Failed to remove old backup AppImage: {}:\n{}", backup_appimage_path, error.GetDescription()));
return false; return false;
} }
// Write "new" appimage. // Write "new" appimage.
{ {
// We want to copy the permissions from the old appimage to the new one. // We want to copy the permissions from the old appimage to the new one.
QFile old_file(qappimage_path); static constexpr int permission_mask = S_IRWXU | S_IRWXG | S_IRWXO;
const QFileDevice::Permissions old_permissions = old_file.permissions(); struct stat old_stat;
QFile new_file(new_appimage_path); if (!FileSystem::StatFile(appimage_path, &old_stat, &error))
if (!new_file.open(QIODevice::WriteOnly) ||
new_file.write(reinterpret_cast<const char*>(update_data.data()), static_cast<qint64>(update_data.size())) !=
static_cast<qint64>(update_data.size()) ||
!new_file.setPermissions(old_permissions))
{ {
QFile::remove(new_appimage_path); reportError(fmt::format("Failed to get old AppImage {} permissions:\n{}", appimage_path, error.GetDescription()));
reportError(fmt::format("Failed to write new destination AppImage: {}", new_appimage_path.toStdString())); return false;
}
// We do this as a manual write here, rather than using WriteAtomicUpdatedFile(), because we want to write the file
// and set the permissions as one atomic operation.
FileSystem::ManagedCFilePtr fp = FileSystem::OpenManagedCFile(new_appimage_path.c_str(), "wb", &error);
bool success = static_cast<bool>(fp);
if (fp)
{
if (std::fwrite(update_data.data(), update_data.size(), 1, fp.get()) == 1 && std::fflush(fp.get()) == 0)
{
const int fd = fileno(fp.get());
if (fd >= 0)
{
if (fchmod(fd, old_stat.st_mode & permission_mask) != 0)
{
error.SetErrno("fchmod() failed: ", errno);
success = false;
}
}
else
{
error.SetErrno("fileno() failed: ", errno);
success = false;
}
}
else
{
error.SetErrno("fwrite() failed: ", errno);
success = false;
}
fp.reset();
if (!success)
FileSystem::DeleteFile(new_appimage_path.c_str());
}
if (!success)
{
reportError(
fmt::format("Failed to write new destination AppImage: {}:\n{}", new_appimage_path, error.GetDescription()));
return false; return false;
} }
} }
// Rename "old" appimage. // Rename "old" appimage.
if (!QFile::rename(qappimage_path, backup_appimage_path)) if (!FileSystem::RenamePath(appimage_path, backup_appimage_path.c_str(), &error))
{ {
reportError(fmt::format("Failed to rename old AppImage to {}", backup_appimage_path.toStdString())); reportError(fmt::format("Failed to rename old AppImage to {}:\n{}", backup_appimage_path, error.GetDescription()));
QFile::remove(new_appimage_path); FileSystem::DeleteFile(new_appimage_path.c_str());
return false; return false;
} }
// Rename "new" appimage. // Rename "new" appimage.
if (!QFile::rename(new_appimage_path, qappimage_path)) if (!FileSystem::RenamePath(new_appimage_path.c_str(), appimage_path, &error))
{ {
reportError(fmt::format("Failed to rename new AppImage to {}", qappimage_path.toStdString())); reportError(fmt::format("Failed to rename new AppImage to {}:\n{}", appimage_path, error.GetDescription()));
return false; return false;
} }
// Execute new appimage. // Execute new appimage.
QProcess* new_process = new QProcess(); QProcess* new_process = new QProcess();
new_process->setProgram(qappimage_path); new_process->setProgram(QString::fromUtf8(appimage_path));
new_process->setArguments(QStringList{QStringLiteral("-updatecleanup")}); new_process->setArguments(QStringList{QStringLiteral("-updatecleanup")});
if (!new_process->startDetached()) if (!new_process->startDetached())
{ {
@ -910,16 +949,14 @@ void AutoUpdaterDialog::cleanupAfterUpdate()
if (!appimage_path) if (!appimage_path)
return; return;
const QString qappimage_path(QString::fromUtf8(appimage_path)); const std::string backup_appimage_path = fmt::format("{}.backup", appimage_path);
const QString backup_appimage_path(qappimage_path + QStringLiteral(".backup")); if (!FileSystem::FileExists(backup_appimage_path.c_str()))
if (!QFile::exists(backup_appimage_path))
return; return;
INFO_LOG(QStringLiteral("Removing backup AppImage %1").arg(backup_appimage_path).toStdString().c_str()); Error error;
if (!QFile::remove(backup_appimage_path)) INFO_LOG("Removing backup AppImage: {}", backup_appimage_path);
{ if (!FileSystem::DeleteFile(backup_appimage_path.c_str(), &error))
ERROR_LOG(QStringLiteral("Failed to remove backup AppImage %1").arg(backup_appimage_path).toStdString().c_str()); ERROR_LOG("Failed to remove backup AppImage {}: {}", backup_appimage_path, error.GetDescription());
}
} }
#else #else