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

0001 /*
0002     SPDX-FileCopyrightText: 2008-2010 Volker Lanz <vl@fidra.de>
0003     SPDX-FileCopyrightText: 2012-2019 Andrius Štikonas <andrius@stikonas.eu>
0004     SPDX-FileCopyrightText: 2016 Chantara Tith <tith.chantara@gmail.com>
0005 
0006     SPDX-License-Identifier: GPL-3.0-or-later
0007 */
0008 
0009 #include "ops/copyoperation.h"
0010 
0011 #include "core/partition.h"
0012 #include "core/device.h"
0013 
0014 #include "jobs/createpartitionjob.h"
0015 #include "jobs/deletepartitionjob.h"
0016 #include "jobs/checkfilesystemjob.h"
0017 #include "jobs/copyfilesystemjob.h"
0018 #include "jobs/resizefilesystemjob.h"
0019 
0020 #include "fs/filesystemfactory.h"
0021 
0022 #include "util/capacity.h"
0023 #include "util/report.h"
0024 
0025 #include <QDebug>
0026 #include <QString>
0027 
0028 #include <KLocalizedString>
0029 
0030 /** Creates a new CopyOperation.
0031     @param targetdevice the Device to copy the Partition to
0032     @param copiedpartition pointer to the new Partition object on the target Device. May not be nullptr.
0033     @param sourcedevice the Device where to copy from
0034     @param sourcepartition pointer to the Partition to copy from. May not be nullptr.
0035 */
0036 CopyOperation::CopyOperation(Device& targetdevice, Partition* copiedpartition, Device& sourcedevice, Partition* sourcepartition) :
0037     Operation(),
0038     m_TargetDevice(targetdevice),
0039     m_CopiedPartition(copiedpartition),
0040     m_SourceDevice(sourcedevice),
0041     m_SourcePartition(sourcepartition),
0042     m_OverwrittenPartition(nullptr),
0043     m_MustDeleteOverwritten(false),
0044     m_CheckSourceJob(nullptr),
0045     m_CreatePartitionJob(nullptr),
0046     m_CopyFSJob(nullptr),
0047     m_CheckTargetJob(nullptr),
0048     m_MaximizeJob(nullptr),
0049     m_Description(updateDescription())
0050 {
0051     Q_ASSERT(targetDevice().partitionTable());
0052 
0053     Partition* dest = targetDevice().partitionTable()->findPartitionBySector(copiedPartition().firstSector(), PartitionRole(PartitionRole::Primary | PartitionRole::Logical | PartitionRole::Unallocated));
0054 
0055     if (dest == nullptr)
0056         qWarning() << "destination partition not found at sector " << copiedPartition().firstSector();
0057 
0058     Q_ASSERT(dest);
0059 
0060     if (dest && !dest->roles().has(PartitionRole::Unallocated)) {
0061         copiedPartition().setLastSector(dest->lastSector());
0062         setOverwrittenPartition(dest);
0063     }
0064 
0065     addJob(m_CheckSourceJob = new CheckFileSystemJob(sourcePartition()));
0066 
0067     if (overwrittenPartition() == nullptr)
0068         addJob(m_CreatePartitionJob = new CreatePartitionJob(targetDevice(), copiedPartition()));
0069 
0070     addJob(m_CopyFSJob = new CopyFileSystemJob(targetDevice(), copiedPartition(), sourceDevice(), sourcePartition()));
0071     addJob(m_CheckTargetJob = new CheckFileSystemJob(copiedPartition()));
0072     addJob(m_MaximizeJob = new ResizeFileSystemJob(targetDevice(), copiedPartition()));
0073 }
0074 
0075 CopyOperation::~CopyOperation()
0076 {
0077     if (status() == StatusPending)
0078         delete m_CopiedPartition;
0079 
0080     if (status() == StatusFinishedSuccess || status() == StatusFinishedWarning || status() == StatusError)
0081         cleanupOverwrittenPartition();
0082 }
0083 
0084 bool CopyOperation::targets(const Device& d) const
0085 {
0086     return d == targetDevice();
0087 }
0088 
0089 bool CopyOperation::targets(const Partition& p) const
0090 {
0091     return p == copiedPartition();
0092 }
0093 
0094 void CopyOperation::preview()
0095 {
0096     if (overwrittenPartition())
0097         removePreviewPartition(targetDevice(), *overwrittenPartition());
0098 
0099     insertPreviewPartition(targetDevice(), copiedPartition());
0100 }
0101 
0102 void CopyOperation::undo()
0103 {
0104     removePreviewPartition(targetDevice(), copiedPartition());
0105 
0106     if (overwrittenPartition())
0107         insertPreviewPartition(targetDevice(), *overwrittenPartition());
0108 }
0109 
0110 bool CopyOperation::execute(Report& parent)
0111 {
0112     bool rval = false;
0113     bool warning = false;
0114 
0115     Report* report = parent.newChild(description());
0116 
0117     // check the source first
0118     if ((rval = checkSourceJob()->run(*report))) {
0119         // At this point, if the target partition is to be created and not overwritten, it
0120         // will still have the wrong device path (the one of the source device). We need
0121         // to adjust that before we're creating it.
0122         copiedPartition().setDevicePath(targetDevice().deviceNode());
0123 
0124         // either we have no partition to create (because we're overwriting) or creating
0125         // must be successful
0126         if (!createPartitionJob() || (rval = createPartitionJob()->run(*report))) {
0127             // set the state of the target partition from StateCopy to StateNone or checking
0128             // it will fail (because its deviceNode() will still be "Copy of sdXn"). This is
0129             // only required for overwritten partitions, but doesn't hurt in any case.
0130             copiedPartition().setState(Partition::State::None);
0131 
0132             // if we have overwritten a partition, reset device path and number
0133             if (overwrittenPartition()) {
0134                 copiedPartition().setDevicePath(overwrittenPartition()->devicePath());
0135                 copiedPartition().setPartitionPath(overwrittenPartition()->partitionPath());
0136             }
0137 
0138             // now run the copy job itself
0139             if ((rval = copyFSJob()->run(*report))) {
0140                 // and if the copy job succeeded, check the target
0141                 if ((rval = checkTargetJob()->run(*report))) {
0142                     // ok, everything went well
0143                     rval = true;
0144 
0145                     // if maximizing doesn't work, just warn the user, don't fail
0146                     if (!maximizeJob()->run(*report)) {
0147                         report->line() << xi18nc("@info:status", "<warning>Maximizing file system on target partition <filename>%1</filename> to the size of the partition failed.</warning>", copiedPartition().deviceNode());
0148                         warning = true;
0149                     }
0150                 } else
0151                     report->line() << xi18nc("@info:status", "Checking target partition <filename>%1</filename> after copy failed.", copiedPartition().deviceNode());
0152             } else {
0153                 if (createPartitionJob()) {
0154                     DeletePartitionJob deleteJob(targetDevice(), copiedPartition());
0155                     deleteJob.run(*report);
0156                 }
0157 
0158                 report->line() << xi18nc("@info:status", "Copying source to target partition failed.");
0159             }
0160         } else
0161             report->line() << xi18nc("@info:status", "Creating target partition for copying failed.");
0162     } else
0163         report->line() << xi18nc("@info:status", "Checking source partition <filename>%1</filename> failed.", sourcePartition().deviceNode());
0164 
0165     if (rval)
0166         setStatus(warning ? StatusFinishedWarning : StatusFinishedSuccess);
0167     else
0168         setStatus(StatusError);
0169 
0170     report->setStatus(xi18nc("@info:status (success, error, warning...) of operation", "%1: %2", description(), statusText()));
0171 
0172     return rval;
0173 }
0174 
0175 QString CopyOperation::updateDescription() const
0176 {
0177     if (overwrittenPartition()) {
0178         if (copiedPartition().length() == overwrittenPartition()->length())
0179             return xi18nc("@info:status", "Copy partition <filename>%1</filename> (%2, %3) to <filename>%4</filename> (%5, %6)",
0180                           sourcePartition().deviceNode(),
0181                           Capacity::formatByteSize(sourcePartition().capacity()),
0182                           sourcePartition().fileSystem().name(),
0183                           overwrittenPartition()->deviceNode(),
0184                           Capacity::formatByteSize(overwrittenPartition()->capacity()),
0185                           overwrittenPartition()->fileSystem().name()
0186                          );
0187 
0188         return xi18nc("@info:status", "Copy partition <filename>%1</filename> (%2, %3) to <filename>%4</filename> (%5, %6) and grow it to %7",
0189                       sourcePartition().deviceNode(),
0190                       Capacity::formatByteSize(sourcePartition().capacity()),
0191                       sourcePartition().fileSystem().name(),
0192                       overwrittenPartition()->deviceNode(),
0193                       Capacity::formatByteSize(overwrittenPartition()->capacity()),
0194                       overwrittenPartition()->fileSystem().name(),
0195                       Capacity::formatByteSize(copiedPartition().capacity())
0196                      );
0197     }
0198 
0199     if (copiedPartition().length() == sourcePartition().length())
0200         return xi18nc("@info:status", "Copy partition <filename>%1</filename> (%2, %3) to unallocated space (starting at %4) on <filename>%5</filename>",
0201                       sourcePartition().deviceNode(),
0202                       Capacity::formatByteSize(sourcePartition().capacity()),
0203                       sourcePartition().fileSystem().name(),
0204                       Capacity::formatByteSize(copiedPartition().firstSector() * targetDevice().logicalSize()),
0205                       targetDevice().deviceNode()
0206                      );
0207 
0208     return xi18nc("@info:status", "Copy partition <filename>%1</filename> (%2, %3) to unallocated space (starting at %4) on <filename>%5</filename> and grow it to %6",
0209                   sourcePartition().deviceNode(),
0210                   Capacity::formatByteSize(sourcePartition().capacity()),
0211                   sourcePartition().fileSystem().name(),
0212                   Capacity::formatByteSize(copiedPartition().firstSector() * targetDevice().logicalSize()),
0213                   targetDevice().deviceNode(),
0214                   Capacity::formatByteSize(copiedPartition().capacity())
0215                  );
0216 }
0217 
0218 void CopyOperation::setOverwrittenPartition(Partition* p)
0219 {
0220     // this code is also in RestoreOperation.
0221 
0222     cleanupOverwrittenPartition();
0223     m_OverwrittenPartition = p;
0224 
0225     // If the overwritten partition has no other operation that owns it (e.g., an OperationNew or
0226     // an OperationRestore), we're the new owner. So remember that, because after the operations all
0227     // have executed and we're asked to clean up after ourselves, the state of the overwritten partition
0228     // might have changed: If it was a new one and the NewOperation has successfully run, the state will
0229     // then be StateNone.
0230     m_MustDeleteOverwritten = (p && p->state() == Partition::State::None);
0231 }
0232 
0233 void CopyOperation::cleanupOverwrittenPartition()
0234 {
0235     if (mustDeleteOverwritten()) {
0236         delete overwrittenPartition();
0237         m_OverwrittenPartition = nullptr;
0238     }
0239 }
0240 
0241 /** Creates a new copied Partition.
0242     @param target the target Partition to copy to (may be unallocated)
0243     @param source the source Partition to copy
0244     @return pointer to the newly created Partition object
0245 */
0246 Partition* CopyOperation::createCopy(const Partition& target, const Partition& source)
0247 {
0248     Partition* p = target.roles().has(PartitionRole::Unallocated) ? new Partition(source) : new Partition(target);
0249 
0250     p->setDevicePath(source.devicePath());
0251     p->setPartitionPath(source.partitionPath());
0252     p->setState(Partition::State::Copy);
0253 
0254     p->deleteFileSystem();
0255     p->setFileSystem(FileSystemFactory::create(source.fileSystem()));
0256 
0257     p->fileSystem().setFirstSector(p->firstSector());
0258     p->fileSystem().setLastSector(p->lastSector());
0259 
0260     p->setFlags(PartitionTable::Flag::None);
0261 
0262     return p;
0263 }
0264 
0265 /** Can a Partition be copied?
0266     @param p the Partition in question, may be nullptr.
0267     @return true if @p p can be copied.
0268  */
0269 bool CopyOperation::canCopy(const Partition* p)
0270 {
0271     if (p == nullptr)
0272         return false;
0273 
0274     if (p->state() == Partition::State::New && p->roles().has(PartitionRole::Luks))
0275         return false;
0276 
0277     if (p->isMounted())
0278         return false;
0279 
0280     // FIXME: Does not work well enough yet
0281     if (p->roles().has(PartitionRole::Lvm_Lv))
0282         return false;
0283 
0284     // Normally, copying partitions that have not been written to disk yet should
0285     // be forbidden here. The operation stack, however, will take care of these
0286     // problematic cases when pushing the CopyOperation onto the stack.
0287 
0288     return p->fileSystem().supportCopy() != FileSystem::cmdSupportNone;
0289 }
0290 
0291 /** Can a Partition be pasted on another one?
0292     @param p the Partition to be pasted to, may be nullptr
0293     @param source the Partition to be pasted, may be nullptr
0294     @return true if @p source can be pasted on @p p
0295  */
0296 bool CopyOperation::canPaste(const Partition* p, const Partition* source)
0297 {
0298     if (p == nullptr || source == nullptr)
0299         return false;
0300 
0301     if (p->isMounted())
0302         return false;
0303 
0304     if (p->roles().has(PartitionRole::Extended))
0305         return false;
0306 
0307     if (p->roles().has(PartitionRole::Lvm_Lv))
0308         return false;
0309 
0310     if (p == source)
0311         return false;
0312 
0313     if (source->length() > p->length())
0314         return false;
0315 
0316     if (!p->roles().has(PartitionRole::Unallocated) && p->capacity() > source->fileSystem().maxCapacity())
0317         return false;
0318 
0319     return true;
0320 }