File indexing completed on 2024-05-05 05:48:52

0001 /*
0002     SPDX-FileCopyrightText: 2008-2010 Volker Lanz <vl@fidra.de>
0003     SPDX-FileCopyrightText: 2012-2019 Andrius Štikonas <andrius@stikonas.eu>
0004     SPDX-FileCopyrightText: 2015 Teo Mrnjavac <teo@kde.org>
0005     SPDX-FileCopyrightText: 2016 Chantara Tith <tith.chantara@gmail.com>
0006 
0007     SPDX-License-Identifier: GPL-3.0-or-later
0008 */
0009 
0010 #include "ops/restoreoperation.h"
0011 
0012 #include "core/partition.h"
0013 #include "core/device.h"
0014 #include "core/partitiontable.h"
0015 #include "core/partitionnode.h"
0016 
0017 #include "jobs/createpartitionjob.h"
0018 #include "jobs/deletepartitionjob.h"
0019 #include "jobs/checkfilesystemjob.h"
0020 #include "jobs/restorefilesystemjob.h"
0021 #include "jobs/resizefilesystemjob.h"
0022 
0023 #include "fs/filesystem.h"
0024 #include "fs/filesystemfactory.h"
0025 #include "fs/luks.h"
0026 
0027 #include "util/capacity.h"
0028 #include "util/report.h"
0029 
0030 #include <QDebug>
0031 #include <QString>
0032 #include <QFileInfo>
0033 
0034 #include <KLocalizedString>
0035 
0036 /** Creates a new RestoreOperation.
0037     @param d the Device to restore the Partition to
0038     @param p pointer to the Partition that will be restored. May not be nullptr.
0039     @param filename name of the image file to restore from
0040 */
0041 RestoreOperation::RestoreOperation(Device& d, Partition* p, const QString& filename) :
0042     Operation(),
0043     m_TargetDevice(d),
0044     m_RestorePartition(p),
0045     m_FileName(filename),
0046     m_OverwrittenPartition(nullptr),
0047     m_MustDeleteOverwritten(false),
0048     m_ImageLength(QFileInfo(filename).size() / 512), // 512 being the "sector size" of an image file.
0049     m_CreatePartitionJob(nullptr),
0050     m_RestoreJob(nullptr),
0051     m_CheckTargetJob(nullptr),
0052     m_MaximizeJob(nullptr)
0053 {
0054     restorePartition().setState(Partition::State::Restore);
0055 
0056     Q_ASSERT(targetDevice().partitionTable());
0057 
0058     Partition* dest = targetDevice().partitionTable()->findPartitionBySector(restorePartition().firstSector(), PartitionRole(PartitionRole::Primary | PartitionRole::Logical | PartitionRole::Unallocated));
0059 
0060     if (dest == nullptr)
0061         qWarning() << "destination partition not found at sector " << restorePartition().firstSector();
0062 
0063     Q_ASSERT(dest);
0064 
0065     if (dest && !dest->roles().has(PartitionRole::Unallocated)) {
0066         restorePartition().setLastSector(dest->lastSector());
0067         setOverwrittenPartition(dest);
0068         removePreviewPartition(targetDevice(), *dest);
0069     }
0070 
0071     if (!overwrittenPartition())
0072         addJob(m_CreatePartitionJob = new CreatePartitionJob(targetDevice(), restorePartition()));
0073 
0074     addJob(m_RestoreJob = new RestoreFileSystemJob(targetDevice(), restorePartition(), fileName()));
0075     addJob(m_CheckTargetJob = new CheckFileSystemJob(restorePartition()));
0076     addJob(m_MaximizeJob = new ResizeFileSystemJob(targetDevice(), restorePartition()));
0077 }
0078 
0079 RestoreOperation::~RestoreOperation()
0080 {
0081     if (status() == StatusPending)
0082         delete m_RestorePartition;
0083 
0084     if (status() == StatusFinishedSuccess || status() == StatusFinishedWarning || status() == StatusError)
0085         cleanupOverwrittenPartition();
0086 }
0087 
0088 bool RestoreOperation::targets(const Device& d) const
0089 {
0090     return d == targetDevice();
0091 }
0092 
0093 bool RestoreOperation::targets(const Partition& p) const
0094 {
0095     return p == restorePartition();
0096 }
0097 
0098 void RestoreOperation::preview()
0099 {
0100     insertPreviewPartition(targetDevice(), restorePartition());
0101 }
0102 
0103 void RestoreOperation::undo()
0104 {
0105     removePreviewPartition(targetDevice(), restorePartition());
0106 
0107     if (overwrittenPartition())
0108         insertPreviewPartition(targetDevice(), *overwrittenPartition());
0109 }
0110 
0111 bool RestoreOperation::execute(Report& parent)
0112 {
0113     bool rval = false;
0114     bool warning = false;
0115 
0116     Report* report = parent.newChild(description());
0117 
0118     if (overwrittenPartition())
0119         restorePartition().setPartitionPath(overwrittenPartition()->partitionPath());
0120 
0121     if (overwrittenPartition() || (rval = createPartitionJob()->run(*report))) {
0122         restorePartition().setState(Partition::State::None);
0123 
0124         if ((rval = restoreJob()->run(*report))) {
0125             if ((rval = checkTargetJob()->run(*report))) {
0126                 // If the partition was written over an existing one, the partition itself may now
0127                 // be larger than the filesystem, so maximize the filesystem to the partition's size
0128                 // or the image length, whichever is larger. If this fails, don't return an error, just
0129                 // warn the user.
0130                 if ((warning = !maximizeJob()->run(*report)))
0131                     report->line() << xi18nc("@info:status", "<warning>Maximizing file system on target partition <filename>%1</filename> to the size of the partition failed.</warning>", restorePartition().deviceNode());
0132             } else
0133                 report->line() << xi18nc("@info:status", "Checking target file system on partition <filename>%1</filename> after the restore failed.", restorePartition().deviceNode());
0134         } else {
0135             if (!overwrittenPartition())
0136                 DeletePartitionJob(targetDevice(), restorePartition()).run(*report);
0137 
0138             report->line() << xi18nc("@info:status", "Restoring file system failed.");
0139         }
0140     } else
0141         report->line() << xi18nc("@info:status", "Creating the destination partition to restore to failed.");
0142 
0143     if (rval)
0144         setStatus(warning ? StatusFinishedWarning : StatusFinishedSuccess);
0145     else
0146         setStatus(StatusError);
0147 
0148     report->setStatus(xi18nc("@info:status (success, error, warning...) of operation", "%1: %2", description(), statusText()));
0149 
0150     return rval;
0151 }
0152 
0153 QString RestoreOperation::description() const
0154 {
0155     if (overwrittenPartition())
0156         return xi18nc("@info:status", "Restore partition from <filename>%1</filename> to <filename>%2</filename>", fileName(), overwrittenPartition()->deviceNode());
0157 
0158     return xi18nc("@info:status", "Restore partition on <filename>%1</filename> at %2 from <filename>%3</filename>", targetDevice().deviceNode(), Capacity::formatByteSize(restorePartition().firstSector() * targetDevice().logicalSize()), fileName());
0159 }
0160 
0161 void RestoreOperation::setOverwrittenPartition(Partition* p)
0162 {
0163     // This is copied from CopyOperation. One day we might create a common base class ;-)
0164 
0165     cleanupOverwrittenPartition();
0166     m_OverwrittenPartition = p;
0167     m_MustDeleteOverwritten = (p && p->state() == Partition::State::None);
0168 }
0169 
0170 void RestoreOperation::cleanupOverwrittenPartition()
0171 {
0172     if (mustDeleteOverwritten()) {
0173         delete overwrittenPartition();
0174         m_OverwrittenPartition = nullptr;
0175     }
0176 }
0177 
0178 /** Can a Partition be restored to somewhere?
0179     @param p the Partition in question, may be nullptr.
0180     @return true if a Partition can be restored to @p p.
0181  */
0182 bool RestoreOperation::canRestore(const Partition* p)
0183 {
0184     if (p == nullptr)
0185         return false;
0186 
0187     if (p->isMounted())
0188         return false;
0189 
0190     if (p->roles().has(PartitionRole::Extended))
0191         return false;
0192 
0193     if (p->roles().has(PartitionRole::Luks)) {
0194         const FS::luks* luksFs = static_cast<const FS::luks*>(&p->fileSystem());
0195         return luksFs->mapperName().isEmpty();
0196     }
0197 
0198     return true;
0199 }
0200 /** Creates a new Partition to restore to.
0201     @param device the Device to create the Partition on
0202     @param parent the parent PartitionNode
0203     @param start start sector of the Partition
0204     @param filename name of the image file to restore from
0205 */
0206 Partition* RestoreOperation::createRestorePartition(const Device& device, PartitionNode& parent, qint64 start, const QString& filename)
0207 {
0208     PartitionRole::Roles r = PartitionRole::Primary;
0209 
0210     if (!parent.isRoot())
0211         r = PartitionRole::Logical;
0212 
0213     QFileInfo fileInfo(filename);
0214 
0215     if (!fileInfo.exists())
0216         return nullptr;
0217 
0218     const qint64 end = start + fileInfo.size() / device.logicalSize() - 1;
0219     Partition* p = new Partition(&parent, device, PartitionRole(r), FileSystemFactory::create(FileSystem::Type::Unknown, start, end, device.logicalSize()), start, end, QString());
0220 
0221     p->setState(Partition::State::Restore);
0222     return p;
0223 }
0224