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 && ©Op->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 && ©Op->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(©Op->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 = ©Op->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"