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"