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

0001 /*
0002     SPDX-FileCopyrightText: 2016 Chantara Tith <tith.chantara@gmail.com>
0003     SPDX-FileCopyrightText: 2016-2020 Andrius Štikonas <andrius@stikonas.eu>
0004     SPDX-FileCopyrightText: 2018 Caio Jordão Carvalho <caiojcarvalho@gmail.com>
0005     SPDX-FileCopyrightText: 2019 Yuri Chornoivan <yurchor@ukr.net>
0006 
0007     SPDX-License-Identifier: GPL-3.0-or-later
0008 */
0009 
0010 #include "core/lvmdevice.h"
0011 #include "core/partition.h"
0012 #include "core/partitiontable.h"
0013 #include "core/volumemanagerdevice_p.h"
0014 #include "fs/filesystem.h"
0015 #include "fs/lvm2_pv.h"
0016 #include "fs/luks.h"
0017 #include "fs/filesystemfactory.h"
0018 
0019 #include "util/externalcommand.h"
0020 #include "util/helpers.h"
0021 #include "util/globallog.h"
0022 #include "util/report.h"
0023 
0024 #include <utility>
0025 
0026 #include <QRegularExpression>
0027 #include <QStorageInfo>
0028 #include <QtMath>
0029 
0030 #include <KLocalizedString>
0031 
0032 #define d_ptr std::static_pointer_cast<LvmDevicePrivate>(d)
0033 
0034 class LvmDevicePrivate : public VolumeManagerDevicePrivate
0035 {
0036 public:
0037     qint64 m_peSize;
0038     qint64 m_totalPE;
0039     qint64 m_allocPE;
0040     qint64 m_freePE;
0041     QString m_UUID;
0042 
0043     mutable QStringList m_LVPathList;
0044     QVector <const Partition*> m_PVs;
0045     mutable std::unique_ptr<QHash<QString, qint64>> m_LVSizeMap;
0046 };
0047 
0048 /** Constructs a representation of LVM device with initialized LV as Partitions
0049  *
0050  *  @param vgName Volume Group name
0051  *  @param iconName Icon representing LVM Volume group
0052  */
0053 LvmDevice::LvmDevice(const QString& vgName, const QString& iconName)
0054     : VolumeManagerDevice(std::make_shared<LvmDevicePrivate>(),
0055                           vgName,
0056                           (QStringLiteral("/dev/") + vgName),
0057                           getPeSize(vgName),
0058                           getTotalPE(vgName),
0059                           iconName,
0060                           Device::Type::LVM_Device)
0061 {
0062     d_ptr->m_peSize  = logicalSize();
0063     d_ptr->m_totalPE = totalLogical();
0064     d_ptr->m_freePE  = getFreePE(vgName);
0065     d_ptr->m_allocPE = d_ptr->m_totalPE - d_ptr->m_freePE;
0066     d_ptr->m_UUID    = getUUID(vgName);
0067     d_ptr->m_LVPathList = getLVs(vgName);
0068     d_ptr->m_LVSizeMap  = std::make_unique<QHash<QString, qint64>>();
0069 
0070     initPartitions();
0071 }
0072 
0073 /**
0074  * shared list of PV's paths that will be added to any VGs.
0075  * (have been added to an operation, but not yet applied)
0076 */
0077 QVector<const Partition*> LvmDevice::s_DirtyPVs;
0078 
0079 
0080 /**
0081  * shared list of PVs paths that are member of VGs that will be deleted soon.
0082  */
0083 QVector<const Partition*> LvmDevice::s_OrphanPVs;
0084 
0085 LvmDevice::~LvmDevice()
0086 {
0087 }
0088 
0089 void LvmDevice::initPartitions()
0090 {
0091     qint64 firstUsable = 0;
0092     qint64 lastUsable  = totalPE() - 1;
0093     PartitionTable* pTable = new PartitionTable(PartitionTable::vmd, firstUsable, lastUsable);
0094 
0095     for (const auto &p : scanPartitions(pTable)) {
0096         LVSizeMap()->insert(p->partitionPath(), p->length());
0097         pTable->append(p);
0098     }
0099 
0100     if (pTable)
0101         pTable->updateUnallocated(*this);
0102     else
0103         pTable = new PartitionTable(PartitionTable::vmd, firstUsable, lastUsable);
0104 
0105     setPartitionTable(pTable);
0106 }
0107 
0108 /**
0109  *  Scan LVM LV Partitions
0110  *
0111  *  @param pTable Virtual PartitionTable of LVM device
0112  *  @return an initialized Partition(LV) list
0113  */
0114 const QList<Partition*> LvmDevice::scanPartitions(PartitionTable* pTable) const
0115 {
0116     QList<Partition*> pList;
0117     for (const auto &lvPath : partitionNodes()) {
0118         Partition *p = scanPartition(lvPath, pTable);
0119         pList.append(p);
0120     }
0121     return pList;
0122 }
0123 
0124 /** scan and construct a partition(LV) at a given path
0125  *
0126  * NOTE:
0127  * LVM partition has 2 different start and end sector values
0128  * 1. representing the actual LV start from 0 -> size of LV - 1
0129  * 2. representing abstract LV's sector inside a VG partitionTable
0130  *    start from last sector + 1 of last Partitions -> size of LV - 1
0131  * Reason for this is for the LV Partition to work nicely with other parts of the codebase
0132  * without too many special cases.
0133  *
0134  * @param lvPath LVM Logical Volume path
0135  * @param pTable Abstract partition table representing partitions of LVM Volume Group
0136  * @return initialized Partition(LV)
0137  */
0138 Partition* LvmDevice::scanPartition(const QString& lvPath, PartitionTable* pTable) const
0139 {
0140     activateLV(lvPath);
0141 
0142     qint64 lvSize = getTotalLE(lvPath);
0143     qint64 startSector = mappedSector(lvPath, 0);
0144     qint64 endSector = startSector + lvSize - 1;
0145 
0146     FileSystem::Type type = FileSystem::detectFileSystem(lvPath);
0147     FileSystem* fs = FileSystemFactory::create(type, 0, lvSize - 1, logicalSize());
0148     fs->scan(lvPath);
0149 
0150     PartitionRole::Roles r = PartitionRole::Lvm_Lv;
0151     QString mountPoint;
0152     bool mounted;
0153 
0154     // Handle LUKS partition
0155     if (fs->type() == FileSystem::Type::Luks) {
0156         r |= PartitionRole::Luks;
0157         FS::luks* luksFs = static_cast<FS::luks*>(fs);
0158         luksFs->initLUKS();
0159 
0160         QString mapperNode = luksFs->mapperName();
0161         mountPoint = FileSystem::detectMountPoint(fs, mapperNode);
0162         mounted    = FileSystem::detectMountStatus(fs, mapperNode);
0163     } else {
0164         mountPoint = FileSystem::detectMountPoint(fs, lvPath);
0165         mounted = FileSystem::detectMountStatus(fs, lvPath);
0166 
0167         if (!mountPoint.isEmpty() && fs->type() != FileSystem::Type::LinuxSwap) {
0168             const QStorageInfo storage = QStorageInfo(mountPoint);
0169             if (logicalSize() > 0 && fs->type() != FileSystem::Type::Luks && mounted && storage.isValid())
0170                 fs->setSectorsUsed( (storage.bytesTotal() - storage.bytesFree()) / logicalSize() );
0171         }
0172         else if (fs->supportGetUsed() == FileSystem::cmdSupportFileSystem)
0173             fs->setSectorsUsed(qCeil(fs->readUsedCapacity(lvPath) / static_cast<double>(logicalSize())));
0174    }
0175 
0176     if (fs->supportGetLabel() != FileSystem::cmdSupportNone) {
0177         fs->setLabel(fs->readLabel(lvPath));
0178     }
0179     if (fs->supportGetUUID() != FileSystem::cmdSupportNone)
0180         fs->setUUID(fs->readUUID(lvPath));
0181 
0182     Partition* part = new Partition(pTable,
0183                     *this,
0184                     PartitionRole(r),
0185                     fs,
0186                     startSector,
0187                     endSector,
0188                     lvPath,
0189                     PartitionTable::Flag::None,
0190                     mountPoint,
0191                     mounted);
0192     return part;
0193 }
0194 
0195 /** scan and construct list of initialized LvmDevice objects.
0196  *
0197  *  @param devices list of initialized Devices
0198  */
0199 void LvmDevice::scanSystemLVM(QList<Device*>& devices)
0200 {
0201     LvmDevice::s_OrphanPVs.clear();
0202 
0203     QList<LvmDevice*> lvmList;
0204     for (const auto &vgName : getVGs()) {
0205         lvmList.append(new LvmDevice(vgName));
0206     }
0207 
0208     // Some LVM operations require additional information about LVM physical volumes which we store in LVM::pvList::list()
0209     LVM::pvList::list().clear();
0210     LVM::pvList::list().append(FS::lvm2_pv::getPVs(devices));
0211 
0212     // Look for LVM physical volumes in LVM VGs
0213     for (const auto &d : lvmList) {
0214         devices.append(d);
0215         LVM::pvList::list().append(FS::lvm2_pv::getPVinNode(d->partitionTable()));
0216     }
0217 
0218     // Inform LvmDevice about which physical volumes form that particular LvmDevice
0219     for (const auto &d : lvmList)
0220         for (const auto &p : std::as_const(LVM::pvList::list()))
0221             if (p.vgName() == d->name())
0222                 d->physicalVolumes().append(p.partition());
0223 
0224 }
0225 
0226 qint64 LvmDevice::mappedSector(const QString& lvPath, qint64 sector) const
0227 {
0228     qint64 mSector = 0;
0229     QStringList lvpathList = partitionNodes();
0230     qint32 devIndex = lvpathList.indexOf(lvPath);
0231 
0232     if (devIndex) {
0233         for (int i = 0; i < devIndex; i++) {
0234             mSector += LVSizeMap()->value(lvpathList[i]);
0235         }
0236         mSector += sector;
0237     }
0238     return mSector;
0239 }
0240 
0241 const QStringList LvmDevice::deviceNodes() const
0242 {
0243     QStringList pvList;
0244     for (const auto &p : physicalVolumes()) {
0245         if (p->roles().has(PartitionRole::Luks))
0246             pvList << static_cast<const FS::luks*>(&p->fileSystem())->mapperName();
0247         else
0248             pvList << p->partitionPath();
0249     }
0250 
0251     return pvList;
0252 }
0253 
0254 const QStringList& LvmDevice::partitionNodes() const
0255 {
0256     return d_ptr->m_LVPathList;
0257 }
0258 
0259 qint64 LvmDevice::partitionSize(QString& partitionPath) const
0260 {
0261     return LVSizeMap()->value(partitionPath);
0262 }
0263 
0264 const QStringList LvmDevice::getVGs()
0265 {
0266     QStringList vgList;
0267     QString output = getField(QStringLiteral("vg_name"));
0268     if (!output.isEmpty()) {
0269         const QStringList vgNameList = output.split(QLatin1Char('\n'), Qt::SkipEmptyParts);
0270         for (const auto &vgName : vgNameList) {
0271             vgList.append(vgName.trimmed());
0272         }
0273     }
0274     return vgList;
0275 }
0276 
0277 const QStringList LvmDevice::getLVs(const QString& vgName)
0278 {
0279     QStringList lvPathList;
0280     QString cmdOutput = getField(QStringLiteral("lv_path"), vgName);
0281 
0282     if (cmdOutput.size()) {
0283         const QStringList tempPathList = cmdOutput.split(QLatin1Char('\n'), Qt::SkipEmptyParts);
0284         for (const auto &lvPath : tempPathList) {
0285             lvPathList.append(lvPath.trimmed());
0286         }
0287     }
0288     return lvPathList;
0289 }
0290 
0291 qint64 LvmDevice::getPeSize(const QString& vgName)
0292 {
0293     QString val = getField(QStringLiteral("vg_extent_size"), vgName);
0294     return val.isEmpty() ? -1 : val.toLongLong();
0295 }
0296 
0297 qint64 LvmDevice::getTotalPE(const QString& vgName)
0298 {
0299     QString val = getField(QStringLiteral("vg_extent_count"), vgName);
0300     return val.isEmpty() ? -1 : val.toInt();
0301 }
0302 
0303 qint64 LvmDevice::getAllocatedPE(const QString& vgName)
0304 {
0305     return getTotalPE(vgName) - getFreePE(vgName);
0306 }
0307 
0308 qint64 LvmDevice::getFreePE(const QString& vgName)
0309 {
0310     QString val =  getField(QStringLiteral("vg_free_count"), vgName);
0311     return val.isEmpty() ? -1 : val.toInt();
0312 }
0313 
0314 QString LvmDevice::getUUID(const QString& vgName)
0315 {
0316     QString val = getField(QStringLiteral("vg_uuid"), vgName);
0317     return val.isEmpty() ? QStringLiteral("---") : val;
0318 
0319 }
0320 
0321 /** Get LVM vgs command output with field name
0322  *
0323  * @param fieldName LVM field name
0324  * @param vgName the name of LVM Volume Group
0325  * @return raw output of command output, usually with many spaces within the returned string
0326  * */
0327 
0328 QString LvmDevice::getField(const QString& fieldName, const QString& vgName)
0329 {
0330     QStringList args = { QStringLiteral("vgs"),
0331               QStringLiteral("--foreign"),
0332               QStringLiteral("--readonly"),
0333               QStringLiteral("--noheadings"),
0334               QStringLiteral("--units"),
0335               QStringLiteral("B"),
0336               QStringLiteral("--nosuffix"),
0337               QStringLiteral("--options"),
0338               fieldName };
0339     if  (!vgName.isEmpty()) {
0340         args << vgName;
0341     }
0342     ExternalCommand cmd(QStringLiteral("lvm"), args, QProcess::ProcessChannelMode::SeparateChannels);
0343     if (cmd.run(-1) && cmd.exitCode() == 0) {
0344         return cmd.output().trimmed();
0345     }
0346     return QString();
0347 }
0348 
0349 qint64 LvmDevice::getTotalLE(const QString& lvPath)
0350 {
0351     ExternalCommand cmd(QStringLiteral("lvm"),
0352             { QStringLiteral("lvdisplay"),
0353               lvPath});
0354 
0355     if (cmd.run(-1) && cmd.exitCode() == 0) {
0356         QRegularExpression re(QStringLiteral("Current LE\\h+(\\d+)"));
0357         QRegularExpressionMatch match = re.match(cmd.output());
0358         if (match.hasMatch()) {
0359              return  match.captured(1).toInt();
0360         }
0361     }
0362     Log(Log::Level::error) << xi18nc("@info:status", "An error occurred while running lvdisplay.");
0363     return -1;
0364 }
0365 
0366 bool LvmDevice::removeLV(Report& report, LvmDevice& d, Partition& p)
0367 {
0368     ExternalCommand cmd(report, QStringLiteral("lvm"),
0369             { QStringLiteral("lvremove"),
0370               QStringLiteral("--yes"),
0371               p.partitionPath()});
0372 
0373     if (cmd.run(-1) && cmd.exitCode() == 0) {
0374         d.partitionTable()->remove(&p);
0375         return  true;
0376     }
0377     return false;
0378 }
0379 
0380 bool LvmDevice::createLV(Report& report, LvmDevice& d, Partition& p, const QString& lvName)
0381 {
0382     ExternalCommand cmd(report, QStringLiteral("lvm"),
0383             { QStringLiteral("lvcreate"),
0384               QStringLiteral("--yes"),
0385               QStringLiteral("--extents"),
0386               QString::number(p.length()),
0387               QStringLiteral("--name"),
0388               lvName,
0389               d.name()});
0390 
0391     return (cmd.run(-1) && cmd.exitCode() == 0);
0392 }
0393 
0394 bool LvmDevice::createLVSnapshot(Report& report, Partition& p, const QString& name, const qint64 extents)
0395 {
0396     QString numExtents = (extents > 0) ? QString::number(extents) :
0397         QString::number(p.length());
0398     ExternalCommand cmd(report, QStringLiteral("lvm"),
0399             { QStringLiteral("lvcreate"),
0400               QStringLiteral("--yes"),
0401               QStringLiteral("--extents"),
0402               numExtents,
0403               QStringLiteral("--snapshot"),
0404               QStringLiteral("--name"),
0405               name,
0406               p.partitionPath() });
0407     return (cmd.run(-1) && cmd.exitCode() == 0);
0408 }
0409 
0410 bool LvmDevice::resizeLV(Report& report, Partition& p)
0411 {
0412     ExternalCommand cmd(report, QStringLiteral("lvm"),
0413             { QStringLiteral("lvresize"),
0414               QStringLiteral("--force"),
0415               QStringLiteral("--yes"),
0416               QStringLiteral("--extents"),
0417               QString::number(p.length()),
0418               p.partitionPath()});
0419 
0420     return (cmd.run(-1) && cmd.exitCode() == 0);
0421 }
0422 
0423 bool LvmDevice::removePV(Report& report, LvmDevice& d, const QString& pvPath)
0424 {
0425     ExternalCommand cmd(report, QStringLiteral("lvm"),
0426             { QStringLiteral("vgreduce"),
0427               d.name(),
0428               pvPath});
0429 
0430     return (cmd.run(-1) && cmd.exitCode() == 0);
0431 }
0432 
0433 bool LvmDevice::insertPV(Report& report, LvmDevice& d, const QString& pvPath)
0434 {
0435     ExternalCommand cmd(report, QStringLiteral("lvm"),
0436             { QStringLiteral("vgextend"),
0437               QStringLiteral("--yes"),
0438               d.name(),
0439               pvPath});
0440 
0441     return (cmd.run(-1) && cmd.exitCode() == 0);
0442 }
0443 
0444 bool LvmDevice::movePV(Report& report, const QString& pvPath, const QStringList& destinations)
0445 {
0446     if (FS::lvm2_pv::getAllocatedPE(pvPath) <= 0)
0447         return true;
0448 
0449     QStringList args = { QStringLiteral("pvmove") };
0450     args << pvPath;
0451     if (!destinations.isEmpty())
0452         for (const auto &destPath : destinations)
0453             args << destPath.trimmed();
0454 
0455     ExternalCommand cmd(report, QStringLiteral("lvm"), args);
0456     return (cmd.run(-1) && cmd.exitCode() == 0);
0457 }
0458 
0459 bool LvmDevice::createVG(Report& report, const QString vgName, const QVector<const Partition*>& pvList, const qint32 peSize)
0460 {
0461     QStringList args = { QStringLiteral("vgcreate"), QStringLiteral("--physicalextentsize"), QString::number(peSize) };
0462     args << vgName;
0463     for (const auto &p : pvList) {
0464         if (p->roles().has(PartitionRole::Luks))
0465             args << static_cast<const FS::luks*>(&p->fileSystem())->mapperName();
0466         else
0467             args << p->partitionPath();
0468     }
0469 
0470     ExternalCommand cmd(report, QStringLiteral("lvm"), args);
0471 
0472     return (cmd.run(-1) && cmd.exitCode() == 0);
0473 }
0474 
0475 bool LvmDevice::removeVG(Report& report, LvmDevice& d)
0476 {
0477     bool deactivated = deactivateVG(report, d);
0478     ExternalCommand cmd(report, QStringLiteral("lvm"),
0479             { QStringLiteral("vgremove"),
0480               QStringLiteral("--force"),
0481               d.name() });
0482     return (deactivated && cmd.run(-1) && cmd.exitCode() == 0);
0483 }
0484 
0485 bool LvmDevice::deactivateVG(Report& report, const LvmDevice& d)
0486 {
0487     ExternalCommand deactivate(report, QStringLiteral("lvm"),
0488             { QStringLiteral("vgchange"),
0489               QStringLiteral("--activate"), QStringLiteral("n"),
0490               d.name() });
0491     return deactivate.run(-1) && deactivate.exitCode() == 0;
0492 }
0493 
0494 bool LvmDevice::deactivateLV(Report& report, const Partition& p)
0495 {
0496     ExternalCommand deactivate(report, QStringLiteral("lvm"),
0497             { QStringLiteral("lvchange"),
0498               QStringLiteral("--activate"), QStringLiteral("n"),
0499               p.partitionPath() });
0500     return deactivate.run(-1) && deactivate.exitCode() == 0;
0501 }
0502 
0503 bool LvmDevice::activateVG(Report& report, const LvmDevice& d)
0504 {
0505     ExternalCommand deactivate(report, QStringLiteral("lvm"),
0506             { QStringLiteral("vgchange"),
0507               QStringLiteral("--activate"), QStringLiteral("y"),
0508               d.name() });
0509     return deactivate.run(-1) && deactivate.exitCode() == 0;
0510 }
0511 
0512 bool LvmDevice::activateLV(const QString& lvPath)
0513 {
0514     ExternalCommand deactivate(QStringLiteral("lvm"),
0515             { QStringLiteral("lvchange"),
0516               QStringLiteral("--activate"), QStringLiteral("y"),
0517               lvPath });
0518     return deactivate.run(-1) && deactivate.exitCode() == 0;
0519 }
0520 
0521 qint64 LvmDevice::peSize() const
0522 {
0523     return d_ptr->m_peSize;
0524 }
0525 
0526 qint64 LvmDevice::totalPE() const
0527 {
0528     return d_ptr->m_totalPE;
0529 }
0530 
0531 qint64 LvmDevice::allocatedPE() const
0532 {
0533     return d_ptr->m_allocPE;
0534 }
0535 
0536 qint64 LvmDevice::freePE() const
0537 {
0538     return d_ptr->m_freePE;
0539 }
0540 
0541 void LvmDevice::setFreePE(qint64 freePE) const
0542 {
0543     d_ptr->m_freePE = freePE;
0544     d_ptr->m_allocPE = d_ptr->m_totalPE - freePE;
0545 }
0546 
0547 QString LvmDevice::UUID() const
0548 {
0549     return d_ptr->m_UUID;
0550 }
0551 
0552 QVector <const Partition*>& LvmDevice::physicalVolumes()
0553 {
0554     return d_ptr->m_PVs;
0555 }
0556 
0557 const QVector <const Partition*>& LvmDevice::physicalVolumes() const
0558 {
0559     return d_ptr->m_PVs;
0560 }
0561 
0562 std::unique_ptr<QHash<QString, qint64>>& LvmDevice::LVSizeMap() const
0563 {
0564     return d_ptr->m_LVSizeMap;
0565 }