summaryrefslogtreecommitdiffstats
path: root/src/libs/installer/extractarchiveoperation.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/libs/installer/extractarchiveoperation.cpp')
-rw-r--r--src/libs/installer/extractarchiveoperation.cpp146
1 files changed, 131 insertions, 15 deletions
diff --git a/src/libs/installer/extractarchiveoperation.cpp b/src/libs/installer/extractarchiveoperation.cpp
index 7660dadb0..b00a67190 100644
--- a/src/libs/installer/extractarchiveoperation.cpp
+++ b/src/libs/installer/extractarchiveoperation.cpp
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2021 The Qt Company Ltd.
+** Copyright (C) 2023 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -30,6 +30,7 @@
#include "constants.h"
#include "globals.h"
+#include "fileguard.h"
#include <QEventLoop>
#include <QThreadPool>
@@ -45,14 +46,14 @@ namespace QInstaller {
*/
/*!
- \typedef QInstaller::Backup
+ \typedef QInstaller::ExtractArchiveOperation::Backup
Synonym for QPair<QString, QString>. Contains a pair
of an original and a generated backup filename for a file.
*/
/*!
- \typedef QInstaller::BackupFiles
+ \typedef QInstaller::ExtractArchiveOperation::BackupFiles
Synonym for QVector<Backup>.
*/
@@ -65,13 +66,64 @@ namespace QInstaller {
ExtractArchiveOperation::ExtractArchiveOperation(PackageManagerCore *core)
: UpdateOperation(core)
+ , m_totalEntries(0)
{
setName(QLatin1String("Extract"));
+ setGroup(OperationGroup::Unpack);
}
void ExtractArchiveOperation::backup()
{
- // we need to backup on the fly...
+ if (!checkArgumentCount(2))
+ return;
+
+ const QStringList args = arguments();
+ const QString archivePath = args.at(0);
+ const QString targetDir = args.at(1);
+
+ QScopedPointer<AbstractArchive> archive(ArchiveFactory::instance().create(archivePath));
+ if (!archive) {
+ setError(UserDefinedError);
+ setErrorString(tr("Unsupported archive \"%1\": no handler registered for file suffix \"%2\".")
+ .arg(archivePath, QFileInfo(archivePath).suffix()));
+ return;
+ }
+
+ if (!(archive->open(QIODevice::ReadOnly) && archive->isSupported())) {
+ setError(UserDefinedError);
+ setErrorString(tr("Cannot open archive \"%1\" for reading: %2")
+ .arg(archivePath, archive->errorString()));
+ return;
+ }
+ const QVector<ArchiveEntry> entries = archive->list();
+ if (entries.isEmpty()) {
+ setError(UserDefinedError);
+ setErrorString(tr("Error while reading contents of archive \"%1\": %2")
+ .arg(archivePath, archive->errorString()));
+ return;
+ }
+
+ const bool hasAdminRights = (packageManager() && packageManager()->hasAdminRights());
+ const bool canCreateSymLinks = QInstaller::canCreateSymbolicLinks();
+ bool needsAdminRights = false;
+
+ for (auto &entry : entries) {
+ const QString completeFilePath = targetDir + QDir::separator() + entry.path;
+ if (!entry.isDirectory) {
+ // Ignore failed backups, existing files are overwritten when extracting.
+ // Should the backups be used on rollback too, this may not be the
+ // desired behavior anymore.
+ prepareForFile(completeFilePath);
+ }
+ if (!hasAdminRights && !canCreateSymLinks && entry.isSymbolicLink)
+ needsAdminRights = true;
+ }
+ m_totalEntries = entries.size();
+ if (needsAdminRights)
+ setValue(QLatin1String("admin"), true);
+
+ // Show something was done
+ emit progressChanged(scBackupProgressPart);
}
bool ExtractArchiveOperation::performOperation()
@@ -88,7 +140,7 @@ bool ExtractArchiveOperation::performOperation()
connect(&callback, &Callback::progressChanged, this, &ExtractArchiveOperation::progressChanged);
- Worker *worker = new Worker(archivePath, targetDir, &callback);
+ Worker *worker = new Worker(archivePath, targetDir, m_totalEntries, &callback);
connect(worker, &Worker::finished, &receiver, &Receiver::workerFinished,
Qt::QueuedConnection);
@@ -126,10 +178,15 @@ bool ExtractArchiveOperation::performOperation()
QString installDir = targetDir;
// If we have package manager in use (normal installer run) then use
// TargetDir for saving filenames, otherwise those would be saved to
- // extracted folder.
- if (packageManager())
- installDir = packageManager()->value(scTargetDir);
+ // extracted folder. Also initialize installerbasebinary which we use later
+ // to check if the extracted file in question is the maintenancetool itself.
+ QString installerBaseBinary;
+ if (PackageManagerCore *core = packageManager()) {
+ installDir = core->value(scTargetDir);
+ installerBaseBinary = core->toNativeSeparators(core->replaceVariables(core->installerBaseBinary()));
+ }
const QString resourcesPath = installDir + QLatin1Char('/') + QLatin1String("installerResources");
+
QString fileDirectory = resourcesPath + QLatin1Char('/') + archivePath.section(QLatin1Char('/'), 1, 1,
QString::SectionSkipEmpty) + QLatin1Char('/');
QString archiveFileName = archivePath.section(QLatin1Char('/'), 2, 2, QString::SectionSkipEmpty);
@@ -140,10 +197,7 @@ bool ExtractArchiveOperation::performOperation()
QFileInfo targetDirectoryInfo(fileDirectory);
- QDir dir(targetDirectoryInfo.absolutePath());
- if (!dir.exists()) {
- dir.mkpath(targetDirectoryInfo.absolutePath());
- }
+ QInstaller::createDirectoryWithParents(targetDirectoryInfo.absolutePath());
setDefaultFilePermissions(resourcesPath, DefaultFilePermissions::Executable);
setDefaultFilePermissions(targetDirectoryInfo.absolutePath(), DefaultFilePermissions::Executable);
@@ -152,9 +206,17 @@ bool ExtractArchiveOperation::performOperation()
setDefaultFilePermissions(file.fileName(), DefaultFilePermissions::NonExecutable);
QDataStream out (&file);
for (int i = 0; i < files.count(); ++i) {
+ if (!installerBaseBinary.isEmpty() && files[i].startsWith(installerBaseBinary)) {
+ // Do not write installerbase binary filename to extracted files. Installer binary
+ // is maintenance tool program, the binary is removed elsewhere
+ // when we do full uninstall.
+ files.clear();
+ break;
+ }
files[i] = replacePath(files.at(i), installDir, QLatin1String(scRelocatable));
}
- out << files;
+ if (!files.isEmpty())
+ out << files;
setValue(QLatin1String("files"), file.fileName());
file.close();
} else {
@@ -164,7 +226,7 @@ bool ExtractArchiveOperation::performOperation()
// TODO: Use backups for rollback, too? Doesn't work for uninstallation though.
// delete all backups we can delete right now, remember the rest
- foreach (const QInstaller::Backup &i, callback.backupFiles())
+ foreach (const Backup &i, m_backupFiles)
deleteFileNowOrLater(i.second);
if (!receiver.success()) {
@@ -192,10 +254,14 @@ bool ExtractArchiveOperation::undoOperation()
if (!readDataFileContents(targetDir, &files))
return false;
}
- startUndoProcess(files);
+ if (!files.isEmpty())
+ startUndoProcess(files);
if (!useStringListType)
deleteDataFile(m_relocatedDataFileName);
+ // Remove the installerResources directory if it is empty.
+ QDir(targetDir).rmdir(QLatin1String("installerResources"));
+
return true;
}
@@ -207,6 +273,9 @@ void ExtractArchiveOperation::startUndoProcess(const QStringList &files)
connect(thread, &WorkerThread::progressChanged, this,
&ExtractArchiveOperation::progressChanged);
+ const QFileInfo archive(arguments().at(0));
+ emit outputTextChanged(tr("Removing files extracted from \"%1\"").arg(archive.fileName()));
+
QEventLoop loop;
connect(thread, &QThread::finished, &loop, &QEventLoop::quit, Qt::QueuedConnection);
thread->start();
@@ -231,11 +300,58 @@ void ExtractArchiveOperation::deleteDataFile(const QString &fileName)
}
}
+QString ExtractArchiveOperation::generateBackupName(const QString &fn)
+{
+ const QString bfn = fn + QLatin1String(".tmpUpdate");
+ QString res = bfn;
+ int i = 0;
+ while (QFile::exists(res))
+ res = bfn + QString::fromLatin1(".%1").arg(i++);
+ return res;
+}
+
+bool ExtractArchiveOperation::prepareForFile(const QString &filename)
+{
+ if (!QFile::exists(filename))
+ return true;
+
+ FileGuardLocker locker(filename, FileGuard::globalObject());
+
+ const QString backup = generateBackupName(filename);
+ QFile f(filename);
+ const bool renamed = f.rename(backup);
+ if (f.exists() && !renamed) {
+ qCritical("Cannot rename %s to %s: %s", qPrintable(filename), qPrintable(backup),
+ qPrintable(f.errorString()));
+ return false;
+ }
+ m_backupFiles.append(qMakePair(filename, backup));
+ return true;
+}
+
bool ExtractArchiveOperation::testOperation()
{
return true;
}
+quint64 ExtractArchiveOperation::sizeHint()
+{
+ if (!checkArgumentCount(2))
+ return UpdateOperation::sizeHint();
+
+ if (hasValue(QLatin1String("sizeHint")))
+ return value(QLatin1String("sizeHint")).toULongLong();
+
+ const QString archivePath = arguments().at(0);
+ const quint64 compressedSize = QFileInfo(archivePath).size();
+
+ setValue(QLatin1String("sizeHint"), QString::number(compressedSize));
+
+ // A rough estimate of how much time it takes to extract this archive. Other
+ // affecting parameters are the archive format, compression filter and -level.
+ return compressedSize;
+}
+
bool ExtractArchiveOperation::readDataFileContents(QString &targetDir, QStringList *resultList)
{
const QString filePath = value(QLatin1String("files")).toString();