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 }