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

0001 /*
0002     SPDX-FileCopyrightText: 2008-2012 Volker Lanz <vl@fidra.de>
0003     SPDX-FileCopyrightText: 2012-2020 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     SPDX-FileCopyrightText: 2018 Caio Jordão Carvalho <caiojcarvalho@gmail.com>
0007 
0008     SPDX-License-Identifier: GPL-3.0-or-later
0009 */
0010 
0011 #include "ops/resizeoperation.h"
0012 
0013 #include "core/partition.h"
0014 #include "core/device.h"
0015 #include "core/lvmdevice.h"
0016 #include "core/partitiontable.h"
0017 #include "core/copysourcedevice.h"
0018 #include "core/copytargetdevice.h"
0019 
0020 #include "jobs/checkfilesystemjob.h"
0021 #include "jobs/setpartgeometryjob.h"
0022 #include "jobs/resizefilesystemjob.h"
0023 #include "jobs/movefilesystemjob.h"
0024 
0025 #include "ops/checkoperation.h"
0026 
0027 #include "fs/filesystem.h"
0028 #include "fs/luks.h"
0029 
0030 #include "util/capacity.h"
0031 #include "util/report.h"
0032 
0033 #include <QDebug>
0034 #include <QString>
0035 
0036 #include <KLocalizedString>
0037 
0038 /** Creates a new ResizeOperation.
0039     @param d the Device to resize a Partition on
0040     @param p the Partition to resize
0041     @param newfirst the new first sector of the Partition
0042     @param newlast the new last sector of the Partition
0043 */
0044 ResizeOperation::ResizeOperation(Device& d, Partition& p, qint64 newfirst, qint64 newlast) :
0045     Operation(),
0046     m_TargetDevice(d),
0047     m_Partition(p),
0048     m_OrigFirstSector(partition().firstSector()),
0049     m_OrigLastSector(partition().lastSector()),
0050     m_NewFirstSector(newfirst),
0051     m_NewLastSector(newlast),
0052     m_CheckOriginalJob(new CheckFileSystemJob(partition())),
0053     m_MoveExtendedJob(nullptr),
0054     m_ShrinkResizeJob(nullptr),
0055     m_ShrinkSetGeomJob(nullptr),
0056     m_MoveSetGeomJob(nullptr),
0057     m_MoveFileSystemJob(nullptr),
0058     m_GrowResizeJob(nullptr),
0059     m_GrowSetGeomJob(nullptr),
0060     m_CheckResizedJob(nullptr)
0061 {
0062     if (CheckOperation::canCheck(&partition()))
0063         addJob(checkOriginalJob());
0064 
0065     if (partition().roles().has(PartitionRole::Extended)) {
0066         m_MoveExtendedJob = new SetPartGeometryJob(targetDevice(), partition(), newFirstSector(), newLength());
0067         addJob(moveExtendedJob());
0068     } else {
0069         if (resizeAction() & Shrink) {
0070             m_ShrinkResizeJob = new ResizeFileSystemJob(targetDevice(), partition(), newLength());
0071             m_ShrinkSetGeomJob = new SetPartGeometryJob(targetDevice(), partition(), partition().firstSector(), newLength());
0072 
0073             addJob(shrinkResizeJob());
0074             addJob(shrinkSetGeomJob());
0075         }
0076 
0077         if ((resizeAction() & MoveLeft) || (resizeAction() & MoveRight)) {
0078             // At this point, we need to set the partition's length to either the resized length, if it has already been
0079             // shrunk, or to the original length (it may or may not then later be grown, we don't care here)
0080             const qint64 currentLength = (resizeAction() & Shrink) ? newLength() : partition().length();
0081 
0082             m_MoveSetGeomJob = new SetPartGeometryJob(targetDevice(), partition(), newFirstSector(), currentLength);
0083             m_MoveFileSystemJob = new MoveFileSystemJob(targetDevice(), partition(), newFirstSector());
0084 
0085             addJob(moveSetGeomJob());
0086             addJob(moveFileSystemJob());
0087         }
0088 
0089         if (resizeAction() & Grow) {
0090             m_GrowSetGeomJob = new SetPartGeometryJob(targetDevice(), partition(), newFirstSector(), newLength());
0091             m_GrowResizeJob = new ResizeFileSystemJob(targetDevice(), partition(), newLength());
0092 
0093             addJob(growSetGeomJob());
0094             addJob(growResizeJob());
0095         }
0096 
0097         m_CheckResizedJob = new CheckFileSystemJob(partition());
0098 
0099         if(CheckOperation::canCheck(&partition()))
0100             addJob(checkResizedJob());
0101     }
0102 }
0103 
0104 bool ResizeOperation::targets(const Device& d) const
0105 {
0106     return d == targetDevice();
0107 }
0108 
0109 bool ResizeOperation::targets(const Partition& p) const
0110 {
0111     return p == partition();
0112 }
0113 
0114 void ResizeOperation::preview()
0115 {
0116     // If the operation has already been executed, the partition will of course have newFirstSector and
0117     // newLastSector as first and last sector. But to remove it from its original position, we need to
0118     // temporarily set these values back to where they were before the operation was executed.
0119     if (partition().firstSector() == newFirstSector() && partition().lastSector() == newLastSector()) {
0120         partition().setFirstSector(origFirstSector());
0121         partition().setLastSector(origLastSector());
0122     }
0123 
0124     removePreviewPartition(targetDevice(), partition());
0125 
0126     partition().setFirstSector(newFirstSector());
0127     partition().setLastSector(newLastSector());
0128 
0129     insertPreviewPartition(targetDevice(), partition());
0130 }
0131 
0132 void ResizeOperation::undo()
0133 {
0134     removePreviewPartition(targetDevice(), partition());
0135     partition().setFirstSector(origFirstSector());
0136     partition().setLastSector(origLastSector());
0137     insertPreviewPartition(targetDevice(), partition());
0138 }
0139 
0140 bool ResizeOperation::execute(Report& parent)
0141 {
0142     bool rval = true;
0143 
0144     Report* report = parent.newChild(description());
0145 
0146     if (CheckOperation::canCheck(&partition()))
0147         rval = checkOriginalJob()->run(*report);
0148 
0149     if (rval) {
0150         // Extended partitions are a special case: They don't have any file systems and so there's no
0151         // need to move, shrink or grow their contents before setting the new geometry. In fact, trying
0152         // to first shrink THEN move would not work for an extended partition that has children, because
0153         // they might temporarily be outside the extended partition and the backend would not let us do that.
0154         if (moveExtendedJob()) {
0155             if (!(rval = moveExtendedJob()->run(*report)))
0156                 report->line() << xi18nc("@info:status", "Moving extended partition <filename>%1</filename> failed.", partition().deviceNode());
0157         } else {
0158             // We run all three methods. Any of them returns true if it has nothing to do.
0159             rval = shrink(*report) && move(*report) && grow(*report);
0160 
0161             if (rval) {
0162                 if (CheckOperation::canCheck(&partition())) {
0163                     rval = checkResizedJob()->run(*report);
0164                     if (!rval)
0165                         report->line() << xi18nc("@info:status", "Checking partition <filename>%1</filename> after resize/move failed.", partition().deviceNode());
0166                 }
0167             } else
0168                 report->line() << xi18nc("@info:status", "Resizing/moving partition <filename>%1</filename> failed.", partition().deviceNode());
0169         }
0170     } else
0171         report->line() << xi18nc("@info:status", "Checking partition <filename>%1</filename> before resize/move failed.", partition().deviceNode());
0172 
0173     setStatus(rval ? StatusFinishedSuccess : StatusError);
0174 
0175     report->setStatus(xi18nc("@info:status (success, error, warning...) of operation", "%1: %2", description(), statusText()));
0176 
0177     return rval;
0178 }
0179 
0180 QString ResizeOperation::description() const
0181 {
0182     // There are eight possible things a resize operation might do:
0183     // 1) Move a partition to the left (closer to the start of the disk)
0184     // 2) Move a partition to the right (closer to the end of the disk)
0185     // 3) Grow a partition
0186     // 4) Shrink a partition
0187     // 5) Move a partition to the left and grow it
0188     // 6) Move a partition to the right and grow it
0189     // 7) Move a partition to the left and shrink it
0190     // 8) Move a partition to the right and shrink it
0191     // Each of these needs a different description. And for reasons of i18n, we cannot
0192     // just concatenate strings together...
0193 
0194     const QString moveDelta = Capacity::formatByteSize(qAbs(newFirstSector() - origFirstSector()) * targetDevice().logicalSize());
0195 
0196     const QString origCapacity = Capacity::formatByteSize(origLength() * targetDevice().logicalSize());
0197     const QString newCapacity = Capacity::formatByteSize(newLength() * targetDevice().logicalSize());
0198 
0199     switch (resizeAction()) {
0200     case MoveLeft:
0201         return xi18nc("@info:status describe resize/move action", "Move partition <filename>%1</filename> to the left by %2", partition().deviceNode(), moveDelta);
0202 
0203     case MoveRight:
0204         return xi18nc("@info:status describe resize/move action", "Move partition <filename>%1</filename> to the right by %2", partition().deviceNode(), moveDelta);
0205 
0206     case Grow:
0207         return xi18nc("@info:status describe resize/move action", "Grow partition <filename>%1</filename> from %2 to %3", partition().deviceNode(), origCapacity, newCapacity);
0208 
0209     case Shrink:
0210         return xi18nc("@info:status describe resize/move action", "Shrink partition <filename>%1</filename> from %2 to %3", partition().deviceNode(), origCapacity, newCapacity);
0211 
0212     case MoveLeftGrow:
0213         return xi18nc("@info:status describe resize/move action", "Move partition <filename>%1</filename> to the left by %2 and grow it from %3 to %4", partition().deviceNode(), moveDelta, origCapacity, newCapacity);
0214 
0215     case MoveRightGrow:
0216         return xi18nc("@info:status describe resize/move action", "Move partition <filename>%1</filename> to the right by %2 and grow it from %3 to %4", partition().deviceNode(), moveDelta, origCapacity, newCapacity);
0217 
0218     case MoveLeftShrink:
0219         return xi18nc("@info:status describe resize/move action", "Move partition <filename>%1</filename> to the left by %2 and shrink it from %3 to %4", partition().deviceNode(), moveDelta, origCapacity, newCapacity);
0220 
0221     case MoveRightShrink:
0222         return xi18nc("@info:status describe resize/move action", "Move partition <filename>%1</filename> to the right by %2 and shrink it from %3 to %4", partition().deviceNode(), moveDelta, origCapacity, newCapacity);
0223 
0224     case None:
0225         qWarning() << "Could not determine what to do with partition " << partition().deviceNode() << ".";
0226         break;
0227     }
0228 
0229     return xi18nc("@info:status describe resize/move action", "Unknown resize/move action.");
0230 }
0231 
0232 ResizeOperation::ResizeAction ResizeOperation::resizeAction() const
0233 {
0234     ResizeAction action = None;
0235 
0236     // Grow?
0237     if (newLength() > origLength())
0238         action = Grow;
0239 
0240     // Shrink?
0241     if (newLength() < origLength())
0242         action = Shrink;
0243 
0244     // Move to the right?
0245     if (newFirstSector() > origFirstSector())
0246         action = static_cast<ResizeAction>(action | MoveRight);
0247 
0248     // Move to the left?
0249     if (newFirstSector() < origFirstSector())
0250         action = static_cast<ResizeAction>(action | MoveLeft);
0251 
0252     return action;
0253 }
0254 
0255 bool ResizeOperation::shrink(Report& report)
0256 {
0257     if (shrinkResizeJob() && !shrinkResizeJob()->run(report)) {
0258         report.line() << xi18nc("@info:status", "Resize/move failed: Could not resize file system to shrink partition <filename>%1</filename>.", partition().deviceNode());
0259         return false;
0260     }
0261 
0262     if (shrinkSetGeomJob() && !shrinkSetGeomJob()->run(report)) {
0263         report.line() << xi18nc("@info:status", "Resize/move failed: Could not shrink partition <filename>%1</filename>.", partition().deviceNode());
0264         return false;
0265 
0266         /** @todo if this fails, no one undoes the shrinking of the file system above, because we
0267         rely upon there being a maximize job at the end, but that's no longer the case. */
0268     }
0269 
0270     return true;
0271 }
0272 
0273 bool ResizeOperation::move(Report& report)
0274 {
0275     // We must make sure not to overwrite the partition's metadata if it's a logical partition
0276     // and we're moving to the left. The easiest way to achieve this is to move the
0277     // partition itself first (it's the backend's responsibility to then move the metadata) and
0278     // only afterwards copy the filesystem. Disadvantage: We need to move the partition
0279     // back to its original position if copyBlocks fails.
0280     const qint64 oldStart = partition().firstSector();
0281     if (moveSetGeomJob() && !moveSetGeomJob()->run(report)) {
0282         report.line() << xi18nc("@info:status", "Moving partition <filename>%1</filename> failed.", partition().deviceNode());
0283         return false;
0284     }
0285 
0286     if (moveFileSystemJob() && !moveFileSystemJob()->run(report)) {
0287         report.line() << xi18nc("@info:status", "Moving the filesystem for partition <filename>%1</filename> failed. Rolling back.", partition().deviceNode());
0288 
0289         // see above: We now have to move back the partition itself.
0290         if (!SetPartGeometryJob(targetDevice(), partition(), oldStart, partition().length()).run(report))
0291             report.line() << xi18nc("@info:status", "Moving back partition <filename>%1</filename> to its original position failed.", partition().deviceNode());
0292 
0293         return false;
0294     }
0295 
0296     return true;
0297 }
0298 
0299 bool ResizeOperation::grow(Report& report)
0300 {
0301     const qint64 oldLength = partition().length();
0302 
0303     if (growSetGeomJob() && !growSetGeomJob()->run(report)) {
0304         report.line() << xi18nc("@info:status", "Resize/move failed: Could not grow partition <filename>%1</filename>.", partition().deviceNode());
0305         return false;
0306     }
0307 
0308     if (growResizeJob() && !growResizeJob()->run(report)) {
0309         report.line() << xi18nc("@info:status", "Resize/move failed: Could not resize the file system on partition <filename>%1</filename>", partition().deviceNode());
0310 
0311         if (!SetPartGeometryJob(targetDevice(), partition(), partition().firstSector(), oldLength).run(report))
0312             report.line() << xi18nc("@info:status", "Could not restore old partition size for partition <filename>%1</filename>.", partition().deviceNode());
0313 
0314         return false;
0315     }
0316 
0317     return true;
0318 }
0319 
0320 /** Can a Partition be grown, i.e. increased in size?
0321     @param p the Partition in question, may be nullptr.
0322     @return true if @p p can be grown.
0323  */
0324 bool ResizeOperation::canGrow(const Partition* p)
0325 {
0326     if (p == nullptr)
0327         return false;
0328 
0329     // Whole block device filesystems cannot be resized
0330     if (p->partitionTable()->type() == PartitionTable::TableType::none)
0331         return false;
0332 
0333     if (isLVMPVinNewlyVG(p))
0334         return false;
0335 
0336     // we can always grow, shrink or move a partition not yet written to disk
0337     if (p->state() == Partition::State::New && !p->roles().has(PartitionRole::Luks))
0338         return true;
0339 
0340     if (p->isMounted())
0341         return p->fileSystem().supportGrowOnline();
0342 
0343     return p->fileSystem().supportGrow() != FileSystem::cmdSupportNone;
0344 }
0345 
0346 /** Can a Partition be shrunk, i.e. decreased in size?
0347     @param p the Partition in question, may be nullptr.
0348     @return true if @p p can be shrunk.
0349  */
0350 bool ResizeOperation::canShrink(const Partition* p)
0351 {
0352     if (p == nullptr)
0353         return false;
0354 
0355     // Whole block device filesystems cannot be resized
0356     if (p->partitionTable()->type() == PartitionTable::TableType::none)
0357         return false;
0358 
0359     if (isLVMPVinNewlyVG(p))
0360         return false;
0361 
0362     // we can always grow, shrink or move a partition not yet written to disk
0363     if (p->state() == Partition::State::New && !p->roles().has(PartitionRole::Luks))
0364         return true;
0365 
0366     if (p->state() == Partition::State::Copy)
0367         return false;
0368 
0369     if (p->isMounted())
0370         return p->fileSystem().supportShrinkOnline();
0371 
0372     return p->fileSystem().supportShrink() != FileSystem::cmdSupportNone;
0373 }
0374 
0375 /** Can a Partition be moved?
0376     @param p the Partition in question, may be nullptr.
0377     @return true if @p p can be moved.
0378  */
0379 bool ResizeOperation::canMove(const Partition* p)
0380 {
0381     if (p == nullptr)
0382         return false;
0383 
0384     // Whole block device filesystems cannot be moved
0385     if (p->partitionTable()->type() == PartitionTable::TableType::none)
0386         return false;
0387 
0388     if (isLVMPVinNewlyVG(p))
0389         return false;
0390 
0391     // we can always grow, shrink or move a partition not yet written to disk
0392     if (p->state() == Partition::State::New)
0393         // too many bad things can happen for LUKS partitions
0394         return p->roles().has(PartitionRole::Luks) ? false : true;
0395 
0396     if (p->isMounted())
0397         return false;
0398 
0399     // no moving of extended partitions if they have logicals
0400     if (p->roles().has(PartitionRole::Extended) && p->hasChildren())
0401         return false;
0402 
0403     return p->fileSystem().supportMove() != FileSystem::cmdSupportNone;
0404 }
0405 
0406 bool ResizeOperation::isLVMPVinNewlyVG(const Partition *p)
0407 {
0408     if (p->fileSystem().type() == FileSystem::Type::Lvm2_PV) {
0409         if (LvmDevice::s_DirtyPVs.contains(p))
0410             return true;
0411     }
0412     else if (p->fileSystem().type() == FileSystem::Type::Luks || p->fileSystem().type() == FileSystem::Type::Luks2) {
0413         // See if innerFS is LVM
0414         FileSystem *fs = static_cast<const FS::luks *>(&p->fileSystem())->innerFS();
0415 
0416         if (fs) {
0417             if (fs->type() == FileSystem::Type::Lvm2_PV) {
0418                 if (LvmDevice::s_DirtyPVs.contains(p))
0419                     return true;
0420             }
0421         }
0422     }
0423 
0424     return false;
0425 }