File indexing completed on 2024-05-19 05:49:06

0001 /*
0002     SPDX-FileCopyrightText: 2017-2019 Andrius Štikonas <andrius@stikonas.eu>
0003     SPDX-FileCopyrightText: 2018 Caio Jordão Carvalho <caiojcarvalho@gmail.com>
0004     SPDX-FileCopyrightText: 2020 Gaël PORTAY <gael.portay@collabora.com>
0005 
0006     SPDX-License-Identifier: GPL-3.0-or-later
0007 */
0008 
0009 #include "plugins/sfdisk/sfdiskpartitiontable.h"
0010 #include "plugins/sfdisk/sfdiskgptattributes.h"
0011 
0012 #include "backend/corebackend.h"
0013 #include "backend/corebackendmanager.h"
0014 
0015 #include "core/partition.h"
0016 #include "core/device.h"
0017 #include "core/raid/softwareraid.h"
0018 
0019 #include "fs/filesystem.h"
0020 
0021 #include "util/report.h"
0022 #include "util/externalcommand.h"
0023 
0024 #include <QJsonArray>
0025 #include <QJsonDocument>
0026 #include <QJsonObject>
0027 #include <QRegularExpression>
0028 
0029 #include <KLocalizedString>
0030 
0031 SfdiskPartitionTable::SfdiskPartitionTable(const Device* d) :
0032     CoreBackendPartitionTable(),
0033     m_device(d)
0034 {
0035 }
0036 
0037 SfdiskPartitionTable::~SfdiskPartitionTable()
0038 {
0039 }
0040 
0041 bool SfdiskPartitionTable::open()
0042 {
0043     return true;
0044 }
0045 
0046 bool SfdiskPartitionTable::commit(quint32 timeout)
0047 {
0048     if (m_device->type() == Device::Type::SoftwareRAID_Device)
0049         ExternalCommand(QStringLiteral("udevadm"), { QStringLiteral("control"), QStringLiteral("--stop-exec-queue") }).run();
0050 
0051     ExternalCommand(QStringLiteral("udevadm"), { QStringLiteral("settle"), QStringLiteral("--timeout=") + QString::number(timeout) }).run();
0052     ExternalCommand(QStringLiteral("partx"), { QStringLiteral("--update"), m_device->deviceNode() }).run();
0053     ExternalCommand(QStringLiteral("udevadm"), { QStringLiteral("trigger"), QStringLiteral("--subsystem-match=block") }).run();
0054 
0055     if (m_device->type() == Device::Type::SoftwareRAID_Device)
0056         ExternalCommand(QStringLiteral("udevadm"), { QStringLiteral("control"), QStringLiteral("--start-exec-queue") }).run();
0057 
0058     ExternalCommand(QStringLiteral("udevadm"), { QStringLiteral("settle"), QStringLiteral("--timeout=") + QString::number(timeout) }).run();
0059     return true;
0060 }
0061 
0062 QString SfdiskPartitionTable::createPartition(Report& report, const Partition& partition)
0063 {
0064     if ( !(partition.roles().has(PartitionRole::Extended) || partition.roles().has(PartitionRole::Logical) || partition.roles().has(PartitionRole::Primary) ) ) {
0065         report.line() << xi18nc("@info:progress", "Unknown partition role for new partition <filename>%1</filename> (roles: %2)", partition.deviceNode(), partition.roles().toString());
0066         return QString();
0067     }
0068 
0069     QByteArray type = QByteArray();
0070     if (partition.roles().has(PartitionRole::Extended))
0071         type = QByteArrayLiteral(" type=5");
0072 
0073     // NOTE: at least on GPT partition types "are" partition flags
0074     ExternalCommand createCommand(report, QStringLiteral("sfdisk"), { QStringLiteral("--force"), QStringLiteral("--append"), partition.devicePath() } );
0075     if ( createCommand.write(QByteArrayLiteral("start=") + QByteArray::number(partition.firstSector()) +
0076                                                         type +
0077                                                         QByteArrayLiteral(" size=") + QByteArray::number(partition.length()) + QByteArrayLiteral("\nwrite\n")) && createCommand.start(-1) ) {
0078         QRegularExpression re(QStringLiteral("Created a new partition (\\d+)"));
0079         QRegularExpressionMatch rem = re.match(createCommand.output());
0080 
0081         if (rem.hasMatch()) {
0082             if ( partition.devicePath().back().isDigit() )
0083                 return partition.devicePath() + QLatin1Char('p') + rem.captured(1);
0084             else
0085                 return partition.devicePath() + rem.captured(1);
0086         }
0087     }
0088 
0089     report.line() << xi18nc("@info:progress", "Failed to add partition <filename>%1</filename> to device <filename>%2</filename>.", partition.deviceNode(), m_device->deviceNode());
0090 
0091     return QString();
0092 }
0093 
0094 bool SfdiskPartitionTable::deletePartition(Report& report, const Partition& partition)
0095 {
0096     ExternalCommand deleteCommand(report, QStringLiteral("sfdisk"), { QStringLiteral("--force"), QStringLiteral("--delete"), partition.devicePath(), QString::number(partition.number()) } );
0097     if (deleteCommand.run(-1) && deleteCommand.exitCode() == 0)
0098         return true;
0099 
0100     report.line() << xi18nc("@info:progress", "Could not delete partition <filename>%1</filename>.", partition.devicePath());
0101     return false;
0102 }
0103 
0104 bool SfdiskPartitionTable::updateGeometry(Report& report, const Partition& partition, qint64 sectorStart, qint64 sectorEnd)
0105 {
0106     ExternalCommand sfdiskCommand(report, QStringLiteral("sfdisk"), { QStringLiteral("--force"), partition.devicePath(), QStringLiteral("-N"), QString::number(partition.number()) } );
0107     if ( sfdiskCommand.write(QByteArrayLiteral("start=") + QByteArray::number(sectorStart) +
0108                                                         QByteArrayLiteral(" size=") + QByteArray::number(sectorEnd - sectorStart + 1) +
0109                                                         QByteArrayLiteral("\nY\n"))
0110                                                         && sfdiskCommand.start(-1) && sfdiskCommand.exitCode() == 0) {
0111         return true;
0112     }
0113 
0114     report.line() << xi18nc("@info:progress", "Could not set geometry for partition <filename>%1</filename> while trying to resize/move it.", partition.devicePath());
0115     return false;
0116 }
0117 
0118 bool SfdiskPartitionTable::clobberFileSystem(Report& report, const Partition& partition)
0119 {
0120     ExternalCommand wipeCommand(report, QStringLiteral("wipefs"), { QStringLiteral("--all"), partition.partitionPath() } );
0121     if (wipeCommand.run(-1) && wipeCommand.exitCode() == 0)
0122         return true;
0123 
0124     report.line() << xi18nc("@info:progress", "Failed to erase filesystem signature on partition <filename>%1</filename>.", partition.partitionPath());
0125 
0126     return false;
0127 }
0128 
0129 bool SfdiskPartitionTable::resizeFileSystem(Report& report, const Partition& partition, qint64 newLength)
0130 {
0131     // sfdisk does not have any partition resize capabilities
0132     Q_UNUSED(report)
0133     Q_UNUSED(partition)
0134     Q_UNUSED(newLength)
0135 
0136     return false;
0137 }
0138 
0139 FileSystem::Type SfdiskPartitionTable::detectFileSystemBySector(Report& report, const Device& device, qint64 sector)
0140 {
0141     FileSystem::Type type = FileSystem::Type::Unknown;
0142 
0143     ExternalCommand jsonCommand(QStringLiteral("sfdisk"), { QStringLiteral("--json"), device.deviceNode() } );
0144     if (jsonCommand.run(-1) && jsonCommand.exitCode() == 0) {
0145         const QJsonArray partitionTable = QJsonDocument::fromJson(jsonCommand.rawOutput()).object()[QLatin1String("partitiontable")].toObject()[QLatin1String("partitions")].toArray();
0146         for (const auto &partition : partitionTable) {
0147             const QJsonObject partitionObject = partition.toObject();
0148             const qint64 start = partitionObject[QLatin1String("start")].toVariant().toLongLong();
0149             if (start == sector) {
0150                 const QString deviceNode = partitionObject[QLatin1String("node")].toString();
0151                 type = CoreBackendManager::self()->backend()->detectFileSystem(deviceNode);
0152                 return type;
0153             }
0154         }
0155     }
0156 
0157     report.line() << xi18nc("@info:progress", "Could not determine file system of partition at sector %1 on device <filename>%2</filename>.", sector, device.deviceNode());
0158 
0159     return type;
0160 }
0161 
0162 static struct {
0163     FileSystem::Type type;
0164     QLatin1String partitionType[2]; // GPT, MBR
0165 } typemap[] = {
0166     { FileSystem::Type::Btrfs, { QLatin1String("0FC63DAF-8483-4772-8E79-3D69D8477DE4"), QLatin1String("83") } },
0167     { FileSystem::Type::Ext2, { QLatin1String("0FC63DAF-8483-4772-8E79-3D69D8477DE4"), QLatin1String("83") } },
0168     { FileSystem::Type::Ext3, { QLatin1String("0FC63DAF-8483-4772-8E79-3D69D8477DE4"), QLatin1String("83") } },
0169     { FileSystem::Type::Ext4, { QLatin1String("0FC63DAF-8483-4772-8E79-3D69D8477DE4"), QLatin1String("83") } },
0170     { FileSystem::Type::LinuxSwap, { QLatin1String("0657FD6D-A4AB-43C4-84E5-0933C84B4F4F"), QLatin1String("82") } },
0171     { FileSystem::Type::Fat12, { QLatin1String("EBD0A0A2-B9E5-4433-87C0-68B6B72699C7"), QLatin1String("6") } },
0172     { FileSystem::Type::Fat16, { QLatin1String("EBD0A0A2-B9E5-4433-87C0-68B6B72699C7"), QLatin1String("6") } },
0173     { FileSystem::Type::Fat32, { QLatin1String("EBD0A0A2-B9E5-4433-87C0-68B6B72699C7"), QLatin1String("c") } },
0174     { FileSystem::Type::Nilfs2, { QLatin1String("0FC63DAF-8483-4772-8E79-3D69D8477DE4"), QLatin1String("83") } },
0175     { FileSystem::Type::Ntfs, { QLatin1String("EBD0A0A2-B9E5-4433-87C0-68B6B72699C7"), QLatin1String("7") } },
0176     { FileSystem::Type::Exfat, { QLatin1String("EBD0A0A2-B9E5-4433-87C0-68B6B72699C7"), QLatin1String("7") } },
0177     { FileSystem::Type::ReiserFS, { QLatin1String("0FC63DAF-8483-4772-8E79-3D69D8477DE4"), QLatin1String("83") } },
0178     { FileSystem::Type::Reiser4, { QLatin1String("0FC63DAF-8483-4772-8E79-3D69D8477DE4"), QLatin1String("83") } },
0179     { FileSystem::Type::Xfs, { QLatin1String("0FC63DAF-8483-4772-8E79-3D69D8477DE4"), QLatin1String("83") } },
0180     { FileSystem::Type::Jfs, { QLatin1String("0FC63DAF-8483-4772-8E79-3D69D8477DE4"), QLatin1String("83") } },
0181     { FileSystem::Type::Hfs, { QLatin1String("48465300-0000-11AA-AA11-00306543ECAC"), QLatin1String("af")} },
0182     { FileSystem::Type::HfsPlus, { QLatin1String("48465300-0000-11AA-AA11-00306543ECAC"), QLatin1String("af") } },
0183     { FileSystem::Type::Udf, { QLatin1String("EBD0A0A2-B9E5-4433-87C0-68B6B72699C7"), QLatin1String("7") } }
0184     // Add ZFS too
0185 };
0186 
0187 static QLatin1String getPartitionType(FileSystem::Type t, PartitionTable::TableType tableType)
0188 {
0189     quint8 type;
0190     switch (tableType) {
0191     case PartitionTable::TableType::gpt:
0192         type = 0;
0193         break;
0194     case PartitionTable::TableType::msdos:
0195     case PartitionTable::TableType::msdos_sectorbased:
0196         type = 1;
0197         break;
0198     default:;
0199         return QLatin1String();
0200     }
0201     for (quint32 i = 0; i < sizeof(typemap) / sizeof(typemap[0]); i++)
0202         if (typemap[i].type == t)
0203             return typemap[i].partitionType[type];
0204 
0205     return QLatin1String();
0206 }
0207 
0208 bool SfdiskPartitionTable::setPartitionLabel(Report& report, const Partition& partition, const QString& label)
0209 {
0210     if (label.isEmpty())
0211         return true;
0212     ExternalCommand sfdiskCommand(report, QStringLiteral("sfdisk"), { QStringLiteral("--part-label"), m_device->deviceNode(), QString::number(partition.number()),
0213                 label } );
0214     return sfdiskCommand.run(-1) && sfdiskCommand.exitCode() == 0;
0215 }
0216 
0217 QString SfdiskPartitionTable::getPartitionUUID(Report& report, const Partition& partition)
0218 {
0219     ExternalCommand sfdiskCommand(report, QStringLiteral("sfdisk"), { QStringLiteral("--list"), QStringLiteral("--output"), QStringLiteral("Device,UUID"),
0220                 m_device->deviceNode() });
0221     if (sfdiskCommand.run(-1) && sfdiskCommand.exitCode() == 0) {
0222         QRegularExpression re(m_device->deviceNode() + QString::number(partition.number()) + QStringLiteral(" +(.+)"));
0223         QRegularExpressionMatch rem = re.match(sfdiskCommand.output());
0224 
0225         if (rem.hasMatch())
0226             return rem.captured(1);
0227     }
0228 
0229     return QString();
0230 }
0231 
0232 bool SfdiskPartitionTable::setPartitionUUID(Report& report, const Partition& partition, const QString& uuid)
0233 {
0234     if (uuid.isEmpty())
0235         return true;
0236     ExternalCommand sfdiskCommand(report, QStringLiteral("sfdisk"), { QStringLiteral("--part-uuid"), m_device->deviceNode(), QString::number(partition.number()),
0237                 uuid } );
0238     return sfdiskCommand.run(-1) && sfdiskCommand.exitCode() == 0;
0239 }
0240 
0241 bool SfdiskPartitionTable::setPartitionAttributes(Report& report, const Partition& partition, quint64 attrs)
0242 {
0243     QStringList attributes = SfdiskGptAttributes::toStringList(attrs);
0244     if (attributes.isEmpty())
0245         return true;
0246     ExternalCommand sfdiskCommand(report, QStringLiteral("sfdisk"), { QStringLiteral("--part-attrs"), m_device->deviceNode(), QString::number(partition.number()),
0247                 attributes.join(QStringLiteral(",")) } );
0248     return sfdiskCommand.run(-1) && sfdiskCommand.exitCode() == 0;
0249 }
0250 
0251 bool SfdiskPartitionTable::setPartitionSystemType(Report& report, const Partition& partition)
0252 {
0253     QString partitionType = partition.type();
0254     if (partitionType.isEmpty())
0255         partitionType = getPartitionType(partition.fileSystem().type(), m_device->partitionTable()->type());
0256     if (partitionType.isEmpty())
0257         return true;
0258     ExternalCommand sfdiskCommand(report, QStringLiteral("sfdisk"), { QStringLiteral("--part-type"), m_device->deviceNode(), QString::number(partition.number()),
0259                 partitionType } );
0260     return sfdiskCommand.run(-1) && sfdiskCommand.exitCode() == 0;
0261 }
0262 
0263 bool SfdiskPartitionTable::setFlag(Report& report, const Partition& partition, PartitionTable::Flag flag, bool state)
0264 {
0265     if (m_device->partitionTable()->type() == PartitionTable::TableType::msdos ||
0266          m_device->partitionTable()->type() == PartitionTable::TableType::msdos_sectorbased) {
0267         // We only allow setting one active partition per device
0268         if (flag == PartitionTable::Flag::Boot && state == true) {
0269             ExternalCommand sfdiskCommand(report, QStringLiteral("sfdisk"), { QStringLiteral("--activate"), m_device->deviceNode(), QString::number(partition.number()) } );
0270             if (sfdiskCommand.run(-1) && sfdiskCommand.exitCode() == 0)
0271                 return true;
0272             else
0273                 return false;
0274         } else if (flag == PartitionTable::Flag::Boot && state == false) {
0275             ExternalCommand sfdiskCommand(report, QStringLiteral("sfdisk"), { QStringLiteral("--activate"), m_device->deviceNode(), QStringLiteral("-") } );
0276             if (sfdiskCommand.run(-1) && sfdiskCommand.exitCode() == 0)
0277                 return true;
0278             else
0279                 return false;
0280         }
0281     }
0282 
0283     if (flag == PartitionTable::Flag::Boot && state == true) {
0284         ExternalCommand sfdiskCommand(report, QStringLiteral("sfdisk"), { QStringLiteral("--part-type"), m_device->deviceNode(), QString::number(partition.number()),
0285                 QStringLiteral("C12A7328-F81F-11D2-BA4B-00A0C93EC93B") } );
0286         if (sfdiskCommand.run(-1) && sfdiskCommand.exitCode() == 0)
0287             return true;
0288         else
0289             return false;
0290     }
0291     if (flag == PartitionTable::Flag::Boot && state == false)
0292         setPartitionSystemType(report, partition);
0293 
0294     if (flag == PartitionTable::Flag::BiosGrub && state == true) {
0295         ExternalCommand sfdiskCommand(report, QStringLiteral("sfdisk"), { QStringLiteral("--part-type"), m_device->deviceNode(), QString::number(partition.number()),
0296                 QStringLiteral("21686148-6449-6E6F-744E-656564454649") } );
0297         if (sfdiskCommand.run(-1) && sfdiskCommand.exitCode() == 0)
0298             return true;
0299         else
0300             return false;
0301     }
0302     if (flag == PartitionTable::Flag::BiosGrub && state == false)
0303         setPartitionSystemType(report, partition);
0304 
0305     return true;
0306 }