LCOV - code coverage report
Current view: top level - manager-lib - installationtask.cpp (source / functions) Coverage Total Hit
Test: QtApplicationManager Lines: 91.0 % 211 192
Test Date: 2024-09-28 10:03:39 Functions: 100.0 % 13 13
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 53.7 % 428 230

             Branch data     Line data    Source code
       1                 :             : // Copyright (C) 2021 The Qt Company Ltd.
       2                 :             : // Copyright (C) 2019 Luxoft Sweden AB
       3                 :             : // Copyright (C) 2018 Pelagicore AG
       4                 :             : // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
       5                 :             : 
       6                 :             : #include <QTemporaryDir>
       7                 :             : #include <QMessageAuthenticationCode>
       8                 :             : #include <QPointer>
       9                 :             : 
      10                 :             : #include "logging.h"
      11                 :             : #include "packagemanager_p.h"
      12                 :             : #include "package.h"
      13                 :             : #include "packageinfo.h"
      14                 :             : #include "packageextractor.h"
      15                 :             : #include "application.h"
      16                 :             : #include "applicationinfo.h"
      17                 :             : #include "exception.h"
      18                 :             : #include "packagemanager.h"
      19                 :             : #include "utilities.h"
      20                 :             : #include "signature.h"
      21                 :             : #include "installationtask.h"
      22                 :             : 
      23                 :             : #include <memory>
      24                 :             : #ifdef Q_OS_UNIX
      25                 :             : #  include <unistd.h>
      26                 :             : #endif
      27                 :             : 
      28                 :             : using namespace Qt::StringLiterals;
      29                 :             : 
      30                 :             : 
      31                 :             : /*
      32                 :             :   Overview of what happens on an installation of an app with <id> to <location>:
      33                 :             : 
      34                 :             :   Step 1 -- startInstallation()
      35                 :             :   =============================
      36                 :             : 
      37                 :             :   delete <location>/<id>+
      38                 :             : 
      39                 :             :   create dir <location>/<id>+
      40                 :             :   set <extractiondir> to <location>/<id>+
      41                 :             : 
      42                 :             : 
      43                 :             :   Step 2 -- unpack files
      44                 :             :   ======================
      45                 :             : 
      46                 :             :   PackageExtractor does its job
      47                 :             : 
      48                 :             : 
      49                 :             :   Step 3 -- finishInstallation()
      50                 :             :   ================================
      51                 :             : 
      52                 :             :   if (exists <location>/<id>)
      53                 :             :       set <isupdate> to <true>
      54                 :             : 
      55                 :             :   create installation report at <extractiondir>/.installation-report.yaml
      56                 :             : 
      57                 :             :   if (not <isupdate>)
      58                 :             :       create document directory
      59                 :             : 
      60                 :             :   if (optional uid separation)
      61                 :             :       chown/chmod recursively in <extractiondir> and document directory
      62                 :             : 
      63                 :             : 
      64                 :             :   Step 3.1 -- final rename in finishInstallation()
      65                 :             :   ==================================================
      66                 :             : 
      67                 :             :   if (<isupdate>)
      68                 :             :       rename <location>/<id> to <location>/<id>-
      69                 :             :   rename <location>/<id>+ to <location>/<id>
      70                 :             : */
      71                 :             : 
      72                 :             : QT_BEGIN_NAMESPACE_AM
      73                 :             : 
      74                 :             : 
      75                 :             : 
      76                 :             : // The standard QTemporaryDir destructor cannot cope with read-only sub-directories.
      77                 :             : class TemporaryDir : public QTemporaryDir
      78                 :             : {
      79                 :             : public:
      80                 :          48 :     TemporaryDir()
      81                 :          48 :         : QTemporaryDir()
      82                 :          48 :     { }
      83                 :             :     explicit TemporaryDir(const QString &templateName)
      84                 :             :         : QTemporaryDir(templateName)
      85                 :             :     { }
      86                 :          48 :     ~TemporaryDir()
      87                 :             :     {
      88                 :          48 :         recursiveOperation(path(), safeRemove);
      89                 :          48 :     }
      90                 :             : private:
      91                 :             :     Q_DISABLE_COPY_MOVE(TemporaryDir)
      92                 :             : };
      93                 :             : 
      94                 :             : 
      95                 :             : QMutex InstallationTask::s_serializeFinishInstallation { };
      96                 :             : 
      97                 :          49 : InstallationTask::InstallationTask(const QString &installationPath, const QString &documentPath,
      98                 :          49 :                                    const QUrl &sourceUrl, QObject *parent)
      99                 :             :     : AsynchronousTask(parent)
     100                 :          98 :     , m_pm(PackageManager::instance())
     101                 :          49 :     , m_installationPath(installationPath)
     102                 :          49 :     , m_documentPath(documentPath)
     103   [ +  -  +  -  :         196 :     , m_sourceUrl(sourceUrl)
          +  -  +  -  +  
                      - ]
     104                 :             : {
     105         [ +  - ]:          49 :     setObjectName(u"QtAM-InstallationTask"_s);
     106                 :          49 : }
     107                 :             : 
     108                 :          98 : InstallationTask::~InstallationTask()
     109                 :          98 : { }
     110                 :             : 
     111                 :           8 : bool InstallationTask::cancel()
     112                 :             : {
     113                 :           8 :     QMutexLocker locker(&m_mutex);
     114                 :             : 
     115                 :             :     // we cannot cancel anymore after finishInstallation() has been called
     116         [ +  - ]:           8 :     if (m_installationAcknowledged)
     117                 :             :         return false;
     118                 :             : 
     119                 :           8 :     m_canceled = true;
     120         [ +  + ]:           8 :     if (m_extractor)
     121         [ +  - ]:           7 :         m_extractor->cancel();
     122         [ +  - ]:           8 :     m_installationAcknowledgeWaitCondition.wakeAll();
     123                 :             :     return true;
     124                 :           8 : }
     125                 :             : 
     126                 :          40 : void InstallationTask::acknowledge()
     127                 :             : {
     128                 :          40 :     QMutexLocker locker(&m_mutex);
     129                 :             : 
     130         [ -  + ]:          40 :     if (m_canceled)
     131         [ #  # ]:           0 :         return;
     132                 :             : 
     133                 :          40 :     m_installationAcknowledged = true;
     134         [ +  - ]:          40 :     m_installationAcknowledgeWaitCondition.wakeAll();
     135                 :          40 : }
     136                 :             : 
     137                 :          48 : void InstallationTask::execute()
     138                 :             : {
     139                 :          48 :     try {
     140         [ -  + ]:          48 :         if (m_installationPath.isEmpty())
     141                 :           0 :             throw Exception("no installation location was configured");
     142                 :             : 
     143         [ +  - ]:          48 :         TemporaryDir extractionDir;
     144   [ +  -  -  + ]:          48 :         if (!extractionDir.isValid())
     145                 :           0 :             throw Exception("could not create a temporary extraction directory");
     146                 :             : 
     147                 :             :         // protect m_canceled and changes to m_extractor
     148                 :          48 :         QMutexLocker locker(&m_mutex);
     149         [ +  + ]:          48 :         if (m_canceled)
     150                 :           1 :             throw Exception(Error::Canceled, "canceled");
     151                 :             : 
     152   [ +  -  +  -  :          47 :         m_extractor = new PackageExtractor(m_sourceUrl, QDir(extractionDir.path()));
             +  -  +  - ]
     153                 :          47 :         locker.unlock();
     154                 :             : 
     155         [ +  - ]:          47 :         connect(m_extractor, &PackageExtractor::progress, this, &AsynchronousTask::progress);
     156                 :             : 
     157   [ +  -  +  + ]:         155 :         m_extractor->setFileExtractedCallback([this](const QString &f) { checkExtractedFile(f); });
     158                 :             : 
     159   [ +  -  +  + ]:          47 :         if (!m_extractor->extract())
     160   [ +  -  +  - ]:          10 :             throw Exception(m_extractor->errorCode(), m_extractor->errorString());
     161                 :             : 
     162   [ +  -  -  + ]:          37 :         if (!m_foundInfo || !m_foundIcon)
     163                 :           0 :             throw Exception(Error::Package, "package did not contain a valid info.yaml and icon file");
     164                 :             : 
     165         [ +  - ]:          37 :         QByteArrayList chainOfTrust = m_pm->caCertificates();
     166                 :             : 
     167   [ +  -  +  + ]:          37 :         if (!m_pm->allowInstallationOfUnsignedPackages()) {
     168   [ +  -  +  -  :          15 :             if (!m_extractor->installationReport().storeSignature().isEmpty()) {
                   +  + ]
     169                 :             :                 // normal package from the store
     170   [ +  -  +  - ]:           1 :                 QByteArray sigDigest = m_extractor->installationReport().digest();
     171                 :           1 :                 bool sigOk = false;
     172                 :             : 
     173   [ +  -  +  -  :           1 :                 if (Signature(sigDigest).verify(m_extractor->installationReport().storeSignature(), chainOfTrust)) {
          +  -  +  -  +  
                      - ]
     174                 :             :                     sigOk = true;
     175   [ +  -  +  - ]:           1 :                 } else if (!m_pm->hardwareId().isEmpty()) {
     176                 :             :                     // did not verify - if we have a hardware-id, try to verify with it
     177   [ +  -  +  - ]:           2 :                     sigDigest = QMessageAuthenticationCode::hash(sigDigest, m_pm->hardwareId().toUtf8(), QCryptographicHash::Sha256);
     178   [ +  -  +  -  :           1 :                     if (Signature(sigDigest).verify(m_extractor->installationReport().storeSignature(), chainOfTrust))
          +  -  +  -  -  
                      + ]
     179                 :             :                         sigOk = true;
     180                 :             :                 }
     181                 :             :                 if (!sigOk)
     182                 :           0 :                     throw Exception(Error::Package, "could not verify the package's store signature");
     183   [ +  -  +  -  :          15 :             } else if (!m_extractor->installationReport().developerSignature().isEmpty()) {
                   +  + ]
     184                 :             :                 // developer package - needs a device in dev mode
     185   [ +  -  +  + ]:          12 :                 if (!m_pm->developmentMode())
     186                 :           1 :                     throw Exception(Error::Package, "cannot install development packages on consumer devices");
     187                 :             : 
     188   [ +  -  +  -  :          11 :                 if (!Signature(m_extractor->installationReport().digest()).verify(m_extractor->installationReport().developerSignature(), chainOfTrust))
          +  -  +  -  +  
             -  +  -  +  
                      + ]
     189                 :           1 :                     throw Exception(Error::Package, "could not verify the package's developer signature");
     190                 :             : 
     191                 :             :             } else {
     192                 :           2 :                 throw Exception(Error::Package, "cannot install unsigned packages");
     193                 :             :             }
     194                 :             :         }
     195                 :             : 
     196         [ +  - ]:          33 :         emit finishedPackageExtraction();
     197         [ +  - ]:          33 :         setState(AwaitingAcknowledge);
     198                 :             : 
     199                 :             :         // now wait in a wait-condition until we get an acknowledge or we get canceled
     200                 :          33 :         locker.relock();
     201   [ +  +  +  + ]:          54 :         while (!m_canceled && !m_installationAcknowledged)
     202         [ +  - ]:          33 :             m_installationAcknowledgeWaitCondition.wait(&m_mutex);
     203                 :             : 
     204                 :             :         // this is the last cancellation point
     205         [ +  + ]:          33 :         if (m_canceled)
     206                 :           7 :             throw Exception(Error::Canceled, "canceled");
     207                 :          26 :         locker.unlock();
     208                 :             : 
     209         [ +  - ]:          26 :         setState(Installing);
     210                 :             : 
     211                 :             :         // However many downloads are allowed to happen in parallel: we need to serialize those
     212                 :             :         // tasks here for the finishInstallation() step
     213                 :          26 :         QMutexLocker finishLocker(&s_serializeFinishInstallation);
     214                 :             : 
     215         [ +  + ]:          26 :         finishInstallation();
     216                 :             : 
     217                 :             :         // At this point, the installation is done, so we cannot throw anymore.
     218                 :             : 
     219                 :             :         // we need to call those PackageManager methods in the correct thread
     220                 :          25 :         bool finishOk = false;
     221   [ +  -  +  - ]:          25 :         QMetaObject::invokeMethod(PackageManager::instance(), [this, &finishOk]()
     222                 :          25 :             { finishOk = PackageManager::instance()->finishedPackageInstall(m_packageId); },
     223                 :             :             Qt::BlockingQueuedConnection);
     224                 :             : 
     225         [ -  + ]:          25 :         if (!finishOk)
     226   [ -  -  -  -  :           1 :             qCWarning(LogInstaller) << "PackageManager rejected the installation of " << m_packageId;
          -  -  -  -  -  
                      - ]
     227                 :             : 
     228         [ -  + ]:          84 :     } catch (const Exception &e) {
     229         [ +  - ]:          23 :         setError(e.errorCode(), e.errorString());
     230                 :             : 
     231         [ +  + ]:          23 :         if (m_managerApproval) {
     232                 :             :             // we need to call those ApplicationManager methods in the correct thread
     233                 :          13 :             bool cancelOk = false;
     234   [ +  -  +  - ]:          13 :             QMetaObject::invokeMethod(PackageManager::instance(), [this, &cancelOk]()
     235                 :          13 :                 { cancelOk = PackageManager::instance()->canceledPackageInstall(m_packageId); },
     236                 :             :                 Qt::BlockingQueuedConnection);
     237                 :             : 
     238         [ -  + ]:          13 :             if (!cancelOk)
     239   [ -  -  -  -  :           0 :                 qCWarning(LogInstaller) << "PackageManager could not remove package" << m_packageId << "after a failed installation";
          -  -  -  -  -  
                -  -  - ]
     240                 :             :         }
     241                 :          23 :     }
     242                 :             : 
     243                 :             : 
     244                 :          48 :     {
     245                 :          48 :         QMutexLocker locker(&m_mutex);
     246         [ +  + ]:          48 :         delete m_extractor;
     247         [ +  - ]:          48 :         m_extractor = nullptr;
     248                 :          48 :     }
     249                 :          48 : }
     250                 :             : 
     251                 :             : 
     252                 :          85 : void InstallationTask::checkExtractedFile(const QString &file) noexcept(false)
     253                 :             : {
     254                 :          85 :     ++m_extractedFileCount;
     255                 :             : 
     256         [ +  + ]:          85 :     if (m_extractedFileCount == 1) {
     257         [ -  + ]:          44 :         if (file != u"info.yaml")
     258                 :           0 :             throw Exception(Error::Package, "info.yaml must be the first file in the package. Got %1")
     259                 :           0 :                 .arg(file);
     260                 :             : 
     261   [ +  -  +  +  :          46 :         m_package.reset(PackageInfo::fromManifest(m_extractor->destinationDirectory().absoluteFilePath(file)));
                   -  + ]
     262   [ +  -  +  + ]:          42 :         if (m_package->id() != m_extractor->installationReport().packageId())
     263                 :           1 :             throw Exception(Error::Package, "the package identifiers in --PACKAGE-HEADER--' and info.yaml do not match");
     264                 :             : 
     265                 :          41 :         m_iconFileName = m_package->icon(); // store it separately as we will give away ApplicationInfo later on
     266                 :             : 
     267         [ -  + ]:          41 :         if (m_iconFileName.isEmpty())
     268                 :           0 :             throw Exception(Error::Package, "the 'icon' field in info.yaml cannot be empty or absent.");
     269                 :             : 
     270                 :          41 :         m_mutex.lock();
     271                 :          41 :         m_packageId = m_package->id();
     272                 :          41 :         m_mutex.unlock();
     273                 :             : 
     274                 :          41 :         m_foundInfo = true;
     275         [ +  - ]:          41 :     } else if (m_extractedFileCount == 2) {
     276                 :             :         // the second file must be the icon
     277                 :             : 
     278                 :          41 :         Q_ASSERT(m_foundInfo);
     279                 :          41 :         Q_ASSERT(!m_foundIcon);
     280                 :             : 
     281         [ +  + ]:          41 :         if (file != m_iconFileName)
     282                 :           2 :             throw Exception(Error::Package,
     283                 :             :                     "The package icon (as stated in info.yaml) must be the second file in the package."
     284                 :           3 :                     " Expected '%1', got '%2'").arg(m_iconFileName, file);
     285                 :             : 
     286   [ +  -  +  - ]:          80 :         QFile icon(m_extractor->destinationDirectory().absoluteFilePath(file));
     287                 :             : 
     288   [ +  -  -  + ]:          40 :         if (icon.size() > 256*1024)
     289                 :           0 :             throw Exception(Error::Package, "the size of %1 is too large (max. 256KB)").arg(file);
     290                 :             : 
     291                 :          40 :         m_foundIcon = true;
     292                 :          40 :     } else {
     293                 :           0 :         throw Exception(Error::Package, "Could not find info.yaml and the icon file at the beginning of the package.");
     294                 :             :     }
     295                 :             : 
     296   [ +  +  +  - ]:          81 :     if (m_foundIcon && m_foundInfo) {
     297                 :             :         // we're not interested in any other files from here on...
     298         [ +  - ]:          40 :         m_extractor->setFileExtractedCallback(nullptr);
     299                 :             : 
     300                 :          40 :         bool doubleInstallation = false;
     301                 :          40 :         QMetaObject::invokeMethod(PackageManager::instance(), [this, &doubleInstallation]() {
     302                 :          40 :             doubleInstallation = PackageManager::instance()->isPackageInstallationActive(m_packageId);
     303                 :          40 :         }, Qt::BlockingQueuedConnection);
     304         [ +  + ]:          40 :         if (doubleInstallation)
     305                 :           1 :             throw Exception(Error::Package, "Cannot install the same package %1 multiple times in parallel").arg(m_packageId);
     306                 :             : 
     307                 :          39 :         QDir oldDestinationDirectory = m_extractor->destinationDirectory();
     308                 :             : 
     309         [ +  + ]:          39 :         startInstallation();
     310                 :             : 
     311   [ +  -  +  -  :         114 :         QFile::copy(oldDestinationDirectory.filePath(u"info.yaml"_s), m_extractionDir.filePath(u"info.yaml"_s));
                   +  - ]
     312   [ +  -  +  -  :          76 :         QFile::copy(oldDestinationDirectory.filePath(m_iconFileName), m_extractionDir.filePath(m_iconFileName));
                   +  - ]
     313                 :             : 
     314                 :          38 :         {
     315                 :          38 :             QMutexLocker locker(&m_mutex);
     316         [ +  - ]:          38 :             m_extractor->setDestinationDirectory(m_extractionDir);
     317                 :             : 
     318         [ +  - ]:          38 :             QString path = m_extractionDir.absolutePath();
     319         [ +  - ]:          38 :             path.chop(1); // remove the '+'
     320   [ +  -  +  - ]:          38 :             m_package->setBaseDir(QDir(path));
     321         [ +  - ]:          38 :         }
     322                 :             : 
     323                 :             :         // we need to call those ApplicationManager methods in the correct thread
     324                 :             :         // this will also exclusively lock the application for us
     325                 :             :         // m_package ownership is transferred to the ApplicationManager
     326         [ +  - ]:          38 :         QString packageId = m_package->id(); // m_package is gone after the invoke
     327                 :          38 :         QPointer<Package> newPackage;
     328   [ +  -  +  - ]:          76 :         QMetaObject::invokeMethod(PackageManager::instance(), [this, &newPackage]()
     329                 :          38 :             { newPackage = PackageManager::instance()->startingPackageInstallation(m_package.release()); },
     330                 :             :             Qt::BlockingQueuedConnection);
     331                 :          38 :         m_managerApproval = !newPackage.isNull();
     332                 :             : 
     333         [ -  + ]:          38 :         if (!m_managerApproval)
     334                 :           0 :             throw Exception("PackageManager declined the installation of %1").arg(packageId);
     335                 :             : 
     336   [ +  -  +  -  :          78 :         qCDebug(LogInstaller) << "emit taskRequestingInstallationAcknowledge" << id() << "for package" << packageId;
          +  -  +  -  +  
          -  +  -  +  -  
                   +  + ]
     337                 :             : 
     338                 :             :         // Create temporary objects for QML just for the signal emission.
     339                 :             :         // The problem here is that the PackageInfo instance backing the Package object is also
     340                 :             :         // temporary and the ownership is with the C++ side of the PackageManager.
     341                 :             :         // Ideally we should have kept the 'package' parameter as a dumb QVariantMap, but changing
     342                 :             :         // that back would be a huge API break nowadays as the QML APIs are fully typed.
     343                 :             :         // At least we have to make sure NOT to change anything in the PackageInfo instance after
     344                 :             :         // the signal emission below.
     345   [ +  -  +  -  :          38 :         m_tempPackageForAcknowledge.reset(new Package(newPackage->info(), Package::BeingInstalled));
                   +  - ]
     346   [ +  -  +  - ]:          38 :         m_tempPackageForAcknowledge->moveToThread(m_pm->thread());
     347   [ +  -  +  - ]:          38 :         const auto &applicationInfos = newPackage->info()->applications();
     348         [ +  + ]:          78 :         for (const auto &applicationInfo : applicationInfos) {
     349   [ +  -  +  - ]:          40 :             auto tempApp = new Application(applicationInfo, m_tempPackageForAcknowledge.get());
     350   [ +  -  +  - ]:          40 :             tempApp->moveToThread(m_pm->thread());
     351         [ +  - ]:          40 :             m_tempPackageForAcknowledge->addApplication(tempApp);
     352         [ +  - ]:          40 :             m_tempApplicationsForAcknowledge.emplace_back(tempApp);
     353                 :             :         }
     354   [ +  -  +  - ]:          76 :         emit m_pm->taskRequestingInstallationAcknowledge(id(), m_tempPackageForAcknowledge.get(),
     355   [ +  -  +  - ]:          76 :                                                          m_extractor->installationReport().extraMetaData(),
     356   [ +  -  +  - ]:          38 :                                                          m_extractor->installationReport().extraSignedMetaData());
     357                 :             : 
     358                 :             :         // if any of the apps in the package were running before, we now need to wait until all of
     359                 :             :         // them have actually stopped
     360   [ +  -  +  -  :         120 :         while (!m_canceled && newPackage && !newPackage->areAllApplicationsStoppedDueToBlock())
             +  -  +  + ]
     361         [ +  - ]:           2 :             QThread::msleep(30);
     362                 :             : 
     363   [ +  -  +  - ]:          76 :         if (m_canceled || newPackage.isNull())
     364                 :           0 :             throw Exception(Error::Canceled, "canceled");
     365                 :          39 :     }
     366                 :          79 : }
     367                 :             : 
     368                 :          39 : void InstallationTask::startInstallation() noexcept(false)
     369                 :             : {
     370                 :             :     // 2. delete old, partial installation
     371                 :             : 
     372   [ +  -  +  - ]:          39 :     QDir installationDir = QString(m_installationPath + u'/');
     373         [ +  - ]:          39 :     QString installationTarget = m_packageId + u'+';
     374   [ +  -  -  + ]:          39 :     if (installationDir.exists(installationTarget)) {
     375   [ #  #  #  #  :           0 :         if (!removeRecursiveHelper(installationDir.absoluteFilePath(installationTarget)))
                   #  # ]
     376                 :           0 :             throw Exception("could not remove old, partial installation %1/%2").arg(installationDir).arg(installationTarget);
     377                 :             :     }
     378                 :             : 
     379                 :             :     // 4. create new installation
     380   [ +  -  +  -  :          39 :     if (!m_installationDirCreator.create(installationDir.absoluteFilePath(installationTarget)))
                   +  + ]
     381                 :           1 :         throw Exception("could not create installation directory %1/%2").arg(installationDir).arg(installationTarget);
     382         [ +  - ]:          38 :     m_extractionDir = installationDir;  // clazy:exclude=qt6-deprecated-api-fixes
     383   [ +  -  -  + ]:          38 :     if (!m_extractionDir.cd(installationTarget))
     384                 :           0 :         throw Exception("could not cd into installation directory %1/%2").arg(installationDir).arg(installationTarget);
     385   [ +  -  +  - ]:          77 :     m_applicationDir.setPath(installationDir.absoluteFilePath(m_packageId));
     386                 :          39 : }
     387                 :             : 
     388                 :          26 : void InstallationTask::finishInstallation() noexcept(false)
     389                 :             : {
     390                 :          26 :     QDir documentDirectory(m_documentPath);
     391         [ +  - ]:          26 :     ScopedDirectoryCreator documentDirCreator;
     392                 :             : 
     393                 :          26 :     enum { Installation, Update } mode = Installation;
     394                 :             : 
     395   [ +  -  +  + ]:          26 :     if (m_applicationDir.exists())
     396                 :           4 :         mode = Update;
     397                 :             : 
     398                 :             :     // create the installation report
     399         [ +  - ]:          26 :     InstallationReport report = m_extractor->installationReport();
     400                 :             : 
     401   [ +  -  +  - ]:          52 :     QFile reportFile(m_extractionDir.absoluteFilePath(u".installation-report.yaml"_s));
     402   [ +  -  +  -  :          26 :     if (!reportFile.open(QFile::WriteOnly) || !report.serialize(&reportFile))
             +  -  -  + ]
     403                 :           0 :         throw Exception(reportFile, "could not write the installation report");
     404         [ +  - ]:          26 :     reportFile.close();
     405                 :             : 
     406                 :             :     // create the document directories when installing (not needed on updates)
     407   [ +  +  +  - ]:          26 :     if ((mode == Installation) && !m_documentPath.isEmpty()) {
     408                 :             :         // this package may have been installed earlier and the document directory may not have been removed
     409   [ +  -  +  - ]:          22 :         if (!documentDirectory.cd(m_packageId)) {
     410   [ +  -  +  -  :          22 :             if (!documentDirCreator.create(documentDirectory.absoluteFilePath(m_packageId)))
                   +  + ]
     411         [ +  - ]:           1 :                 throw Exception(Error::IO, "could not create the document directory %1").arg(documentDirectory.filePath(m_packageId));
     412                 :             :         }
     413                 :             :     }
     414                 :             : 
     415                 :             :     // final rename
     416                 :             : 
     417                 :             :     // POSIX cannot atomically rename directories, if the destination directory exists
     418                 :             :     // and is non-empty. We need to do a double-rename in this case, which might fail!
     419                 :             : 
     420         [ +  - ]:          25 :     ScopedRenamer renameApplication;
     421                 :             : 
     422         [ +  + ]:          25 :     if (mode == Update) {
     423   [ +  -  -  + ]:           4 :         if (!renameApplication.rename(m_applicationDir, ScopedRenamer::NamePlusToName | ScopedRenamer::NameToNameMinus))
     424                 :           0 :             throw Exception(Error::IO, "could not rename application directory %1+ to %1 (including a backup to %1-)").arg(m_applicationDir);
     425                 :             :     } else {
     426   [ +  -  -  + ]:          21 :         if (!renameApplication.rename(m_applicationDir, ScopedRenamer::NamePlusToName))
     427                 :           0 :             throw Exception(Error::IO, "could not rename application directory %1+ to %1").arg(m_applicationDir);
     428                 :             :     }
     429                 :             : 
     430                 :             :     // from this point onwards, we are not allowed to throw anymore, since the installation is "done"
     431                 :             : 
     432         [ +  - ]:          25 :     setState(CleaningUp);
     433                 :             : 
     434         [ +  - ]:          25 :     renameApplication.take();
     435         [ +  - ]:          25 :     documentDirCreator.take();
     436                 :             : 
     437         [ +  - ]:          25 :     m_installationDirCreator.take();
     438                 :             : 
     439                 :             :     // this should not be necessary, but it also won't hurt
     440         [ +  + ]:          25 :     if (mode == Update)
     441   [ +  -  +  -  :           8 :         removeRecursiveHelper(m_applicationDir.absolutePath() + u'-');
                   +  - ]
     442                 :             : 
     443                 :             : #ifdef Q_OS_UNIX
     444                 :             :     // write files to the filesystem
     445                 :          25 :     sync();
     446                 :             : #endif
     447                 :             : 
     448                 :          25 :     m_errorString.clear();
     449                 :          29 : }
     450                 :             : 
     451                 :             : QT_END_NAMESPACE_AM
     452                 :             : 
     453                 :             : #include "moc_installationtask.cpp"
        

Generated by: LCOV version 2.0-1