diff --git a/src/ops/deleteoperation.cpp b/src/ops/deleteoperation.cpp index 768051c..f0168e4 100644 --- a/src/ops/deleteoperation.cpp +++ b/src/ops/deleteoperation.cpp @@ -1,159 +1,161 @@ /************************************************************************* * Copyright (C) 2008, 2010 by Volker Lanz * * Copyright (C) 2016 by Andrius Štikonas * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 3 of * * the License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see .* *************************************************************************/ #include "ops/createvolumegroupoperation.h" #include "ops/deleteoperation.h" #include "core/partition.h" #include "core/device.h" #include "core/partitiontable.h" #include "fs/luks.h" #include "jobs/deletepartitionjob.h" #include "jobs/deletefilesystemjob.h" #include "jobs/shredfilesystemjob.h" #include "util/capacity.h" #include #include /** Creates a new DeleteOperation @param d the Device to delete a Partition on @param p pointer to the Partition to delete. May not be nullptr */ DeleteOperation::DeleteOperation(Device& d, Partition* p, ShredAction shred) : Operation(), m_TargetDevice(d), m_DeletedPartition(p), m_ShredAction(shred), m_DeletePartitionJob(new DeletePartitionJob(targetDevice(), deletedPartition())) { switch (shredAction()) { case ShredAction::NoShred: m_DeleteFileSystemJob = static_cast(new DeleteFileSystemJob(targetDevice(), deletedPartition())); break; case ShredAction::ZeroShred: m_DeleteFileSystemJob = static_cast(new ShredFileSystemJob(targetDevice(), deletedPartition(), false)); break; case ShredAction::RandomShred: m_DeleteFileSystemJob = static_cast(new ShredFileSystemJob(targetDevice(), deletedPartition(), true)); } addJob(deleteFileSystemJob()); addJob(deletePartitionJob()); } DeleteOperation::~DeleteOperation() { if (status() != StatusPending && status() != StatusNone) // don't delete the partition if we're being merged or undone delete m_DeletedPartition; } bool DeleteOperation::targets(const Device& d) const { return d == targetDevice(); } bool DeleteOperation::targets(const Partition& p) const { return p == deletedPartition(); } void DeleteOperation::preview() { removePreviewPartition(targetDevice(), deletedPartition()); checkAdjustLogicalNumbers(deletedPartition(), false); } void DeleteOperation::undo() { checkAdjustLogicalNumbers(deletedPartition(), true); insertPreviewPartition(targetDevice(), deletedPartition()); } QString DeleteOperation::description() const { if (shredAction() != ShredAction::NoShred) return xi18nc("@info:status", "Shred partition %1 (%2, %3)", deletedPartition().deviceNode(), Capacity::formatByteSize(deletedPartition().capacity()), deletedPartition().fileSystem().name()); else return xi18nc("@info:status", "Delete partition %1 (%2, %3)", deletedPartition().deviceNode(), Capacity::formatByteSize(deletedPartition().capacity()), deletedPartition().fileSystem().name()); } void DeleteOperation::checkAdjustLogicalNumbers(Partition& p, bool undo) { // If the deleted partition is a logical one, we need to adjust the numbers of the // other logical partitions in the extended one, if there are any, because the OS // will do that, too: Logicals must be numbered without gaps, i.e., a numbering like // sda5, sda6, sda8 (after sda7 is deleted) will become sda5, sda6, sda7 Partition* parentPartition = dynamic_cast(p.parent()); if (parentPartition && parentPartition->roles().has(PartitionRole::Extended)) parentPartition->adjustLogicalNumbers(undo ? -1 : p.number(), undo ? p.number() : -1); } /** Can a Partition be deleted? @param p the Partition in question, may be nullptr. @return true if @p p can be deleted. */ bool DeleteOperation::canDelete(const Partition* p, const QList pendingOps) { if (p == nullptr) return false; if (p->isMounted()) return false; if (p->fileSystem().type() == FileSystem::Type::Lvm2_PV) { // See if there is a newly created VG targeting this partition for (Operation *op : qAsConst(pendingOps)) { if (dynamic_cast(op) && op->targets(*p)) return false; } } else if (p->fileSystem().type() == FileSystem::Type::Luks || p->fileSystem().type() == FileSystem::Type::Luks2) { // See if innerFS is LVM - FileSystem *fs = static_cast(&p->fileSystem())->innerFS(); - - if (fs->type() == FileSystem::Type::Lvm2_PV) { - // See if there is a newly created VG targeting this partition - for (Operation *op : qAsConst(pendingOps)) { - if (dynamic_cast(op) && op->targets(*p)) - return false; + FileSystem *fs = dynamic_cast(&p->fileSystem())->innerFS(); + + if (fs) { + if (fs->type() == FileSystem::Type::Lvm2_PV) { + // See if there is a newly created VG targeting this partition + for (Operation *op : qAsConst(pendingOps)) { + if (dynamic_cast(op) && op->targets(*p)) + return false; + } } } } if (p->roles().has(PartitionRole::Unallocated)) return false; if (p->roles().has(PartitionRole::Extended)) return p->children().size() == 1 && p->children()[0]->roles().has(PartitionRole::Unallocated); if (p->roles().has(PartitionRole::Luks)) { const FS::luks* luksFs = dynamic_cast(&p->fileSystem()); if (!luksFs) return false; if (luksFs->isCryptOpen() || luksFs->isMounted()) return false; } return true; } diff --git a/src/ops/resizeoperation.cpp b/src/ops/resizeoperation.cpp index 57b7361..8c44f7b 100644 --- a/src/ops/resizeoperation.cpp +++ b/src/ops/resizeoperation.cpp @@ -1,431 +1,435 @@ /************************************************************************* * Copyright (C) 2008, 2012 by Volker Lanz * * Copyright (C) 2016 by Andrius Štikonas * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 3 of * * the License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see .* *************************************************************************/ #include "ops/resizeoperation.h" #include "core/partition.h" #include "core/device.h" #include "core/partitiontable.h" #include "core/copysourcedevice.h" #include "core/copytargetdevice.h" #include "jobs/checkfilesystemjob.h" #include "jobs/setpartgeometryjob.h" #include "jobs/resizefilesystemjob.h" #include "jobs/movefilesystemjob.h" #include "ops/checkoperation.h" #include "ops/createvolumegroupoperation.h" #include "fs/filesystem.h" #include "fs/luks.h" #include "util/capacity.h" #include "util/report.h" #include #include #include /** Creates a new ResizeOperation. @param d the Device to resize a Partition on @param p the Partition to resize @param newfirst the new first sector of the Partition @param newlast the new last sector of the Partition */ ResizeOperation::ResizeOperation(Device& d, Partition& p, qint64 newfirst, qint64 newlast) : Operation(), m_TargetDevice(d), m_Partition(p), m_OrigFirstSector(partition().firstSector()), m_OrigLastSector(partition().lastSector()), m_NewFirstSector(newfirst), m_NewLastSector(newlast), m_CheckOriginalJob(new CheckFileSystemJob(partition())), m_MoveExtendedJob(nullptr), m_ShrinkResizeJob(nullptr), m_ShrinkSetGeomJob(nullptr), m_MoveSetGeomJob(nullptr), m_MoveFileSystemJob(nullptr), m_GrowResizeJob(nullptr), m_GrowSetGeomJob(nullptr), m_CheckResizedJob(nullptr) { if(CheckOperation::canCheck(&partition())) addJob(checkOriginalJob()); if (partition().roles().has(PartitionRole::Extended)) { m_MoveExtendedJob = new SetPartGeometryJob(targetDevice(), partition(), newFirstSector(), newLength()); addJob(moveExtendedJob()); } else { if (resizeAction() & Shrink) { m_ShrinkResizeJob = new ResizeFileSystemJob(targetDevice(), partition(), newLength()); m_ShrinkSetGeomJob = new SetPartGeometryJob(targetDevice(), partition(), partition().firstSector(), newLength()); addJob(shrinkResizeJob()); addJob(shrinkSetGeomJob()); } if ((resizeAction() & MoveLeft) || (resizeAction() & MoveRight)) { // At this point, we need to set the partition's length to either the resized length, if it has already been // shrunk, or to the original length (it may or may not then later be grown, we don't care here) const qint64 currentLength = (resizeAction() & Shrink) ? newLength() : partition().length(); m_MoveSetGeomJob = new SetPartGeometryJob(targetDevice(), partition(), newFirstSector(), currentLength); m_MoveFileSystemJob = new MoveFileSystemJob(targetDevice(), partition(), newFirstSector()); addJob(moveSetGeomJob()); addJob(moveFileSystemJob()); } if (resizeAction() & Grow) { m_GrowSetGeomJob = new SetPartGeometryJob(targetDevice(), partition(), newFirstSector(), newLength()); m_GrowResizeJob = new ResizeFileSystemJob(targetDevice(), partition(), newLength()); addJob(growSetGeomJob()); addJob(growResizeJob()); } m_CheckResizedJob = new CheckFileSystemJob(partition()); if(CheckOperation::canCheck(&partition())) addJob(checkResizedJob()); } } bool ResizeOperation::targets(const Device& d) const { return d == targetDevice(); } bool ResizeOperation::targets(const Partition& p) const { return p == partition(); } void ResizeOperation::preview() { // If the operation has already been executed, the partition will of course have newFirstSector and // newLastSector as first and last sector. But to remove it from its original position, we need to // temporarily set these values back to where they were before the operation was executed. if (partition().firstSector() == newFirstSector() && partition().lastSector() == newLastSector()) { partition().setFirstSector(origFirstSector()); partition().setLastSector(origLastSector()); } removePreviewPartition(targetDevice(), partition()); partition().setFirstSector(newFirstSector()); partition().setLastSector(newLastSector()); insertPreviewPartition(targetDevice(), partition()); } void ResizeOperation::undo() { removePreviewPartition(targetDevice(), partition()); partition().setFirstSector(origFirstSector()); partition().setLastSector(origLastSector()); insertPreviewPartition(targetDevice(), partition()); } bool ResizeOperation::execute(Report& parent) { bool rval = true; Report* report = parent.newChild(description()); if (CheckOperation::canCheck(&partition())) rval = checkOriginalJob()->run(*report); if (rval) { // Extended partitions are a special case: They don't have any file systems and so there's no // need to move, shrink or grow their contents before setting the new geometry. In fact, trying // to first shrink THEN move would not work for an extended partition that has children, because // they might temporarily be outside the extended partition and the backend would not let us do that. if (moveExtendedJob()) { if (!(rval = moveExtendedJob()->run(*report))) report->line() << xi18nc("@info:status", "Moving extended partition %1 failed.", partition().deviceNode()); } else { // We run all three methods. Any of them returns true if it has nothing to do. rval = shrink(*report) && move(*report) && grow(*report); if (rval) { if (CheckOperation::canCheck(&partition())) { rval = checkResizedJob()->run(*report); if (!rval) report->line() << xi18nc("@info:status", "Checking partition %1 after resize/move failed.", partition().deviceNode()); } } else report->line() << xi18nc("@info:status", "Resizing/moving partition %1 failed.", partition().deviceNode()); } } else report->line() << xi18nc("@info:status", "Checking partition %1 before resize/move failed.", partition().deviceNode()); setStatus(rval ? StatusFinishedSuccess : StatusError); report->setStatus(xi18nc("@info:status (success, error, warning...) of operation", "%1: %2", description(), statusText())); return rval; } QString ResizeOperation::description() const { // There are eight possible things a resize operation might do: // 1) Move a partition to the left (closer to the start of the disk) // 2) Move a partition to the right (closer to the end of the disk) // 3) Grow a partition // 4) Shrink a partition // 5) Move a partition to the left and grow it // 6) Move a partition to the right and grow it // 7) Move a partition to the left and shrink it // 8) Move a partition to the right and shrink it // Each of these needs a different description. And for reasons of i18n, we cannot // just concatenate strings together... const QString moveDelta = Capacity::formatByteSize(qAbs(newFirstSector() - origFirstSector()) * targetDevice().logicalSize()); const QString origCapacity = Capacity::formatByteSize(origLength() * targetDevice().logicalSize()); const QString newCapacity = Capacity::formatByteSize(newLength() * targetDevice().logicalSize()); switch (resizeAction()) { case MoveLeft: return xi18nc("@info:status describe resize/move action", "Move partition %1 to the left by %2", partition().deviceNode(), moveDelta); case MoveRight: return xi18nc("@info:status describe resize/move action", "Move partition %1 to the right by %2", partition().deviceNode(), moveDelta); case Grow: return xi18nc("@info:status describe resize/move action", "Grow partition %1 from %2 to %3", partition().deviceNode(), origCapacity, newCapacity); case Shrink: return xi18nc("@info:status describe resize/move action", "Shrink partition %1 from %2 to %3", partition().deviceNode(), origCapacity, newCapacity); case MoveLeftGrow: return xi18nc("@info:status describe resize/move action", "Move partition %1 to the left by %2 and grow it from %3 to %4", partition().deviceNode(), moveDelta, origCapacity, newCapacity); case MoveRightGrow: return xi18nc("@info:status describe resize/move action", "Move partition %1 to the right by %2 and grow it from %3 to %4", partition().deviceNode(), moveDelta, origCapacity, newCapacity); case MoveLeftShrink: return xi18nc("@info:status describe resize/move action", "Move partition %1 to the left by %2 and shrink it from %3 to %4", partition().deviceNode(), moveDelta, origCapacity, newCapacity); case MoveRightShrink: return xi18nc("@info:status describe resize/move action", "Move partition %1 to the right by %2 and shrink it from %3 to %4", partition().deviceNode(), moveDelta, origCapacity, newCapacity); case None: qWarning() << "Could not determine what to do with partition " << partition().deviceNode() << "."; break; } return xi18nc("@info:status describe resize/move action", "Unknown resize/move action."); } ResizeOperation::ResizeAction ResizeOperation::resizeAction() const { ResizeAction action = None; // Grow? if (newLength() > origLength()) action = Grow; // Shrink? if (newLength() < origLength()) action = Shrink; // Move to the right? if (newFirstSector() > origFirstSector()) action = static_cast(action | MoveRight); // Move to the left? if (newFirstSector() < origFirstSector()) action = static_cast(action | MoveLeft); return action; } bool ResizeOperation::shrink(Report& report) { if (shrinkResizeJob() && !shrinkResizeJob()->run(report)) { report.line() << xi18nc("@info:status", "Resize/move failed: Could not resize file system to shrink partition %1.", partition().deviceNode()); return false; } if (shrinkSetGeomJob() && !shrinkSetGeomJob()->run(report)) { report.line() << xi18nc("@info:status", "Resize/move failed: Could not shrink partition %1.", partition().deviceNode()); return false; /** @todo if this fails, no one undoes the shrinking of the file system above, because we rely upon there being a maximize job at the end, but that's no longer the case. */ } return true; } bool ResizeOperation::move(Report& report) { // We must make sure not to overwrite the partition's metadata if it's a logical partition // and we're moving to the left. The easiest way to achieve this is to move the // partition itself first (it's the backend's responsibility to then move the metadata) and // only afterwards copy the filesystem. Disadvantage: We need to move the partition // back to its original position if copyBlocks fails. const qint64 oldStart = partition().firstSector(); if (moveSetGeomJob() && !moveSetGeomJob()->run(report)) { report.line() << xi18nc("@info:status", "Moving partition %1 failed.", partition().deviceNode()); return false; } if (moveFileSystemJob() && !moveFileSystemJob()->run(report)) { report.line() << xi18nc("@info:status", "Moving the filesystem for partition %1 failed. Rolling back.", partition().deviceNode()); // see above: We now have to move back the partition itself. if (!SetPartGeometryJob(targetDevice(), partition(), oldStart, partition().length()).run(report)) report.line() << xi18nc("@info:status", "Moving back partition %1 to its original position failed.", partition().deviceNode()); return false; } return true; } bool ResizeOperation::grow(Report& report) { const qint64 oldLength = partition().length(); if (growSetGeomJob() && !growSetGeomJob()->run(report)) { report.line() << xi18nc("@info:status", "Resize/move failed: Could not grow partition %1.", partition().deviceNode()); return false; } if (growResizeJob() && !growResizeJob()->run(report)) { report.line() << xi18nc("@info:status", "Resize/move failed: Could not resize the file system on partition %1", partition().deviceNode()); if (!SetPartGeometryJob(targetDevice(), partition(), partition().firstSector(), oldLength).run(report)) report.line() << xi18nc("@info:status", "Could not restore old partition size for partition %1.", partition().deviceNode()); return false; } return true; } /** Can a Partition be grown, i.e. increased in size? @param p the Partition in question, may be nullptr. @return true if @p p can be grown. */ bool ResizeOperation::canGrow(const Partition* p) { if (p == nullptr) return false; // we can always grow, shrink or move a partition not yet written to disk if (p->state() == Partition::State::New && !p->roles().has(PartitionRole::Luks)) return true; if (p->isMounted()) return p->fileSystem().supportGrowOnline(); return p->fileSystem().supportGrow() != FileSystem::cmdSupportNone; } /** Can a Partition be shrunk, i.e. decreased in size? @param p the Partition in question, may be nullptr. @return true if @p p can be shrunk. */ bool ResizeOperation::canShrink(const Partition* p, const QList pendingOps) { if (p == nullptr) return false; if (p->fileSystem().type() == FileSystem::Type::Lvm2_PV) { // See if there is a newly created VG targeting this partition for (Operation *op : qAsConst(pendingOps)) { if (dynamic_cast(op) && op->targets(*p)) return false; } } else if (p->fileSystem().type() == FileSystem::Type::Luks || p->fileSystem().type() == FileSystem::Type::Luks2) { // See if innerFS is LVM - FileSystem *fs = static_cast(&p->fileSystem())->innerFS(); - - if (fs->type() == FileSystem::Type::Lvm2_PV) { - // See if there is a newly created VG targeting this partition - for (Operation *op : qAsConst(pendingOps)) { - if (dynamic_cast(op) && op->targets(*p)) - return false; + FileSystem *fs = dynamic_cast(&p->fileSystem())->innerFS(); + + if (fs) { + if (fs->type() == FileSystem::Type::Lvm2_PV) { + // See if there is a newly created VG targeting this partition + for (Operation *op : qAsConst(pendingOps)) { + if (dynamic_cast(op) && op->targets(*p)) + return false; + } } } } // we can always grow, shrink or move a partition not yet written to disk if (p->state() == Partition::State::New && !p->roles().has(PartitionRole::Luks)) return true; if (p->state() == Partition::State::Copy) return false; if (p->isMounted()) return p->fileSystem().supportShrinkOnline(); return p->fileSystem().supportShrink() != FileSystem::cmdSupportNone; } /** Can a Partition be moved? @param p the Partition in question, may be nullptr. @return true if @p p can be moved. */ bool ResizeOperation::canMove(const Partition* p, const QList pendingOps) { if (p == nullptr) return false; if (p->fileSystem().type() == FileSystem::Type::Lvm2_PV) { // See if there is a newly created VG targeting this partition for (Operation *op : qAsConst(pendingOps)) { if (dynamic_cast(op) && op->targets(*p)) return false; } } else if (p->fileSystem().type() == FileSystem::Type::Luks || p->fileSystem().type() == FileSystem::Type::Luks2) { // See if innerFS is LVM - FileSystem *fs = static_cast(&p->fileSystem())->innerFS(); - - if (fs->type() == FileSystem::Type::Lvm2_PV) { - // See if there is a newly created VG targeting this partition - for (Operation *op : qAsConst(pendingOps)) { - if (dynamic_cast(op) && op->targets(*p)) - return false; + FileSystem *fs = dynamic_cast(&p->fileSystem())->innerFS(); + + if (fs) { + if (fs->type() == FileSystem::Type::Lvm2_PV) { + // See if there is a newly created VG targeting this partition + for (Operation *op : qAsConst(pendingOps)) { + if (dynamic_cast(op) && op->targets(*p)) + return false; + } } } } // we can always grow, shrink or move a partition not yet written to disk if (p->state() == Partition::State::New) // too many bad things can happen for LUKS partitions return p->roles().has(PartitionRole::Luks) ? false : true; if (p->isMounted()) return false; // no moving of extended partitions if they have logicals if (p->roles().has(PartitionRole::Extended) && p->hasChildren()) return false; return p->fileSystem().supportMove() != FileSystem::cmdSupportNone; }