Warning, file /system/kpmcore/src/core/operationstack.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002     SPDX-FileCopyrightText: 2008-2010 Volker Lanz <vl@fidra.de>
0003     SPDX-FileCopyrightText: 2008 Laurent Montel <montel@kde.org>
0004     SPDX-FileCopyrightText: 2014-2020 Andrius Štikonas <andrius@stikonas.eu>
0005     SPDX-FileCopyrightText: 2015 Teo Mrnjavac <teo@kde.org>
0006 
0007     SPDX-License-Identifier: GPL-3.0-or-later
0008 */
0009 
0010 #include "core/operationstack.h"
0011 #include "core/device.h"
0012 #include "core/partition.h"
0013 #include "core/partitiontable.h"
0014 
0015 #include "ops/operation.h"
0016 #include "ops/deleteoperation.h"
0017 #include "ops/newoperation.h"
0018 #include "ops/resizeoperation.h"
0019 #include "ops/copyoperation.h"
0020 #include "ops/restoreoperation.h"
0021 #include "ops/createfilesystemoperation.h"
0022 #include "ops/setpartflagsoperation.h"
0023 #include "ops/setfilesystemlabeloperation.h"
0024 #include "ops/createpartitiontableoperation.h"
0025 #include "ops/resizevolumegroupoperation.h"
0026 #include "ops/checkoperation.h"
0027 
0028 #include "jobs/setfilesystemlabeljob.h"
0029 
0030 #include "fs/filesystemfactory.h"
0031 
0032 #include "util/globallog.h"
0033 
0034 #include <KLocalizedString>
0035 
0036 #include <QReadLocker>
0037 #include <QWriteLocker>
0038 
0039 /** Constructs a new OperationStack */
0040 OperationStack::OperationStack(QObject* parent) :
0041     QObject(parent),
0042     m_Operations(),
0043     m_PreviewDevices(),
0044     m_Lock(QReadWriteLock::Recursive)
0045 {
0046 }
0047 
0048 /** Destructs an OperationStack, cleaning up Operations and Devices */
0049 OperationStack::~OperationStack()
0050 {
0051     clearOperations();
0052     clearDevices();
0053 }
0054 
0055 /** Tries to merge an existing NewOperation with a new Operation pushed on the OperationStack
0056 
0057     There are several cases what might need to be done:
0058 
0059     <ol>
0060     <!-- 1 -->
0061     <li>An existing operation created a Partition that is now being deleted: In this case, just remove
0062         the corresponding NewOperation from the OperationStack.<br/>This does not work for
0063         extended partitions.(#232092)</li>
0064     <!-- 2 -->
0065     <li>An existing Operation created a Partition that is now being moved or resized. In this case,
0066         remove the original NewOperation and create a new NewOperation with updated start and end
0067         sectors. This new NewOperation is appended to the OperationStack.<br/>This does not work for
0068         extended partitions.(#232092)</li>
0069     <!-- 3 -->
0070     <li>An existing NewOperation created a Partition that is now being copied. We're not copying
0071         but instead creating another new Partition in its place.</li>
0072     <!-- 4 -->
0073     <li>The label for a new Partition's FileSystem is modified: Modify in NewOperation and forget it.</li>
0074     <!-- 5 -->
0075     <li>File system is changed for a new Partition: Modify in NewOperation and forget it.</li>
0076     <!-- 6 -->
0077     <li>A file system on a new Partition is about to be checked: Just delete the CheckOperation, because
0078         file systems are checked anyway when they're created. This fixes #275657.</li>
0079     </ol>
0080 
0081     @param currentOp the Operation already on the stack to try to merge with
0082     @param pushedOp the newly pushed Operation
0083     @return true if the OperationStack has been modified in a way that requires merging to stop
0084 */
0085 bool OperationStack::mergeNewOperation(Operation*& currentOp, Operation*& pushedOp)
0086 {
0087     NewOperation* newOp = dynamic_cast<NewOperation*>(currentOp);
0088 
0089     if (newOp == nullptr)
0090         return false;
0091 
0092     DeleteOperation* pushedDeleteOp = dynamic_cast<DeleteOperation*>(pushedOp);
0093     ResizeOperation* pushedResizeOp = dynamic_cast<ResizeOperation*>(pushedOp);
0094     CopyOperation* pushedCopyOp = dynamic_cast<CopyOperation*>(pushedOp);
0095     SetFileSystemLabelOperation* pushedLabelOp = dynamic_cast<SetFileSystemLabelOperation*>(pushedOp);
0096     CreateFileSystemOperation* pushedCreateFileSystemOp = dynamic_cast<CreateFileSystemOperation*>(pushedOp);
0097     CheckOperation* pushedCheckOp = dynamic_cast<CheckOperation*>(pushedOp);
0098 
0099     // -- 1 --
0100     if (pushedDeleteOp && &newOp->newPartition() == &pushedDeleteOp->deletedPartition() && !pushedDeleteOp->deletedPartition().roles().has(PartitionRole::Extended)) {
0101         Log() << xi18nc("@info:status", "Deleting a partition just created: Undoing the operation to create the partition.");
0102 
0103         delete pushedOp;
0104         pushedOp = nullptr;
0105 
0106         newOp->undo();
0107         delete operations().takeAt(operations().indexOf(newOp));
0108 
0109         return true;
0110     }
0111 
0112     // -- 2 --
0113     if (pushedResizeOp && &newOp->newPartition() == &pushedResizeOp->partition() && !pushedResizeOp->partition().roles().has(PartitionRole::Extended)) {
0114         // NOTE: In theory it would be possible to merge resizing an extended as long as it has no children.
0115         // But that still doesn't save us: If we're not merging a resize on an extended that has children,
0116         // a resizeop is added to the stack. Next, the user deletes the child. Then he resizes the
0117         // extended again (a second resize): The ResizeOp still has the pointer to the original extended that
0118         // will now be deleted.
0119         Log() << xi18nc("@info:status", "Resizing a partition just created: Updating start and end in existing operation.");
0120 
0121         Partition* newPartition = new Partition(newOp->newPartition());
0122         newPartition->setFirstSector(pushedResizeOp->newFirstSector());
0123         newPartition->fileSystem().setFirstSector(pushedResizeOp->newFirstSector());
0124         newPartition->setLastSector(pushedResizeOp->newLastSector());
0125         newPartition->fileSystem().setLastSector(pushedResizeOp->newLastSector());
0126 
0127         NewOperation* revisedNewOp = new NewOperation(newOp->targetDevice(), newPartition);
0128         delete pushedOp;
0129         pushedOp = revisedNewOp;
0130 
0131         newOp->undo();
0132         delete operations().takeAt(operations().indexOf(newOp));
0133 
0134         return true;
0135     }
0136 
0137     // -- 3 --
0138     if (pushedCopyOp && &newOp->newPartition() == &pushedCopyOp->sourcePartition()) {
0139         Log() << xi18nc("@info:status", "Copying a new partition: Creating a new partition instead.");
0140 
0141         Partition* newPartition = new Partition(newOp->newPartition());
0142         newPartition->setFirstSector(pushedCopyOp->copiedPartition().firstSector());
0143         newPartition->fileSystem().setFirstSector(pushedCopyOp->copiedPartition().fileSystem().firstSector());
0144         newPartition->setLastSector(pushedCopyOp->copiedPartition().lastSector());
0145         newPartition->fileSystem().setLastSector(pushedCopyOp->copiedPartition().fileSystem().lastSector());
0146 
0147         NewOperation* revisedNewOp = new NewOperation(pushedCopyOp->targetDevice(), newPartition);
0148         delete pushedOp;
0149         pushedOp = revisedNewOp;
0150 
0151         return true;
0152     }
0153 
0154     // -- 4 --
0155     if (pushedLabelOp && &newOp->newPartition() == &pushedLabelOp->labeledPartition()) {
0156         Log() << xi18nc("@info:status", "Changing label for a new partition: No new operation required.");
0157 
0158         newOp->setLabelJob()->setLabel(pushedLabelOp->newLabel());
0159         newOp->newPartition().fileSystem().setLabel(pushedLabelOp->newLabel());
0160 
0161         delete pushedOp;
0162         pushedOp = nullptr;
0163 
0164         return true;
0165     }
0166 
0167     // -- 5 --
0168     if (pushedCreateFileSystemOp && &newOp->newPartition() == &pushedCreateFileSystemOp->partition()) {
0169         Log() << xi18nc("@info:status", "Changing file system for a new partition: No new operation required.");
0170 
0171         FileSystem* oldFs = &newOp->newPartition().fileSystem();
0172 
0173         newOp->newPartition().setFileSystem(FileSystemFactory::cloneWithNewType(pushedCreateFileSystemOp->newFileSystem()->type(), *oldFs));
0174 
0175         delete oldFs;
0176         oldFs = nullptr;
0177 
0178         delete pushedOp;
0179         pushedOp = nullptr;
0180 
0181         return true;
0182     }
0183 
0184     // -- 6 --
0185     if (pushedCheckOp && &newOp->newPartition() == &pushedCheckOp->checkedPartition()) {
0186         Log() << xi18nc("@info:status", "Checking file systems is automatically done when creating them: No new operation required.");
0187 
0188         delete pushedOp;
0189         pushedOp = nullptr;
0190 
0191         return true;
0192     }
0193 
0194     return false;
0195 }
0196 
0197 /** Tries to merge an existing CopyOperation with a new Operation pushed on the OperationStack.
0198 
0199     These are the cases to consider:
0200 
0201     <ol>
0202     <!-- 1 -->
0203     <li>An existing CopyOperation created a Partition that is now being deleted. Remove the
0204         CopyOperation, and, if the CopyOperation was an overwrite, carry on with the delete. Else
0205         also remove the DeleteOperation.</li>
0206 
0207     <!-- 2 -->
0208     <li>An existing CopyOperation created a Partition that is now being copied. We're not copying
0209         the target of this existing CopyOperation, but its source instead. If this merge is not done,
0210         copied partitions will have misleading labels ("copy of sdXY" instead of "copy of copy of
0211         sdXY" for a second-generation copy) but the Operation itself will still work.</li>
0212     </ol>
0213 
0214     @param currentOp the Operation already on the stack to try to merge with
0215     @param pushedOp the newly pushed Operation
0216     @return true if the OperationStack has been modified in a way that requires merging to stop
0217 */
0218 bool OperationStack::mergeCopyOperation(Operation*& currentOp, Operation*& pushedOp)
0219 {
0220     CopyOperation* copyOp = dynamic_cast<CopyOperation*>(currentOp);
0221 
0222     if (copyOp == nullptr)
0223         return false;
0224 
0225     DeleteOperation* pushedDeleteOp = dynamic_cast<DeleteOperation*>(pushedOp);
0226     CopyOperation* pushedCopyOp = dynamic_cast<CopyOperation*>(pushedOp);
0227 
0228     // -- 1 --
0229     if (pushedDeleteOp && &copyOp->copiedPartition() == &pushedDeleteOp->deletedPartition()) {
0230         // If the copy operation didn't overwrite, but create a new partition, just remove the
0231         // copy operation, forget the delete and be done.
0232         if (copyOp->overwrittenPartition() == nullptr) {
0233             Log() << xi18nc("@info:status", "Deleting a partition just copied: Removing the copy.");
0234 
0235             delete pushedOp;
0236             pushedOp = nullptr;
0237         } else {
0238             Log() << xi18nc("@info:status", "Deleting a partition just copied over an existing partition: Removing the copy and deleting the existing partition.");
0239 
0240             pushedDeleteOp->setDeletedPartition(copyOp->overwrittenPartition());
0241         }
0242 
0243         copyOp->undo();
0244         delete operations().takeAt(operations().indexOf(copyOp));
0245 
0246         return true;
0247     }
0248 
0249     // -- 2 --
0250     if (pushedCopyOp && &copyOp->copiedPartition() == &pushedCopyOp->sourcePartition()) {
0251         Log() << xi18nc("@info:status", "Copying a partition that is itself a copy: Copying the original source partition instead.");
0252 
0253         pushedCopyOp->setSourcePartition(&copyOp->sourcePartition());
0254     }
0255 
0256     return false;
0257 }
0258 
0259 /** Tries to merge an existing RestoreOperation with a new Operation pushed on the OperationStack.
0260 
0261     If an existing RestoreOperation created a Partition that is now being deleted, remove the
0262     RestoreOperation, and, if the RestoreOperation was an overwrite, carry on with the delete. Else
0263     also remove the DeleteOperation.
0264 
0265     @param currentOp the Operation already on the stack to try to merge with
0266     @param pushedOp the newly pushed Operation
0267     @return true if the OperationStack has been modified in a way that requires merging to stop
0268 */
0269 bool OperationStack::mergeRestoreOperation(Operation*& currentOp, Operation*& pushedOp)
0270 {
0271     RestoreOperation* restoreOp = dynamic_cast<RestoreOperation*>(currentOp);
0272 
0273     if (restoreOp == nullptr)
0274         return false;
0275 
0276     DeleteOperation* pushedDeleteOp = dynamic_cast<DeleteOperation*>(pushedOp);
0277 
0278     if (pushedDeleteOp && &restoreOp->restorePartition() == &pushedDeleteOp->deletedPartition()) {
0279         if (restoreOp->overwrittenPartition() == nullptr) {
0280             Log() << xi18nc("@info:status", "Deleting a partition just restored: Removing the restore operation.");
0281 
0282             delete pushedOp;
0283             pushedOp = nullptr;
0284         } else {
0285             Log() << xi18nc("@info:status", "Deleting a partition just restored to an existing partition: Removing the restore operation and deleting the existing partition.");
0286 
0287             pushedDeleteOp->setDeletedPartition(restoreOp->overwrittenPartition());
0288         }
0289 
0290         restoreOp->undo();
0291         delete operations().takeAt(operations().indexOf(restoreOp));
0292 
0293         return true;
0294     }
0295 
0296     return false;
0297 }
0298 
0299 /** Tries to merge an existing SetPartFlagsOperation with a new Operation pushed on the OperationStack.
0300 
0301     If the Partition flags for an existing Partition are modified look if there is an existing
0302     Operation for the same Partition and modify that one.
0303 
0304     @param currentOp the Operation already on the stack to try to merge with
0305     @param pushedOp the newly pushed Operation
0306     @return true if the OperationStack has been modified in a way that requires merging to stop
0307 */
0308 bool OperationStack::mergePartFlagsOperation(Operation*& currentOp, Operation*& pushedOp)
0309 {
0310     SetPartFlagsOperation* partFlagsOp = dynamic_cast<SetPartFlagsOperation*>(currentOp);
0311 
0312     if (partFlagsOp == nullptr)
0313         return false;
0314 
0315     SetPartFlagsOperation* pushedFlagsOp = dynamic_cast<SetPartFlagsOperation*>(pushedOp);
0316 
0317     if (pushedFlagsOp && &partFlagsOp->flagPartition() == &pushedFlagsOp->flagPartition()) {
0318         Log() << xi18nc("@info:status", "Changing flags again for the same partition: Removing old operation.");
0319 
0320         pushedFlagsOp->setOldFlags(partFlagsOp->oldFlags());
0321         partFlagsOp->undo();
0322         delete operations().takeAt(operations().indexOf(partFlagsOp));
0323 
0324         return true;
0325     }
0326 
0327     return false;
0328 }
0329 
0330 /** Tries to merge an existing SetFileSystemLabelOperation with a new Operation pushed on the OperationStack.
0331 
0332     If a FileSystem label for an existing Partition is modified look if there is an existing
0333     SetFileSystemLabelOperation for the same Partition.
0334 
0335     @param currentOp the Operation already on the stack to try to merge with
0336     @param pushedOp the newly pushed Operation
0337     @return true if the OperationStack has been modified in a way that requires merging to stop
0338 */
0339 bool OperationStack::mergePartLabelOperation(Operation*& currentOp, Operation*& pushedOp)
0340 {
0341     SetFileSystemLabelOperation* partLabelOp = dynamic_cast<SetFileSystemLabelOperation*>(currentOp);
0342 
0343     if (partLabelOp == nullptr)
0344         return false;
0345 
0346     SetFileSystemLabelOperation* pushedLabelOp = dynamic_cast<SetFileSystemLabelOperation*>(pushedOp);
0347 
0348     if (pushedLabelOp && &partLabelOp->labeledPartition() == &pushedLabelOp->labeledPartition()) {
0349         Log() << xi18nc("@info:status", "Changing label again for the same partition: Removing old operation.");
0350 
0351         pushedLabelOp->setOldLabel(partLabelOp->oldLabel());
0352         partLabelOp->undo();
0353         delete operations().takeAt(operations().indexOf(partLabelOp));
0354 
0355         return true;
0356     }
0357 
0358     return false;
0359 }
0360 
0361 /** Tries to merge an existing CreatePartitionTableOperation with a new Operation pushed on the OperationStack.
0362 
0363     If a new partition table is to be created on a device and a previous operation targets that
0364     device, remove this previous operation.
0365 
0366     @param currentOp the Operation already on the stack to try to merge with
0367     @param pushedOp the newly pushed Operation
0368     @return true if the OperationStack has been modified in a way that requires merging to stop
0369 */
0370 bool OperationStack::mergeCreatePartitionTableOperation(Operation*& currentOp, Operation*& pushedOp)
0371 {
0372     CreatePartitionTableOperation* pushedCreatePartitionTableOp = dynamic_cast<CreatePartitionTableOperation*>(pushedOp);
0373 
0374     if (pushedCreatePartitionTableOp && currentOp->targets(pushedCreatePartitionTableOp->targetDevice())) {
0375         Log() << xi18nc("@info:status", "Creating new partition table, discarding previous operation on device.");
0376 
0377         CreatePartitionTableOperation* createPartitionTableOp = dynamic_cast<CreatePartitionTableOperation*>(currentOp);
0378         if (createPartitionTableOp != nullptr)
0379             pushedCreatePartitionTableOp->setOldPartitionTable(createPartitionTableOp->oldPartitionTable());
0380 
0381         currentOp->undo();
0382         delete operations().takeAt(operations().indexOf(currentOp));
0383 
0384         return true;
0385     }
0386 
0387     return false;
0388 }
0389 
0390 bool OperationStack::mergeResizeVolumeGroupResizeOperation(Operation*& pushedOp)
0391 {
0392     ResizeVolumeGroupOperation* pushedResizeVolumeGroupOp = dynamic_cast<ResizeVolumeGroupOperation*>(pushedOp);
0393 
0394     if (pushedResizeVolumeGroupOp && pushedResizeVolumeGroupOp->jobs().count() == 0) {
0395         Log() << xi18nc("@info:status", "Resizing Volume Group, nothing to do.");
0396 
0397         return true;
0398     }
0399 
0400     return false;
0401 }
0402 
0403 /** Pushes a new Operation on the OperationStack.
0404 
0405     This method will call all methods that try to merge the new Operation with the
0406     existing ones. It is not uncommon that any of these will delete the pushed
0407     Operation. Callers <b>must not rely</b> on the pushed Operation to exist after
0408     calling OperationStack::push().
0409 
0410     @param o Pointer to the Operation. Must not be nullptr.
0411 */
0412 void OperationStack::push(Operation* o)
0413 {
0414     Q_ASSERT(o);
0415 
0416     if (mergeResizeVolumeGroupResizeOperation(o))
0417         return;
0418 
0419     for (auto currentOp = operations().rbegin(); currentOp != operations().rend(); ++currentOp) {
0420         if (mergeNewOperation(*currentOp, o))
0421             break;
0422 
0423         if (mergeCopyOperation(*currentOp, o))
0424             break;
0425 
0426         if (mergeRestoreOperation(*currentOp, o))
0427             break;
0428 
0429         if (mergePartFlagsOperation(*currentOp, o))
0430             break;
0431 
0432         if (mergePartLabelOperation(*currentOp, o))
0433             break;
0434 
0435         if (mergeCreatePartitionTableOperation(*currentOp, o))
0436             break;
0437     }
0438 
0439     if (o != nullptr) {
0440         Log() << xi18nc("@info:status", "Add operation: %1", o->description());
0441         operations().append(o);
0442         o->preview();
0443         o->setStatus(Operation::StatusPending);
0444     }
0445 
0446     // Q_EMIT operationsChanged even if o is nullptr because it has been merged: merging might
0447     // have led to an existing operation changing.
0448     Q_EMIT operationsChanged();
0449 }
0450 
0451 /** Removes the topmost Operation from the OperationStack, calls Operation::undo() on it and deletes it. */
0452 void OperationStack::pop()
0453 {
0454     Operation* o = operations().takeLast();
0455     o->undo();
0456     delete o;
0457     Q_EMIT operationsChanged();
0458 }
0459 
0460 /** Check whether previous operations involve given partition.
0461 
0462     @param p Pointer to the Partition. Must not be nullptr.
0463 */
0464 bool OperationStack::contains(const Partition* p) const
0465 {
0466     Q_ASSERT(p);
0467 
0468     for (const auto &o : operations()) {
0469         if (o->targets(*p))
0470             return true;
0471 
0472         CopyOperation* copyOp = dynamic_cast<CopyOperation*>(o);
0473         if (copyOp) {
0474             const Partition* source = &copyOp->sourcePartition();
0475             if (source == p)
0476                 return true;
0477         }
0478     }
0479 
0480     return false;
0481 }
0482 
0483 /** Removes all Operations from the OperationStack, calling Operation::undo() on them and deleting them. */
0484 void OperationStack::clearOperations()
0485 {
0486     while (!operations().isEmpty()) {
0487         Operation* o = operations().takeLast();
0488         if (o->status() == Operation::StatusPending)
0489             o->undo();
0490         delete o;
0491     }
0492 
0493     Q_EMIT operationsChanged();
0494 }
0495 
0496 /** Clears the list of Devices. */
0497 void OperationStack::clearDevices()
0498 {
0499     QWriteLocker lockDevices(&lock());
0500 
0501     qDeleteAll(previewDevices());
0502     previewDevices().clear();
0503     Q_EMIT devicesChanged();
0504 }
0505 
0506 /** Finds a Device a Partition is on.
0507     @param p pointer to the Partition to find a Device for
0508     @return the Device or nullptr if none could be found
0509 */
0510 Device* OperationStack::findDeviceForPartition(const Partition* p)
0511 {
0512     QReadLocker lockDevices(&lock());
0513 
0514     const auto devices = previewDevices();
0515     for (Device *d : devices) {
0516         if (d->partitionTable() == nullptr)
0517             continue;
0518 
0519         const auto partitions = d->partitionTable()->children();
0520         for (const auto *part : partitions) {
0521             if (part == p)
0522                 return d;
0523 
0524             for (const auto &child : part->children())
0525                 if (child == p)
0526                     return d;
0527         }
0528     }
0529 
0530     return nullptr;
0531 }
0532 
0533 /** Adds a Device to the OperationStack
0534     @param d pointer to the Device to add. Must not be nullptr.
0535 */
0536 void OperationStack::addDevice(Device* d)
0537 {
0538     Q_ASSERT(d);
0539 
0540     QWriteLocker lockDevices(&lock());
0541 
0542     previewDevices().append(d);
0543     Q_EMIT devicesChanged();
0544 }
0545 
0546 static bool deviceLessThan(const Device* d1, const Device* d2)
0547 {
0548     // Display alphabetically sorted disk devices above LVM VGs
0549     if (d1->type() == Device::Type::LVM_Device && d2->type() == Device::Type::Disk_Device )
0550         return false;
0551 
0552     return d1->deviceNode() <= d2->deviceNode();
0553 }
0554 
0555 void OperationStack::sortDevices()
0556 {
0557     QWriteLocker lockDevices(&lock());
0558 
0559     std::sort(previewDevices().begin(), previewDevices().end(), deviceLessThan);
0560 
0561     Q_EMIT devicesChanged();
0562 }
0563 
0564 #include "moc_operationstack.cpp"