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

0001 /*
0002     SPDX-FileCopyrightText: 2012-2018 Andrius Štikonas <andrius@stikonas.eu>
0003     SPDX-FileCopyrightText: 2016 Chantara Tith <tith.chantara@gmail.com>
0004     SPDX-FileCopyrightText: 2016 Teo Mrnjavac <teo@kde.org>
0005     SPDX-FileCopyrightText: 2018 Caio Jordão Carvalho <caiojcarvalho@gmail.com>
0006     SPDX-FileCopyrightText: 2019 Yuri Chornoivan <yurchor@ukr.net>
0007     SPDX-FileCopyrightText: 2020 Arnaud Ferraris <arnaud.ferraris@collabora.com>
0008     SPDX-FileCopyrightText: 2020 Gaël PORTAY <gael.portay@collabora.com>
0009 
0010     SPDX-License-Identifier: GPL-3.0-or-later
0011 */
0012 
0013 #include "fs/lvm2_pv.h"
0014 #include "core/device.h"
0015 
0016 #include "util/externalcommand.h"
0017 #include "util/capacity.h"
0018 
0019 #include <QString>
0020 
0021 #include <KLocalizedString>
0022 
0023 namespace FS
0024 {
0025 FileSystem::CommandSupportType lvm2_pv::m_GetUsed = FileSystem::cmdSupportNone;
0026 FileSystem::CommandSupportType lvm2_pv::m_GetLabel = FileSystem::cmdSupportNone;
0027 FileSystem::CommandSupportType lvm2_pv::m_Create = FileSystem::cmdSupportNone;
0028 FileSystem::CommandSupportType lvm2_pv::m_Grow = FileSystem::cmdSupportNone;
0029 FileSystem::CommandSupportType lvm2_pv::m_Shrink = FileSystem::cmdSupportNone;
0030 FileSystem::CommandSupportType lvm2_pv::m_Move = FileSystem::cmdSupportNone;
0031 FileSystem::CommandSupportType lvm2_pv::m_Check = FileSystem::cmdSupportNone;
0032 FileSystem::CommandSupportType lvm2_pv::m_Copy = FileSystem::cmdSupportNone;
0033 FileSystem::CommandSupportType lvm2_pv::m_Backup = FileSystem::cmdSupportNone;
0034 FileSystem::CommandSupportType lvm2_pv::m_SetLabel = FileSystem::cmdSupportNone;
0035 FileSystem::CommandSupportType lvm2_pv::m_UpdateUUID = FileSystem::cmdSupportNone;
0036 FileSystem::CommandSupportType lvm2_pv::m_GetUUID = FileSystem::cmdSupportNone;
0037 
0038 lvm2_pv::lvm2_pv(qint64 firstsector, qint64 lastsector,
0039                  qint64 sectorsused, const QString& label, const QVariantMap& features)
0040     : FileSystem(firstsector, lastsector, sectorsused, label, features, FileSystem::Type::Lvm2_PV)
0041     , m_PESize(0)
0042     , m_TotalPE(0)
0043     , m_AllocatedPE(0)
0044 {
0045 }
0046 
0047 void lvm2_pv::init()
0048 {
0049     CommandSupportType lvmFound = findExternal(QStringLiteral("lvm"), {}, 3) ? cmdSupportFileSystem : cmdSupportNone;
0050 
0051     m_Create     = lvmFound;
0052     m_Check      = lvmFound;
0053     m_Grow       = lvmFound;
0054     m_Shrink     = lvmFound;
0055     m_UpdateUUID = lvmFound;
0056     m_GetUsed    = lvmFound;
0057 
0058     m_Move = (m_Check != cmdSupportNone) ? cmdSupportCore : cmdSupportNone;
0059 
0060     m_GetLabel = cmdSupportCore;
0061     m_Backup   = cmdSupportCore;
0062     m_GetUUID  = cmdSupportCore;
0063 
0064     m_GetLabel = cmdSupportNone;
0065     m_Copy     = cmdSupportNone; // Copying PV can confuse LVM
0066 }
0067 
0068 void lvm2_pv::scan(const QString& deviceNode)
0069 {
0070     getPESize(deviceNode);
0071     m_AllocatedPE = getAllocatedPE(deviceNode);
0072     m_TotalPE = getTotalPE(deviceNode);
0073 }
0074 
0075 bool lvm2_pv::supportToolFound() const
0076 {
0077     return
0078         m_GetUsed != cmdSupportNone &&
0079         m_Create != cmdSupportNone &&
0080         m_Check != cmdSupportNone &&
0081         m_UpdateUUID != cmdSupportNone &&
0082         m_Grow != cmdSupportNone &&
0083         m_Shrink != cmdSupportNone &&
0084         m_Move != cmdSupportNone &&
0085         m_Backup != cmdSupportNone &&
0086         m_GetUUID != cmdSupportNone;
0087 }
0088 
0089 FileSystem::SupportTool lvm2_pv::supportToolName() const
0090 {
0091     return SupportTool(QStringLiteral("lvm2"), QUrl(QStringLiteral("https://sourceware.org/lvm2/")));
0092 }
0093 
0094 qint64 lvm2_pv::maxCapacity() const
0095 {
0096     return Capacity::unitFactor(Capacity::Unit::Byte, Capacity::Unit::EiB);
0097 }
0098 
0099 qint64 lvm2_pv::readUsedCapacity(const QString& deviceNode) const
0100 {
0101     QString pvUsed = getpvField(QStringLiteral("pv_used"), deviceNode);
0102     QString metadataOffset = getpvField(QStringLiteral("pe_start"), deviceNode);
0103     return pvUsed.isEmpty() ? -1 : pvUsed.toLongLong() + metadataOffset.toLongLong();
0104 }
0105 
0106 bool lvm2_pv::check(Report& report, const QString& deviceNode) const
0107 {
0108     ExternalCommand cmd(report, QStringLiteral("lvm"), { QStringLiteral("pvck"), QStringLiteral("--verbose"), deviceNode });
0109     return cmd.run(-1) && cmd.exitCode() == 0;
0110 }
0111 
0112 bool lvm2_pv::create(Report& report, const QString& deviceNode)
0113 {
0114     ExternalCommand cmd(report, QStringLiteral("lvm"), { QStringLiteral("pvcreate"), QStringLiteral("--force"), deviceNode });
0115     return cmd.run(-1) && cmd.exitCode() == 0;
0116 }
0117 
0118 bool lvm2_pv::remove(Report& report, const QString& deviceNode) const
0119 {
0120     ExternalCommand cmd(report, QStringLiteral("lvm"), { QStringLiteral("pvremove"), QStringLiteral("--force"), QStringLiteral("--force"), QStringLiteral("--yes"), deviceNode });
0121     return cmd.run(-1) && cmd.exitCode() == 0;
0122 }
0123 
0124 bool lvm2_pv::resize(Report& report, const QString& deviceNode, qint64 length) const
0125 {
0126     bool rval = true;
0127 
0128     qint64 metadataOffset = getpvField(QStringLiteral("pe_start"), deviceNode).toLongLong();
0129 
0130     qint64 lastPE = getTotalPE(deviceNode) - 1; // starts from 0
0131     if (lastPE > 0) { // make sure that the PV is already in a VG
0132         qint64 targetPE = (length - metadataOffset) / peSize() - 1; // starts from 0
0133         if (targetPE < lastPE) { //shrinking FS
0134             qint64 firstMovedPE = qMax(targetPE + 1, getAllocatedPE(deviceNode)); // starts from 1
0135             ExternalCommand moveCmd(report,
0136                                     QStringLiteral("lvm"), {
0137                                     QStringLiteral("pvmove"),
0138                                     QStringLiteral("--alloc"),
0139                                     QStringLiteral("anywhere"),
0140                                     deviceNode + QStringLiteral(":") + QString::number(firstMovedPE) + QStringLiteral("-") + QString::number(lastPE),
0141                                     deviceNode + QStringLiteral(":") + QStringLiteral("0-") + QString::number(firstMovedPE - 1)
0142                                     });
0143             rval = moveCmd.run(-1) && (moveCmd.exitCode() == 0 || moveCmd.exitCode() == 5); // FIXME: exit code 5: NO data to move
0144         }
0145     }
0146 
0147     ExternalCommand cmd(report, QStringLiteral("lvm"), {
0148                                 QStringLiteral("pvresize"),
0149                                 QStringLiteral("--yes"),
0150                                 QStringLiteral("--setphysicalvolumesize"),
0151                                 QString::number(length) + QStringLiteral("B"),
0152                                 deviceNode });
0153     return rval && cmd.run(-1) && cmd.exitCode() == 0;
0154 }
0155 
0156 bool lvm2_pv::resizeOnline(Report& report, const QString& deviceNode, const QString& mountPoint, qint64 length) const
0157 {
0158     Q_UNUSED(mountPoint)
0159     return resize(report, deviceNode, length);
0160 }
0161 
0162 bool lvm2_pv::updateUUID(Report& report, const QString& deviceNode) const
0163 {
0164     ExternalCommand cmd(report, QStringLiteral("lvm"), { QStringLiteral("pvchange"), QStringLiteral("--uuid"), deviceNode });
0165     return cmd.run(-1) && cmd.exitCode() == 0;
0166 }
0167 
0168 QString lvm2_pv::readUUID(const QString& deviceNode) const
0169 {
0170     return getpvField(QStringLiteral("pv_uuid"), deviceNode);
0171 }
0172 
0173 bool lvm2_pv::mount(Report& report, const QString& deviceNode, const QString& mountPoint)
0174 {
0175     Q_UNUSED(report)
0176     Q_UNUSED(deviceNode)
0177     Q_UNUSED(mountPoint)
0178     return false;
0179 }
0180 
0181 bool lvm2_pv::unmount(Report& report, const QString& deviceNode)
0182 {
0183     Q_UNUSED(deviceNode)
0184     Q_UNUSED(report)
0185     return false;
0186 }
0187 
0188 bool lvm2_pv::canMount(const QString& deviceNode, const QString& mountPoint) const
0189 {
0190     Q_UNUSED(deviceNode)
0191     Q_UNUSED(mountPoint)
0192     return false;
0193 }
0194 
0195 bool lvm2_pv::canUnmount(const QString& deviceNode) const
0196 {
0197     Q_UNUSED(deviceNode)
0198     return false;
0199 }
0200 
0201 qint64 lvm2_pv::getTotalPE(const QString& deviceNode)
0202 {
0203     QString pvPeCount = getpvField(QStringLiteral("pv_pe_count"), deviceNode);
0204     return pvPeCount.isEmpty() ? -1 : pvPeCount.toLongLong();
0205 }
0206 
0207 qint64 lvm2_pv::getAllocatedPE(const QString& deviceNode)
0208 {
0209     QString pvPeAllocCount = getpvField(QStringLiteral("pv_pe_alloc_count"), deviceNode);
0210     return pvPeAllocCount.isEmpty() ? -1 : pvPeAllocCount.toLongLong();
0211 }
0212 
0213 void lvm2_pv::getPESize(const QString& deviceNode)
0214 {
0215     QString vgExtentSize = getpvField(QStringLiteral("vg_extent_size"), deviceNode);
0216     m_PESize = vgExtentSize.isEmpty() ? -1 : vgExtentSize.toLongLong();
0217 }
0218 
0219 /** Get pvs command output with field name
0220  *
0221  *  @param fieldName LVM field name
0222  *  @param deviceNode path to PV
0223  *  @return raw output of pvs command, usually with many spaces
0224  */
0225 QString  lvm2_pv::getpvField(const QString& fieldName, const QString& deviceNode)
0226 {
0227     QStringList args = { QStringLiteral("pvs"),
0228                     QStringLiteral("--foreign"),
0229                     QStringLiteral("--readonly"),
0230                     QStringLiteral("--noheadings"),
0231                     QStringLiteral("--units"),
0232                     QStringLiteral("B"),
0233                     QStringLiteral("--nosuffix"),
0234                     QStringLiteral("--options"),
0235                     fieldName };
0236     if (!deviceNode.isEmpty()) {
0237         args << deviceNode;
0238     }
0239     ExternalCommand cmd(QStringLiteral("lvm"), args, QProcess::ProcessChannelMode::SeparateChannels);
0240     if (cmd.run(-1) && cmd.exitCode() == 0) {
0241         return cmd.output().trimmed();
0242     }
0243     return QString();
0244 }
0245 
0246 QString lvm2_pv::getVGName(const QString& deviceNode)
0247 {
0248     return getpvField(QStringLiteral("vg_name"), deviceNode);
0249 }
0250 
0251 QList<LvmPV> lvm2_pv::getPVinNode(const PartitionNode* parent)
0252 {
0253     QList<LvmPV> partitions;
0254     if (parent == nullptr)
0255         return partitions;
0256 
0257     for (const auto &node : parent->children()) {
0258         const Partition* p = dynamic_cast<const Partition*>(node);
0259 
0260         if (p == nullptr)
0261             continue;
0262 
0263         if (node->children().size() > 0)
0264             partitions.append(getPVinNode(node));
0265 
0266         // FIXME: reenable newly created PVs (before applying) once everything works
0267         if(p->fileSystem().type() == FileSystem::Type::Lvm2_PV && p->deviceNode() == p->partitionPath())
0268             partitions.append(LvmPV(p->mountPoint(), p));
0269 
0270         if(p->fileSystem().type() == FileSystem::Type::Luks && p->deviceNode() == p->partitionPath())
0271             partitions.append(LvmPV(p->mountPoint(), p, true));
0272     }
0273 
0274     return partitions;
0275 }
0276 
0277 /** construct a list of Partition objects for LVM PVs that are either unused or belong to some VG.
0278  *
0279  *  @param devices list of Devices which we scan for LVM PVs
0280  *  @return list of LVM PVs
0281  */
0282 QList<LvmPV> lvm2_pv::getPVs(const QList<Device*>& devices)
0283 {
0284     QList<LvmPV> partitions;
0285     for (auto const &d : devices)
0286         partitions.append(getPVinNode(d->partitionTable()));
0287 
0288     return partitions;
0289 }
0290 
0291 }
0292 
0293 namespace LVM {
0294 
0295 QList<LvmPV> pvList::m_list;
0296 
0297 }
0298 
0299 LvmPV::LvmPV(const QString vgName, const Partition* p, bool isLuks)
0300     : m_vgName(vgName)
0301     , m_p(p)
0302     , m_isLuks(isLuks)
0303 {
0304 }