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 }