diff --git a/src/core/lvmdevice.cpp b/src/core/lvmdevice.cpp index 203fb0b..d6b4a24 100644 --- a/src/core/lvmdevice.cpp +++ b/src/core/lvmdevice.cpp @@ -1,566 +1,571 @@ /************************************************************************* * Copyright (C) 2016 by Chantara Tith * * Copyright (C) 2016-2018 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 "core/lvmdevice.h" #include "core/partition.h" #include "core/partitiontable.h" #include "core/volumemanagerdevice_p.h" #include "fs/filesystem.h" #include "fs/lvm2_pv.h" #include "fs/luks.h" #include "fs/filesystemfactory.h" #include "util/externalcommand.h" #include "util/helpers.h" #include "util/globallog.h" #include "util/report.h" #include #include #include #include #define d_ptr std::static_pointer_cast(d) class LvmDevicePrivate : public VolumeManagerDevicePrivate { public: qint64 m_peSize; qint64 m_totalPE; qint64 m_allocPE; qint64 m_freePE; QString m_UUID; mutable QStringList m_LVPathList; QVector m_PVs; mutable std::unique_ptr> m_LVSizeMap; }; /** Constructs a representation of LVM device with initialized LV as Partitions * * @param vgName Volume Group name * @param iconName Icon representing LVM Volume group */ LvmDevice::LvmDevice(const QString& vgName, const QString& iconName) : VolumeManagerDevice(std::make_shared(), vgName, (QStringLiteral("/dev/") + vgName), getPeSize(vgName), getTotalPE(vgName), iconName, Device::Type::LVM_Device) { d_ptr->m_peSize = logicalSize(); d_ptr->m_totalPE = totalLogical(); d_ptr->m_freePE = getFreePE(vgName); d_ptr->m_allocPE = d_ptr->m_totalPE - d_ptr->m_freePE; d_ptr->m_UUID = getUUID(vgName); d_ptr->m_LVPathList = getLVs(vgName); d_ptr->m_LVSizeMap = std::make_unique>(); initPartitions(); } /** * shared list of PV's paths that will be added to any VGs. * (have been added to an operation, but not yet applied) */ QVector LvmDevice::s_DirtyPVs; /** * shared list of PVs paths that are member of VGs that will be deleted soon. */ QVector LvmDevice::s_OrphanPVs; LvmDevice::~LvmDevice() { } void LvmDevice::initPartitions() { qint64 firstUsable = 0; qint64 lastUsable = totalPE() - 1; PartitionTable* pTable = new PartitionTable(PartitionTable::vmd, firstUsable, lastUsable); for (const auto &p : scanPartitions(pTable)) { LVSizeMap()->insert(p->partitionPath(), p->length()); pTable->append(p); } if (pTable) pTable->updateUnallocated(*this); else pTable = new PartitionTable(PartitionTable::vmd, firstUsable, lastUsable); setPartitionTable(pTable); } /** * Scan LVM LV Partitions * * @param pTable Virtual PartitionTable of LVM device * @return an initialized Partition(LV) list */ const QList LvmDevice::scanPartitions(PartitionTable* pTable) const { QList pList; for (const auto &lvPath : partitionNodes()) { Partition *p = scanPartition(lvPath, pTable); pList.append(p); } return pList; } /** scan and construct a partition(LV) at a given path * * NOTE: * LVM partition has 2 different start and end sector values * 1. representing the actual LV start from 0 -> size of LV - 1 * 2. representing abstract LV's sector inside a VG partitionTable * start from last sector + 1 of last Partitions -> size of LV - 1 * Reason for this is for the LV Partition to work nicely with other parts of the codebase * without too many special cases. * * @param lvPath LVM Logical Volume path * @param pTable Abstract partition table representing partitions of LVM Volume Group * @return initialized Partition(LV) */ Partition* LvmDevice::scanPartition(const QString& lvPath, PartitionTable* pTable) const { activateLV(lvPath); qint64 lvSize = getTotalLE(lvPath); qint64 startSector = mappedSector(lvPath, 0); qint64 endSector = startSector + lvSize - 1; FileSystem::Type type = FileSystem::detectFileSystem(lvPath); FileSystem* fs = FileSystemFactory::create(type, 0, lvSize - 1, logicalSize()); fs->scan(lvPath); PartitionRole::Roles r = PartitionRole::Lvm_Lv; QString mountPoint; bool mounted; // Handle LUKS partition if (fs->type() == FileSystem::Type::Luks) { r |= PartitionRole::Luks; FS::luks* luksFs = static_cast(fs); luksFs->initLUKS(); QString mapperNode = luksFs->mapperName(); mountPoint = FileSystem::detectMountPoint(fs, mapperNode); mounted = FileSystem::detectMountStatus(fs, mapperNode); } else { mountPoint = FileSystem::detectMountPoint(fs, lvPath); mounted = FileSystem::detectMountStatus(fs, lvPath); if (mountPoint != QString() && fs->type() != FileSystem::Type::LinuxSwap) { const QStorageInfo storage = QStorageInfo(mountPoint); if (logicalSize() > 0 && fs->type() != FileSystem::Type::Luks && mounted && storage.isValid()) fs->setSectorsUsed( (storage.bytesTotal() - storage.bytesFree()) / logicalSize() ); } else if (fs->supportGetUsed() == FileSystem::cmdSupportFileSystem) fs->setSectorsUsed(qCeil(fs->readUsedCapacity(lvPath) / static_cast(logicalSize()))); } if (fs->supportGetLabel() != FileSystem::cmdSupportNone) { fs->setLabel(fs->readLabel(lvPath)); } if (fs->supportGetUUID() != FileSystem::cmdSupportNone) fs->setUUID(fs->readUUID(lvPath)); Partition* part = new Partition(pTable, *this, PartitionRole(r), fs, startSector, endSector, lvPath, PartitionTable::Flag::FlagNone, mountPoint, mounted); return part; } /** scan and construct list of initialized LvmDevice objects. * * @param devices list of initialized Devices */ void LvmDevice::scanSystemLVM(QList& devices) { LvmDevice::s_OrphanPVs.clear(); QList lvmList; for (const auto &vgName : getVGs()) { lvmList.append(new LvmDevice(vgName)); } // Some LVM operations require additional information about LVM physical volumes which we store in LVM::pvList::list() LVM::pvList::list().clear(); LVM::pvList::list().append(FS::lvm2_pv::getPVs(devices)); // Look for LVM physical volumes in LVM VGs for (const auto &d : lvmList) { devices.append(d); LVM::pvList::list().append(FS::lvm2_pv::getPVinNode(d->partitionTable())); } // Inform LvmDevice about which physical volumes form that particular LvmDevice for (const auto &d : lvmList) for (const auto &p : qAsConst(LVM::pvList::list())) if (p.vgName() == d->name()) d->physicalVolumes().append(p.partition()); } qint64 LvmDevice::mappedSector(const QString& lvPath, qint64 sector) const { qint64 mSector = 0; QStringList lvpathList = partitionNodes(); qint32 devIndex = lvpathList.indexOf(lvPath); if (devIndex) { for (int i = 0; i < devIndex; i++) { mSector += LVSizeMap()->value(lvpathList[i]); } mSector += sector; } return mSector; } const QStringList LvmDevice::deviceNodes() const { QStringList pvList; for (const auto &p : physicalVolumes()) { if (p->roles().has(PartitionRole::Luks)) pvList << static_cast(&p->fileSystem())->mapperName(); else pvList << p->partitionPath(); } return pvList; } const QStringList& LvmDevice::partitionNodes() const { return d_ptr->m_LVPathList; } qint64 LvmDevice::partitionSize(QString& partitionPath) const { return LVSizeMap()->value(partitionPath); } const QStringList LvmDevice::getVGs() { QStringList vgList; QString output = getField(QStringLiteral("vg_name")); if (!output.isEmpty()) { const QStringList vgNameList = output.split(QLatin1Char('\n'), QString::SkipEmptyParts); for (const auto &vgName : vgNameList) { vgList.append(vgName.trimmed()); } } return vgList; } const QStringList LvmDevice::getLVs(const QString& vgName) { QStringList lvPathList; QString cmdOutput = getField(QStringLiteral("lv_path"), vgName); if (cmdOutput.size()) { const QStringList tempPathList = cmdOutput.split(QLatin1Char('\n'), QString::SkipEmptyParts); for (const auto &lvPath : tempPathList) { lvPathList.append(lvPath.trimmed()); } } return lvPathList; } qint64 LvmDevice::getPeSize(const QString& vgName) { QString val = getField(QStringLiteral("vg_extent_size"), vgName); return val.isEmpty() ? -1 : val.toLongLong(); } qint64 LvmDevice::getTotalPE(const QString& vgName) { QString val = getField(QStringLiteral("vg_extent_count"), vgName); return val.isEmpty() ? -1 : val.toInt(); } qint64 LvmDevice::getAllocatedPE(const QString& vgName) { return getTotalPE(vgName) - getFreePE(vgName); } qint64 LvmDevice::getFreePE(const QString& vgName) { QString val = getField(QStringLiteral("vg_free_count"), vgName); return val.isEmpty() ? -1 : val.toInt(); } QString LvmDevice::getUUID(const QString& vgName) { QString val = getField(QStringLiteral("vg_uuid"), vgName); return val.isEmpty() ? QStringLiteral("---") : val; } /** Get LVM vgs command output with field name * * @param fieldName LVM field name * @param vgName the name of LVM Volume Group * @return raw output of command output, usually with many spaces within the returned string * */ QString LvmDevice::getField(const QString& fieldName, const QString& vgName) { QStringList args = { QStringLiteral("vgs"), QStringLiteral("--foreign"), QStringLiteral("--readonly"), QStringLiteral("--noheadings"), QStringLiteral("--units"), QStringLiteral("B"), QStringLiteral("--nosuffix"), QStringLiteral("--options"), fieldName }; if (!vgName.isEmpty()) { args << vgName; } ExternalCommand cmd(QStringLiteral("lvm"), args, QProcess::ProcessChannelMode::SeparateChannels); if (cmd.run(-1) && cmd.exitCode() == 0) { return cmd.output().trimmed(); } return QString(); } qint64 LvmDevice::getTotalLE(const QString& lvPath) { ExternalCommand cmd(QStringLiteral("lvm"), { QStringLiteral("lvdisplay"), lvPath}); if (cmd.run(-1) && cmd.exitCode() == 0) { QRegularExpression re(QStringLiteral("Current LE\\h+(\\d+)")); QRegularExpressionMatch match = re.match(cmd.output()); if (match.hasMatch()) { return match.captured(1).toInt(); } } Log(Log::Level::error) << xi18nc("@info:status", "An error occurred while running lvdisplay."); return -1; } bool LvmDevice::removeLV(Report& report, LvmDevice& d, Partition& p) { ExternalCommand cmd(report, QStringLiteral("lvm"), { QStringLiteral("lvremove"), QStringLiteral("--yes"), p.partitionPath()}); if (cmd.run(-1) && cmd.exitCode() == 0) { d.partitionTable()->remove(&p); return true; } return false; } bool LvmDevice::createLV(Report& report, LvmDevice& d, Partition& p, const QString& lvName) { ExternalCommand cmd(report, QStringLiteral("lvm"), { QStringLiteral("lvcreate"), QStringLiteral("--yes"), QStringLiteral("--extents"), QString::number(p.length()), QStringLiteral("--name"), lvName, d.name()}); return (cmd.run(-1) && cmd.exitCode() == 0); } bool LvmDevice::createLVSnapshot(Report& report, Partition& p, const QString& name, const qint64 extents) { QString numExtents = (extents > 0) ? QString::number(extents) : QString::number(p.length()); ExternalCommand cmd(report, QStringLiteral("lvm"), { QStringLiteral("lvcreate"), QStringLiteral("--yes"), QStringLiteral("--extents"), numExtents, QStringLiteral("--snapshot"), QStringLiteral("--name"), name, p.partitionPath() }); return (cmd.run(-1) && cmd.exitCode() == 0); } bool LvmDevice::resizeLV(Report& report, Partition& p) { ExternalCommand cmd(report, QStringLiteral("lvm"), { QStringLiteral("lvresize"), QStringLiteral("--force"), QStringLiteral("--yes"), QStringLiteral("--extents"), QString::number(p.length()), p.partitionPath()}); return (cmd.run(-1) && cmd.exitCode() == 0); } bool LvmDevice::removePV(Report& report, LvmDevice& d, const QString& pvPath) { ExternalCommand cmd(report, QStringLiteral("lvm"), { QStringLiteral("vgreduce"), d.name(), pvPath}); return (cmd.run(-1) && cmd.exitCode() == 0); } bool LvmDevice::insertPV(Report& report, LvmDevice& d, const QString& pvPath) { ExternalCommand cmd(report, QStringLiteral("lvm"), { QStringLiteral("vgextend"), QStringLiteral("--yes"), d.name(), pvPath}); return (cmd.run(-1) && cmd.exitCode() == 0); } bool LvmDevice::movePV(Report& report, const QString& pvPath, const QStringList& destinations) { if (FS::lvm2_pv::getAllocatedPE(pvPath) <= 0) return true; QStringList args = { QStringLiteral("pvmove") }; args << pvPath; if (!destinations.isEmpty()) for (const auto &destPath : destinations) args << destPath.trimmed(); ExternalCommand cmd(report, QStringLiteral("lvm"), args); return (cmd.run(-1) && cmd.exitCode() == 0); } bool LvmDevice::createVG(Report& report, const QString vgName, const QVector& pvList, const qint32 peSize) { QStringList args = { QStringLiteral("vgcreate"), QStringLiteral("--physicalextentsize"), QString::number(peSize) }; args << vgName; for (const auto &p : pvList) { if (p->roles().has(PartitionRole::Luks)) args << static_cast(&p->fileSystem())->mapperName(); else args << p->partitionPath(); } ExternalCommand cmd(report, QStringLiteral("lvm"), args); return (cmd.run(-1) && cmd.exitCode() == 0); } bool LvmDevice::removeVG(Report& report, LvmDevice& d) { bool deactivated = deactivateVG(report, d); ExternalCommand cmd(report, QStringLiteral("lvm"), { QStringLiteral("vgremove"), QStringLiteral("--force"), d.name() }); return (deactivated && cmd.run(-1) && cmd.exitCode() == 0); } bool LvmDevice::deactivateVG(Report& report, const LvmDevice& d) { ExternalCommand deactivate(report, QStringLiteral("lvm"), { QStringLiteral("vgchange"), QStringLiteral("--activate"), QStringLiteral("n"), d.name() }); return deactivate.run(-1) && deactivate.exitCode() == 0; } bool LvmDevice::deactivateLV(Report& report, const Partition& p) { ExternalCommand deactivate(report, QStringLiteral("lvm"), { QStringLiteral("lvchange"), QStringLiteral("--activate"), QStringLiteral("n"), p.partitionPath() }); return deactivate.run(-1) && deactivate.exitCode() == 0; } bool LvmDevice::activateVG(Report& report, const LvmDevice& d) { ExternalCommand deactivate(report, QStringLiteral("lvm"), { QStringLiteral("vgchange"), QStringLiteral("--activate"), QStringLiteral("y"), d.name() }); return deactivate.run(-1) && deactivate.exitCode() == 0; } bool LvmDevice::activateLV(const QString& lvPath) { ExternalCommand deactivate(QStringLiteral("lvm"), { QStringLiteral("lvchange"), QStringLiteral("--activate"), QStringLiteral("y"), lvPath }); return deactivate.run(-1) && deactivate.exitCode() == 0; } qint64 LvmDevice::peSize() const { return d_ptr->m_peSize; } qint64 LvmDevice::totalPE() const { return d_ptr->m_totalPE; } qint64 LvmDevice::allocatedPE() const { return d_ptr->m_allocPE; } qint64 LvmDevice::freePE() const { return d_ptr->m_freePE; } +void LvmDevice::setFreePE(qint64 freePE) const +{ + d_ptr->m_freePE = freePE; +} + QString LvmDevice::UUID() const { return d_ptr->m_UUID; } QVector & LvmDevice::physicalVolumes() { return d_ptr->m_PVs; } const QVector & LvmDevice::physicalVolumes() const { return d_ptr->m_PVs; } std::unique_ptr>& LvmDevice::LVSizeMap() const { return d_ptr->m_LVSizeMap; } diff --git a/src/core/lvmdevice.h b/src/core/lvmdevice.h index 089719b..18b546f 100644 --- a/src/core/lvmdevice.h +++ b/src/core/lvmdevice.h @@ -1,109 +1,110 @@ /************************************************************************* * Copyright (C) 2016 by Chantara Tith * * 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 .* *************************************************************************/ #ifndef KPMCORE_LVMDEVICE_H #define KPMCORE_LVMDEVICE_H #include "core/device.h" #include "core/volumemanagerdevice.h" #include "util/libpartitionmanagerexport.h" #include #include #include #include #include #include class PartitionTable; class Report; class Partition; class SmartStatus; /** Representation of LVM Volume Group(VG). Devices are the outermost entity; they contain a PartitionTable that itself contains Partitions. @see Device, VolumeManagerDevice, PartitionTable, Partition */ class LIBKPMCORE_EXPORT LvmDevice : public VolumeManagerDevice { Q_DISABLE_COPY(LvmDevice) public: LvmDevice(const QString& name, const QString& iconName = QString()); ~LvmDevice(); public: const QStringList deviceNodes() const override; const QStringList& partitionNodes() const override; qint64 partitionSize(QString& partitionPath) const override; static QVector s_DirtyPVs; static QVector s_OrphanPVs; static void scanSystemLVM(QList& devices); static const QStringList getVGs(); static const QStringList getLVs(const QString& vgName); static qint64 getPeSize(const QString& vgName); static qint64 getTotalPE(const QString& vgName); static qint64 getAllocatedPE(const QString& vgName); static qint64 getFreePE(const QString& vgName); static QString getUUID(const QString& vgName); static QString getField(const QString& fieldName, const QString& vgName = QString()); static qint64 getTotalLE(const QString& lvPath); static bool removeLV(Report& report, LvmDevice& d, Partition& p); static bool createLV(Report& report, LvmDevice& d, Partition& p, const QString& lvName); static bool createLVSnapshot(Report& report, Partition& p, const QString& name, const qint64 extents = 0); static bool resizeLV(Report& report, Partition& p); static bool deactivateLV(Report& report, const Partition& p); static bool activateLV(const QString& deviceNode); static bool removePV(Report& report, LvmDevice& d, const QString& pvPath); static bool insertPV(Report& report, LvmDevice& d, const QString& pvPath); static bool movePV(Report& report, const QString& pvPath, const QStringList& destinations = QStringList()); static bool removeVG(Report& report, LvmDevice& d); static bool createVG(Report& report, const QString vgName, const QVector& pvList, const qint32 peSize = 4); // peSize in megabytes static bool deactivateVG(Report& report, const LvmDevice& d); static bool activateVG(Report& report, const LvmDevice& d); protected: void initPartitions() override; const QList scanPartitions(PartitionTable* pTable) const; Partition* scanPartition(const QString& lvPath, PartitionTable* pTable) const; qint64 mappedSector(const QString& lvPath, qint64 sector) const override; public: qint64 peSize() const; qint64 totalPE() const; qint64 allocatedPE() const; qint64 freePE() const; + void setFreePE(qint64 freePE) const; QString UUID() const; QVector & physicalVolumes(); const QVector & physicalVolumes() const; protected: std::unique_ptr>& LVSizeMap() const; }; #endif diff --git a/src/ops/resizeoperation.cpp b/src/ops/resizeoperation.cpp index 249f2b1..fee9207 100644 --- a/src/ops/resizeoperation.cpp +++ b/src/ops/resizeoperation.cpp @@ -1,421 +1,431 @@ /************************************************************************* * 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/lvmdevice.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 "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())) + 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 (targetDevice().type() == Device::Type::LVM_Device) { + const LvmDevice& lvm = static_cast(targetDevice()); + lvm.setFreePE(lvm.freePE() + partition().lastSector() - newLastSector()); + } + // 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() { + if (targetDevice().type() == Device::Type::LVM_Device) { + const LvmDevice& lvm = static_cast(targetDevice()); + lvm.setFreePE(lvm.freePE() - origLastSector() + partition().lastSector()); + } + 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; if (isLVMPVinNewlyVG(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->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) { if (p == nullptr) return false; if (isLVMPVinNewlyVG(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) { if (p == nullptr) return false; if (isLVMPVinNewlyVG(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; } bool ResizeOperation::isLVMPVinNewlyVG(const Partition *p) { if (p->fileSystem().type() == FileSystem::Type::Lvm2_PV) { if (LvmDevice::s_DirtyPVs.contains(p)) return true; } 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) { if (fs->type() == FileSystem::Type::Lvm2_PV) { if (LvmDevice::s_DirtyPVs.contains(p)) return true; } } } return false; }