File indexing completed on 2024-05-12 05:48:28

0001 /*
0002     SPDX-FileCopyrightText: 2017-2020 Andrius Štikonas <andrius@stikonas.eu>
0003     SPDX-FileCopyrightText: 2018-2019 Caio Jordão Carvalho <caiojcarvalho@gmail.com>
0004     SPDX-FileCopyrightText: 2019 Shubham Jangra <aryan100jangid@gmail.com>
0005     SPDX-FileCopyrightText: 2020 Gaël PORTAY <gael.portay@collabora.com>
0006     SPDX-FileCopyrightText: 2020 Adriaan de Groot <groot@kde.org>
0007 
0008     SPDX-License-Identifier: GPL-3.0-or-later
0009 */
0010 
0011 /** @file
0012 */
0013 
0014 #include "plugins/sfdisk/sfdiskbackend.h"
0015 #include "plugins/sfdisk/sfdiskdevice.h"
0016 #include "plugins/sfdisk/sfdiskgptattributes.h"
0017 
0018 #include "core/copysourcedevice.h"
0019 #include "core/copytargetbytearray.h"
0020 #include "core/diskdevice.h"
0021 #include "core/lvmdevice.h"
0022 #include "core/partitiontable.h"
0023 #include "core/partitionalignment.h"
0024 #include "core/raid/softwareraid.h"
0025 
0026 #include "fs/filesystemfactory.h"
0027 #include "fs/luks.h"
0028 #include "fs/luks2.h"
0029 
0030 #include "util/globallog.h"
0031 #include "util/externalcommand.h"
0032 #include "util/helpers.h"
0033 
0034 #include <utility>
0035 
0036 #include <QDataStream>
0037 #include <QDebug>
0038 #include <QFile>
0039 #include <QJsonArray>
0040 #include <QJsonDocument>
0041 #include <QJsonObject>
0042 #include <QRegularExpression>
0043 #include <QStorageInfo>
0044 #include <QString>
0045 #include <QStringList>
0046 
0047 #include <KLocalizedString>
0048 #include <KPluginFactory>
0049 
0050 K_PLUGIN_CLASS_WITH_JSON(SfdiskBackend, "pmsfdiskbackendplugin.json")
0051 
0052 SfdiskBackend::SfdiskBackend(QObject*, const QList<QVariant>&) :
0053     CoreBackend()
0054 {
0055 }
0056 
0057 void SfdiskBackend::initFSSupport()
0058 {
0059 }
0060 
0061 QList<Device*> SfdiskBackend::scanDevices(bool excludeReadOnly)
0062 {
0063     return scanDevices(excludeReadOnly ? ScanFlags() : ScanFlag::includeReadOnly);
0064 }
0065 
0066 QList<Device*> SfdiskBackend::scanDevices(const ScanFlags scanFlags)
0067 {
0068     const bool includeReadOnly = scanFlags.testFlag(ScanFlag::includeReadOnly);
0069     const bool includeLoopback = scanFlags.testFlag(ScanFlag::includeLoopback);
0070 
0071     QList<Device*> result;
0072     QStringList deviceNodes;
0073 
0074     ExternalCommand cmd(QStringLiteral("lsblk"),
0075                         { QStringLiteral("--nodeps"),
0076                           QStringLiteral("--paths"),
0077                           QStringLiteral("--sort"), QStringLiteral("name"),
0078                           QStringLiteral("--json"),
0079                           QStringLiteral("--output"),
0080                           QStringLiteral("type,name") });
0081 
0082     if (cmd.run(-1) && cmd.exitCode() == 0) {
0083         const QJsonDocument jsonDocument = QJsonDocument::fromJson(cmd.rawOutput());
0084         const QJsonObject jsonObject = jsonDocument.object();
0085         const QJsonArray jsonArray = jsonObject[QLatin1String("blockdevices")].toArray();
0086         for (const auto &deviceLine : jsonArray) {
0087             QJsonObject deviceObject = deviceLine.toObject();
0088             if (! (deviceObject[QLatin1String("type")].toString() == QLatin1String("disk")
0089                 || (includeLoopback && deviceObject[QLatin1String("type")].toString() == QLatin1String("loop")) ))
0090             {
0091                 continue;
0092             }
0093 
0094             const QString deviceNode = deviceObject[QLatin1String("name")].toString();
0095             if (!includeReadOnly) {
0096                 QString deviceName = deviceNode;
0097                 deviceName.remove(QStringLiteral("/dev/"));
0098                 QFile f(QStringLiteral("/sys/block/%1/ro").arg(deviceName));
0099                 if (f.open(QIODevice::ReadOnly))
0100                     if (f.readLine().trimmed().toInt() == 1)
0101                         continue;
0102             }
0103             deviceNodes << deviceNode;
0104         }
0105 
0106         int totalDevices = deviceNodes.length();
0107         for (int i = 0; i < totalDevices; ++i) {
0108             const QString deviceNode = deviceNodes[i];
0109 
0110             emitScanProgress(deviceNode, i * 100 / totalDevices);
0111             Device* device = scanDevice(deviceNode);
0112             if (device != nullptr) {
0113                 result.append(device);
0114             }
0115         }
0116         
0117     }
0118 
0119     VolumeManagerDevice::scanDevices(result); // scan all types of VolumeManagerDevices
0120 
0121     return result;
0122 }
0123 
0124 /*** @brief Fix up bogus JSON from `sfdisk --json /dev/sdb`
0125  *
0126  * The command `sfdisk --json /dev/sdb` outputs a JSON representation
0127  * of the partition table, with general device characteristics and
0128  * the list of partitions, **but**..
0129  *
0130  * This isn't necessarily valid JSON: in particular, when there are
0131  * no partitions on the disk because it is empty / was recently zeroed /
0132  * is a USB stick for testing purposes, the output is changed **only**
0133  * by there being no partitions in the partition table. However,
0134  * the comma (",") after sectorsize is still printed. Bogus output looks
0135  * like this:
0136  *
0137  * {
0138  *   "partitiontable": {
0139  *       "label":"gpt",
0140  *       "id":"1F9E80D9-DD78-024F-94A3-B61EC82B18C8",
0141  *       "device":"/dev/sdb",
0142  *       "unit":"sectors",
0143  *       "firstlba":2048,
0144  *       "lastlba":30949342,
0145  *       "sectorsize":512,
0146  *   }
0147  * }
0148  *
0149  * That's not valid JSON because of the "," followed by nothing until
0150  * the brace, and yields an empty object is passed to fromJson().
0151  *
0152  * We'll go through and check if there's a "," followed by whitespace
0153  * and then a }. If there is, replace the ,.
0154  *
0155  * This is also fixed in util-linux 2.37.
0156  *
0157  * For some partition tables sfdisk prints an error message before the actual json
0158  * starts (seen with sfdisk 2.37.4). It looks like this:
0159  *
0160  * omitting empty partition (5)
0161  *  {
0162  *  "partitiontable": {
0163  *     "label": "dos",
0164  *     "id": "0x91769176",
0165  *     "device": "/dev/sdb",
0166  *     "unit": "sectors",
0167  *     "sectorsize": 512,
0168  *     "partitions": [
0169  *        {
0170  *           "node": "/dev/sdb1",
0171  *           "start": 63,
0172  *           "size": 84630357,
0173  *           "type": "7",
0174  *           "bootable": true
0175  *        },{
0176  * etc.
0177  */
0178 static void
0179 fixInvalidJsonFromSFDisk( QByteArray& s )
0180 {
0181     int jsonStart = s.indexOf('{');
0182     if (jsonStart != 0) {
0183         const QByteArray invalidStart = s.left(jsonStart);
0184         qDebug() << "removed \"" << invalidStart.data() << "\" from beginning of sfdisk json output";
0185         const QByteArray copy = s.mid(jsonStart);
0186         s = copy;
0187     }
0188 
0189     // -1 if there is no comma (but then there's no useful JSON either),
0190     //    not is 0 a valid place (the start) for a , in a JSON document.
0191     int lastComma = s.lastIndexOf(',');
0192     if ( lastComma > 0 )
0193     {
0194         for ( int charIndex = lastComma + 1; charIndex < s.length(); ++charIndex )
0195         {
0196             if ( s[charIndex] == '}' )
0197             {
0198                 s[lastComma] = ' ';  // Erase that comma
0199             }
0200             if ( !isspace( s[charIndex] ) )
0201             {
0202                 break;
0203             }
0204         }
0205     }
0206 }
0207 
0208 /** Create a Device for the given device_node and scan it for partitions.
0209     @param deviceNode the device node (e.g. "/dev/sda")
0210     @return the created Device object. callers need to free this.
0211 */
0212 Device* SfdiskBackend::scanDevice(const QString& deviceNode)
0213 {
0214     ExternalCommand modelCommand(QStringLiteral("lsblk"),
0215                         { QStringLiteral("--nodeps"),
0216                           QStringLiteral("--noheadings"),
0217                           QStringLiteral("--output"), QStringLiteral("model"),
0218                           deviceNode });
0219     ExternalCommand sizeCommand(QStringLiteral("blockdev"), { QStringLiteral("--getsize64"), deviceNode });
0220     ExternalCommand sizeCommand2(QStringLiteral("blockdev"), { QStringLiteral("--getss"), deviceNode });
0221     ExternalCommand sfdiskJsonCommand(QStringLiteral("sfdisk"), { QStringLiteral("--json"), deviceNode }, QProcess::ProcessChannelMode::SeparateChannels );
0222 
0223     if ( sizeCommand.run(-1) && sizeCommand.exitCode() == 0
0224          && sizeCommand2.run(-1) && sizeCommand2.exitCode() == 0
0225          && sfdiskJsonCommand.run(-1) )
0226     {
0227         Device* d = nullptr;
0228         qint64 deviceSize = sizeCommand.output().trimmed().toLongLong();
0229         int logicalSectorSize = sizeCommand2.output().trimmed().toLongLong();
0230 
0231         QFile mdstat(QStringLiteral("/proc/mdstat"));
0232 
0233         if (mdstat.open(QIODevice::ReadOnly)) {
0234             QTextStream stream(&mdstat);
0235 
0236             QString content = stream.readAll();
0237 
0238             mdstat.close();
0239 
0240             QRegularExpression re(QStringLiteral("md([\\/\\w]+)\\s+:"));
0241             QRegularExpressionMatchIterator i  = re.globalMatch(content);
0242 
0243             while (i.hasNext()) {
0244                 QRegularExpressionMatch reMatch = i.next();
0245                 QString name = reMatch.captured(1);
0246 
0247                 if ((QStringLiteral("/dev/md") + name) == deviceNode) {
0248                     Log(Log::Level::information) << xi18nc("@info:status", "Software RAID Device found: %1", deviceNode);
0249                     d = new SoftwareRAID( QStringLiteral("md") + name, SoftwareRAID::Status::Active );
0250                     break;
0251                 }
0252             }
0253         }
0254 
0255         if ( d == nullptr && modelCommand.run(-1) && modelCommand.exitCode() == 0 )
0256         {
0257             QString name = modelCommand.output();
0258             name = name.left(name.length() - 1).replace(QLatin1Char('_'), QLatin1Char(' '));
0259 
0260             if (name.trimmed().isEmpty()) {
0261                 // Get 'lsblk --output kname' in the cases where the model name is not available.
0262                 // As lsblk doesn't have an option to include a separator in its output, it is
0263                 // necessary to run it again getting only the kname as output.
0264                 ExternalCommand kname(QStringLiteral("lsblk"), {QStringLiteral("--nodeps"), QStringLiteral("--noheadings"), QStringLiteral("--output"), QStringLiteral("kname"),
0265                                                                 deviceNode});
0266 
0267                 if (kname.run(-1) && kname.exitCode() == 0)
0268                     name = kname.output().trimmed();
0269             }
0270 
0271             ExternalCommand transport(QStringLiteral("lsblk"), {QStringLiteral("--nodeps"), QStringLiteral("--noheadings"), QStringLiteral("--output"), QStringLiteral("tran"),
0272                                                                 deviceNode});
0273             QString icon;
0274             if (transport.run(-1) && transport.exitCode() == 0)
0275                 if (transport.output().trimmed() == QStringLiteral("usb"))
0276                     icon = QStringLiteral("drive-removable-media-usb");
0277 
0278             Log(Log::Level::information) << xi18nc("@info:status", "Device found: %1", name);
0279 
0280             d = new DiskDevice(name, deviceNode, 255, 63, deviceSize / logicalSectorSize / 255 / 63, logicalSectorSize, icon);
0281         }
0282 
0283         if ( d )
0284         {
0285             if (sfdiskJsonCommand.exitCode() != 0) {
0286                 scanWholeDevicePartition(*d);
0287                 return d;
0288             }
0289 
0290             auto s = sfdiskJsonCommand.rawOutput();
0291             fixInvalidJsonFromSFDisk(s);
0292 
0293             const QJsonObject jsonObject = QJsonDocument::fromJson(s).object();
0294             const QJsonObject partitionTable = jsonObject[QLatin1String("partitiontable")].toObject();
0295 
0296             if (jsonObject.isEmpty()) {
0297                 qDebug() << "json object created from sfdisk output is empty !\nOutput is \"" << s.data() << "\"";
0298             }
0299 
0300             /* Workaround for whole device FAT partitions */
0301             if(partitionTable[QLatin1String("label")].toString() == QStringLiteral("dos") &&
0302                partitionTable[QLatin1String("id")].toInt() == 0) {
0303                 scanWholeDevicePartition(*d);
0304                 return d;
0305             }
0306 
0307             if (!updateDevicePartitionTable(*d, partitionTable))
0308                 return nullptr;
0309 
0310             return d;
0311         }
0312     }
0313     else
0314     {
0315         // Look if this device is a LVM VG
0316         ExternalCommand checkVG(QStringLiteral("lvm"), { QStringLiteral("vgdisplay"), deviceNode });
0317 
0318         if (checkVG.run(-1) && checkVG.exitCode() == 0)
0319         {
0320             QList<Device *> availableDevices = scanDevices();
0321 
0322             for (Device *device : std::as_const(availableDevices))
0323                 if (device->deviceNode() == deviceNode)
0324                     return device;
0325         }
0326     }
0327     return nullptr;
0328 }
0329 
0330 /** Scans a Device for FileSystems spanning the whole block device
0331 
0332     This method  will scan a Device for a FileSystem.
0333     It tries to determine the FileSystem usage, reads the FileSystem label and creates
0334     PartitionTable of type "none" and a single Partition object.
0335 */
0336 void SfdiskBackend::scanWholeDevicePartition(Device& d) {
0337     const QString partitionNode = d.deviceNode();
0338     constexpr qint64 firstSector = 0;
0339     const qint64 lastSector = d.totalLogical() - 1;
0340     setPartitionTableForDevice(d, new PartitionTable(PartitionTable::TableType::none, firstSector, lastSector));
0341     Partition *partition = scanPartition(d, partitionNode, firstSector, lastSector, QString(), false);
0342 
0343     if (partition->fileSystem().type() == FileSystem::Type::Unknown) {
0344         setPartitionTableForDevice(d, nullptr);
0345         delete d.partitionTable();
0346     }
0347 
0348     if (!partition->roles().has(PartitionRole::Luks))
0349         readSectorsUsed(d, *partition, partition->mountPoint());
0350 }
0351 
0352 /** Scans a Device for Partitions.
0353 
0354     This method  will scan a Device for all Partitions on it, detect the FileSystem for each Partition,
0355     try to determine the FileSystem usage, read the FileSystem label and store it all in newly created
0356     objects that are in the end added to the Device's PartitionTable.
0357 */
0358 void SfdiskBackend::scanDevicePartitions(Device& d, const QJsonArray& jsonPartitions)
0359 {
0360     Q_ASSERT(d.partitionTable());
0361 
0362     QList<Partition*> partitions;
0363     for (const auto &partition : jsonPartitions) {
0364         const QJsonObject partitionObject = partition.toObject();
0365         const QString partitionNode = partitionObject[QLatin1String("node")].toString();
0366         const qint64 start = partitionObject[QLatin1String("start")].toVariant().toLongLong();
0367         const qint64 size = partitionObject[QLatin1String("size")].toVariant().toLongLong();
0368         const QString partitionType = partitionObject[QLatin1String("type")].toString();
0369         const bool bootable = partitionObject[QLatin1String("bootable")].toBool();
0370         const auto lastSector = start + size - 1;
0371 
0372         Partition* part = scanPartition(d, partitionNode, start, lastSector, partitionType, bootable);
0373 
0374         setupPartitionInfo(d, part, partitionObject);
0375 
0376         partitions.append(part);
0377     }
0378 
0379     d.partitionTable()->updateUnallocated(d);
0380 
0381     if (d.partitionTable()->isSectorBased(d))
0382         d.partitionTable()->setType(d, PartitionTable::msdos_sectorbased);
0383 
0384     for (const Partition *part : std::as_const(partitions))
0385         PartitionAlignment::isAligned(d, *part);
0386 }
0387 
0388 Partition* SfdiskBackend::scanPartition(Device& d, const QString& partitionNode, const qint64 firstSector, const qint64 lastSector, const QString& partitionType, const bool bootable)
0389 {
0390     PartitionTable::Flags activeFlags = bootable ? PartitionTable::Flag::Boot : PartitionTable::Flag::None;
0391     if (partitionType == QStringLiteral("C12A7328-F81F-11D2-BA4B-00A0C93EC93B"))
0392         activeFlags |= PartitionTable::Flag::Boot;
0393     else if (partitionType == QStringLiteral("21686148-6449-6E6F-744E-656564454649"))
0394         activeFlags |= PartitionTable::Flag::BiosGrub;
0395 
0396     FileSystem::Type type = detectFileSystem(partitionNode);
0397     PartitionRole::Roles r = PartitionRole::Primary;
0398 
0399     if ( (d.partitionTable()->type() == PartitionTable::msdos || d.partitionTable()->type() == PartitionTable::msdos_sectorbased) &&
0400         ( partitionType == QStringLiteral("5") || partitionType == QStringLiteral("f") ) ) {
0401         r = PartitionRole::Extended;
0402         type = FileSystem::Type::Extended;
0403     }
0404 
0405     // Find an extended partition this partition is in.
0406     PartitionNode* parent = d.partitionTable()->findPartitionBySector(firstSector, PartitionRole(PartitionRole::Extended));
0407 
0408     // None found, so it's a primary in the device's partition table.
0409     if (parent == nullptr)
0410         parent = d.partitionTable();
0411     else
0412         r = PartitionRole::Logical;
0413 
0414     FileSystem* fs = FileSystemFactory::create(type, firstSector, lastSector, d.logicalSize());
0415     fs->scan(partitionNode);
0416 
0417     QString mountPoint;
0418     bool mounted;
0419     // sfdisk does not handle LUKS partitions
0420     if (fs->type() == FileSystem::Type::Luks || fs->type() == FileSystem::Type::Luks2) {
0421         r |= PartitionRole::Luks;
0422         FS::luks* luksFs = static_cast<FS::luks*>(fs);
0423         luksFs->initLUKS();
0424         QString mapperNode = luksFs->mapperName();
0425         mountPoint = FileSystem::detectMountPoint(fs, mapperNode);
0426         mounted    = FileSystem::detectMountStatus(fs, mapperNode);
0427     } else {
0428         mountPoint = FileSystem::detectMountPoint(fs, partitionNode);
0429         mounted = FileSystem::detectMountStatus(fs, partitionNode);
0430     }
0431 
0432     Partition* partition = new Partition(parent, d, PartitionRole(r), fs, firstSector, lastSector, partitionNode, availableFlags(d.partitionTable()->type()), mountPoint, mounted, activeFlags);
0433 
0434     if (fs->supportGetLabel() != FileSystem::cmdSupportNone)
0435         fs->setLabel(fs->readLabel(partition->deviceNode()));
0436 
0437     if (fs->supportGetUUID() != FileSystem::cmdSupportNone)
0438         fs->setUUID(fs->readUUID(partition->deviceNode()));
0439 
0440     parent->append(partition);
0441     return partition;
0442 }
0443 
0444 void SfdiskBackend::setupPartitionInfo(const Device &d, Partition *partition, const QJsonObject& partitionObject)
0445 {
0446     if (!partition->roles().has(PartitionRole::Luks))
0447         readSectorsUsed(d, *partition, partition->mountPoint());
0448 
0449     if (d.partitionTable()->type() == PartitionTable::TableType::gpt) {
0450         partition->setLabel(partitionObject[QLatin1String("name")].toString());
0451         partition->setUUID(partitionObject[QLatin1String("uuid")].toString());
0452         partition->setType(partitionObject[QLatin1String("type")].toString());
0453         QString attrs = partitionObject[QLatin1String("attrs")].toString();
0454         partition->setAttributes(SfdiskGptAttributes::toULongLong(attrs.split(QLatin1Char(' '))));
0455     }
0456 }
0457 
0458 bool SfdiskBackend::updateDevicePartitionTable(Device &d, const QJsonObject &jsonPartitionTable)
0459 {
0460     QString tableType = jsonPartitionTable[QLatin1String("label")].toString();
0461     const PartitionTable::TableType type = PartitionTable::nameToTableType(tableType);
0462 
0463     qint64 firstUsableSector = 0;
0464     qint64 lastUsableSector = 0;
0465 
0466     if (d.type() == Device::Type::Disk_Device) {
0467         const DiskDevice* diskDevice = static_cast<const DiskDevice*>(&d);
0468 
0469         lastUsableSector = diskDevice->totalSectors();
0470     }
0471     else if (d.type() == Device::Type::SoftwareRAID_Device) {
0472         const SoftwareRAID* raidDevice = static_cast<const SoftwareRAID*>(&d);
0473 
0474         lastUsableSector = raidDevice->totalLogical() - 1;
0475     }
0476 
0477     if (type == PartitionTable::gpt) {
0478         firstUsableSector = jsonPartitionTable[QLatin1String("firstlba")].toVariant().toLongLong();
0479         lastUsableSector = jsonPartitionTable[QLatin1String("lastlba")].toVariant().toLongLong();
0480     }
0481 
0482     if (lastUsableSector < firstUsableSector) {
0483         return false;
0484     }
0485 
0486     setPartitionTableForDevice(d, new PartitionTable(type, firstUsableSector, lastUsableSector));
0487     switch (type) {
0488     case PartitionTable::gpt:
0489     {
0490         // Read the maximum number of GPT partitions
0491         qint32 maxEntries;
0492         QByteArray gptHeader;
0493         qint64 sectorSize = d.logicalSize();
0494         CopySourceDevice source(d, sectorSize, sectorSize * 2 - 1);
0495 
0496         ExternalCommand readCmd;
0497         gptHeader = readCmd.readData(source);
0498         if (gptHeader != QByteArray()) {
0499             QByteArray gptMaxEntries = gptHeader.mid(80, 4);
0500             QDataStream stream(&gptMaxEntries, QIODevice::ReadOnly);
0501             stream.setByteOrder(QDataStream::LittleEndian);
0502             stream >> maxEntries;
0503         }
0504         else
0505             maxEntries = 128;
0506         CoreBackend::setPartitionTableMaxPrimaries(*d.partitionTable(), maxEntries);
0507         break;
0508     }
0509     default:
0510         break;
0511     }
0512 
0513     scanDevicePartitions(d, jsonPartitionTable[QLatin1String("partitions")].toArray());
0514 
0515     return true;
0516 }
0517 
0518 /** Reads the sectors used in a FileSystem and stores the result in the Partition's FileSystem object.
0519     @param p the Partition the FileSystem is on
0520     @param mountPoint mount point of the partition in question
0521 */
0522 void SfdiskBackend::readSectorsUsed(const Device& d, Partition& p, const QString& mountPoint)
0523 {
0524     if (!mountPoint.isEmpty() && p.fileSystem().type() != FileSystem::Type::LinuxSwap && p.fileSystem().type() != FileSystem::Type::Lvm2_PV) {
0525         const QStorageInfo storage = QStorageInfo(mountPoint);
0526         if (p.isMounted() && storage.isValid())
0527             p.fileSystem().setSectorsUsed( (storage.bytesTotal() - storage.bytesFree()) / d.logicalSize());
0528     }
0529     else if (p.fileSystem().supportGetUsed() == FileSystem::cmdSupportFileSystem)
0530         p.fileSystem().setSectorsUsed(p.fileSystem().readUsedCapacity(p.deviceNode()) / d.logicalSize());
0531 }
0532 
0533 FileSystem::Type SfdiskBackend::detectFileSystem(const QString& partitionPath)
0534 {
0535     FileSystem::Type rval = FileSystem::Type::Unknown;
0536 
0537     ExternalCommand udevCommand(QStringLiteral("udevadm"), {
0538                                  QStringLiteral("info"),
0539                                  QStringLiteral("--query=property"),
0540                                  partitionPath });
0541 
0542     QString typeRegExp = QStringLiteral("ID_FS_TYPE=(\\w+)");
0543     QString versionRegExp = QStringLiteral("ID_FS_VERSION=(\\w+)");
0544 
0545     QString name = {};
0546 
0547     rval = runDetectFileSystemCommand(udevCommand, typeRegExp, versionRegExp, name);
0548 
0549     // Fallback to blkid which has slightly worse detection but it works on whole block device filesystems.
0550     if (rval == FileSystem::Type::Unknown) {
0551         ExternalCommand blkidCommand(QStringLiteral("blkid"), { partitionPath });
0552         typeRegExp = QStringLiteral("TYPE=\"(\\w+)\"");
0553         versionRegExp = QStringLiteral("SEC_TYPE=\"(\\w+)\"");
0554         rval = runDetectFileSystemCommand(blkidCommand, typeRegExp, versionRegExp, name);
0555     }
0556 
0557     if (rval == FileSystem::Type::Unknown) {
0558         qWarning() << "unknown file system type " << name << " on " << partitionPath;
0559     }
0560     return rval;
0561 }
0562 
0563 FileSystem::Type SfdiskBackend::runDetectFileSystemCommand(ExternalCommand& command, QString& typeRegExp, QString& versionRegExp, QString& name)
0564 {
0565     FileSystem::Type rval = FileSystem::Type::Unknown;
0566 
0567     if (command.run(-1) && command.exitCode() == 0) {
0568         QRegularExpression re(typeRegExp);
0569         QRegularExpression re2(versionRegExp);
0570         QRegularExpressionMatch reFileSystemType = re.match(command.output());
0571         QRegularExpressionMatch reFileSystemVersion = re2.match(command.output());
0572 
0573         if (reFileSystemType.hasMatch()) {
0574             name = reFileSystemType.captured(1);
0575         }
0576 
0577         QString version = {};
0578         if (reFileSystemVersion.hasMatch()) {
0579             version = reFileSystemVersion.captured(1);
0580         }
0581         rval = fileSystemNameToType(name, version);
0582     }
0583     return rval;
0584 }
0585 
0586 FileSystem::Type SfdiskBackend::fileSystemNameToType(const QString& name, const QString& version)
0587 {
0588     FileSystem::Type rval = FileSystem::Type::Unknown;
0589 
0590     if (name == QStringLiteral("ext2")) rval = FileSystem::Type::Ext2;
0591     else if (name == QStringLiteral("ext3")) rval = FileSystem::Type::Ext3;
0592     else if (name.startsWith(QStringLiteral("ext4"))) rval = FileSystem::Type::Ext4;
0593     else if (name == QStringLiteral("swap")) rval = FileSystem::Type::LinuxSwap;
0594     else if (name == QStringLiteral("ntfs")) rval = FileSystem::Type::Ntfs;
0595     else if (name == QStringLiteral("reiserfs")) rval = FileSystem::Type::ReiserFS;
0596     else if (name == QStringLiteral("reiser4")) rval = FileSystem::Type::Reiser4;
0597     else if (name == QStringLiteral("xfs")) rval = FileSystem::Type::Xfs;
0598     else if (name == QStringLiteral("jfs")) rval = FileSystem::Type::Jfs;
0599     else if (name == QStringLiteral("hfs")) rval = FileSystem::Type::Hfs;
0600     else if (name == QStringLiteral("hfsplus")) rval = FileSystem::Type::HfsPlus;
0601     else if (name == QStringLiteral("ufs")) rval = FileSystem::Type::Ufs;
0602     else if (name == QStringLiteral("vfat")) {
0603         if (version == QStringLiteral("FAT32"))
0604             rval = FileSystem::Type::Fat32;
0605         else if (version == QStringLiteral("FAT16") || version == QStringLiteral("msdos")) // blkid uses msdos for both FAT16 and FAT12
0606             rval = FileSystem::Type::Fat16;
0607         else if (version == QStringLiteral("FAT12"))
0608             rval = FileSystem::Type::Fat12;
0609     }
0610     else if (name == QStringLiteral("btrfs")) rval = FileSystem::Type::Btrfs;
0611     else if (name == QStringLiteral("ocfs2")) rval = FileSystem::Type::Ocfs2;
0612     else if (name == QStringLiteral("zfs_member")) rval = FileSystem::Type::Zfs;
0613     else if (name == QStringLiteral("hpfs")) rval = FileSystem::Type::Hpfs;
0614     else if (name == QStringLiteral("crypto_LUKS")) {
0615         if (version == QStringLiteral("1"))
0616             rval = FileSystem::Type::Luks;
0617         else if (version == QStringLiteral("2")) {
0618             rval = FileSystem::Type::Luks2;
0619         }
0620     }
0621     else if (name == QStringLiteral("exfat")) rval = FileSystem::Type::Exfat;
0622     else if (name == QStringLiteral("nilfs2")) rval = FileSystem::Type::Nilfs2;
0623     else if (name == QStringLiteral("LVM2_member")) rval = FileSystem::Type::Lvm2_PV;
0624     else if (name == QStringLiteral("f2fs")) rval = FileSystem::Type::F2fs;
0625     else if (name == QStringLiteral("udf")) rval = FileSystem::Type::Udf;
0626     else if (name == QStringLiteral("iso9660")) rval = FileSystem::Type::Iso9660;
0627     else if (name == QStringLiteral("linux_raid_member")) rval = FileSystem::Type::LinuxRaidMember;
0628     else if (name == QStringLiteral("BitLocker")) rval = FileSystem::Type::BitLocker;
0629     else if (name == QStringLiteral("apfs")) rval = FileSystem::Type::Apfs;
0630     else if (name == QStringLiteral("minix")) rval = FileSystem::Type::Minix;
0631 
0632     return rval;
0633 }
0634 
0635 // udev encodes the labels with ID_LABEL_FS_ENC which is done with
0636 // blkid_encode_string(). Within this function some 1-byte utf-8
0637 // characters not considered safe (e.g. '\' or ' ') are encoded as hex
0638 // TODO: Qt6: get a more efficient implementation from Qt
0639 static QString decodeFsEncString(const QString &str)
0640 {
0641     QString decoded;
0642     decoded.reserve(str.size());
0643     int i = 0;
0644     while (i < str.size()) {
0645         if (i <= str.size() - 4) {    // we need at least four characters \xAB
0646             if (str.at(i) == QLatin1Char('\\') &&
0647                 str.at(i+1) == QLatin1Char('x')) {
0648                 bool bOk;
0649                 const int code = str.mid(i+2, 2).toInt(&bOk, 16);
0650                 if (bOk && code >= 0x20 && code < 0x80) {
0651                     decoded += QChar(code);
0652                     i += 4;
0653                     continue;
0654                 }
0655             }
0656         }
0657         decoded += str.at(i);
0658         ++i;
0659     }
0660     return decoded;
0661 }
0662 
0663 QString SfdiskBackend::readLabel(const QString& deviceNode) const
0664 {
0665     ExternalCommand udevCommand(QStringLiteral("udevadm"), {
0666                                  QStringLiteral("info"),
0667                                  QStringLiteral("--query=property"),
0668                                  deviceNode });
0669     udevCommand.run();
0670     QRegularExpression re(QStringLiteral("ID_FS_LABEL_ENC=(.*)"));
0671     QRegularExpressionMatch reFileSystemLabel = re.match(udevCommand.output());
0672     if (reFileSystemLabel.hasMatch()) {
0673         QString escapedLabel = reFileSystemLabel.captured(1);
0674         return decodeFsEncString(escapedLabel);
0675     }
0676 
0677     return QString();
0678 }
0679 
0680 QString SfdiskBackend::readUUID(const QString& deviceNode) const
0681 {
0682     ExternalCommand udevCommand(QStringLiteral("udevadm"), {
0683                                  QStringLiteral("info"),
0684                                  QStringLiteral("--query=property"),
0685                                  deviceNode });
0686     udevCommand.run();
0687     QRegularExpression re(QStringLiteral("ID_FS_UUID=(.*)"));
0688     QRegularExpressionMatch reFileSystemUUID = re.match(udevCommand.output());
0689     if (reFileSystemUUID.hasMatch())
0690         return reFileSystemUUID.captured(1);
0691 
0692     return QString();
0693 }
0694 
0695 PartitionTable::Flags SfdiskBackend::availableFlags(PartitionTable::TableType type)
0696 {
0697     PartitionTable::Flags flags;
0698     if (type == PartitionTable::gpt) {
0699         // These are not really flags but for now keep them for compatibility
0700         // We should implement changing partition type
0701         flags = PartitionTable::Flag::BiosGrub |
0702                 PartitionTable::Flag::Boot;
0703     }
0704     else if (type == PartitionTable::msdos || type == PartitionTable::msdos_sectorbased)
0705         flags = PartitionTable::Flag::Boot;
0706 
0707     return flags;
0708 }
0709 
0710 std::unique_ptr<CoreBackendDevice> SfdiskBackend::openDevice(const Device& d)
0711 {
0712     std::unique_ptr<SfdiskDevice> device = std::make_unique<SfdiskDevice>(d);
0713 
0714     if (!device->open())
0715         device = nullptr;
0716 
0717     return device;
0718 }
0719 
0720 std::unique_ptr<CoreBackendDevice> SfdiskBackend::openDeviceExclusive(const Device& d)
0721 {
0722     std::unique_ptr<SfdiskDevice> device = std::make_unique<SfdiskDevice>(d);
0723 
0724     if (!device->openExclusive())
0725         device = nullptr;
0726 
0727     return device;
0728 }
0729 
0730 bool SfdiskBackend::closeDevice(std::unique_ptr<CoreBackendDevice> coreDevice)
0731 {
0732     return coreDevice->close();
0733 }
0734 
0735 #include "sfdiskbackend.moc"