From 0d88e26c8c8474a4282b1dd51859e8ad74e548df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrius=20=C5=A0tikonas?= Date: Sun, 17 Sep 2017 15:33:25 +0100 Subject: [PATCH] Add an experimental sfdisk backend. --- src/core/partitiontable.cpp | 1 + src/plugins/CMakeLists.txt | 1 + src/plugins/pmcorebackendplugin.desktop | 1 - src/plugins/sfdisk/CMakeLists.txt | 29 ++ .../sfdisk/pmsfdiskbackendplugin.desktop | 17 + src/plugins/sfdisk/sfdiskbackend.cpp | 395 ++++++++++++++++++ src/plugins/sfdisk/sfdiskbackend.h | 62 +++ src/plugins/sfdisk/sfdiskdevice.cpp | 120 ++++++ src/plugins/sfdisk/sfdiskdevice.h | 55 +++ src/plugins/sfdisk/sfdiskpartition.cpp | 35 ++ src/plugins/sfdisk/sfdiskpartition.h | 40 ++ src/plugins/sfdisk/sfdiskpartitiontable.cpp | 178 ++++++++ src/plugins/sfdisk/sfdiskpartitiontable.h | 58 +++ 13 files changed, 991 insertions(+), 1 deletion(-) create mode 100644 src/plugins/sfdisk/CMakeLists.txt create mode 100644 src/plugins/sfdisk/pmsfdiskbackendplugin.desktop create mode 100644 src/plugins/sfdisk/sfdiskbackend.cpp create mode 100644 src/plugins/sfdisk/sfdiskbackend.h create mode 100644 src/plugins/sfdisk/sfdiskdevice.cpp create mode 100644 src/plugins/sfdisk/sfdiskdevice.h create mode 100644 src/plugins/sfdisk/sfdiskpartition.cpp create mode 100644 src/plugins/sfdisk/sfdiskpartition.h create mode 100644 src/plugins/sfdisk/sfdiskpartitiontable.cpp create mode 100644 src/plugins/sfdisk/sfdiskpartitiontable.h diff --git a/src/core/partitiontable.cpp b/src/core/partitiontable.cpp index accbe3d..1c816ed 100644 --- a/src/core/partitiontable.cpp +++ b/src/core/partitiontable.cpp @@ -455,6 +455,7 @@ static struct { { QLatin1String("dasd"), 1, false, true, PartitionTable::dasd }, { QLatin1String("msdos"), 4, true, false, PartitionTable::msdos }, { QLatin1String("msdos"), 4, true, false, PartitionTable::msdos_sectorbased }, + { QLatin1String("dos"), 4, true, false, PartitionTable::msdos_sectorbased }, { QLatin1String("dvh"), 16, true, true, PartitionTable::dvh }, { QLatin1String("gpt"), 128, false, false, PartitionTable::gpt }, { QLatin1String("loop"), 1, false, true, PartitionTable::loop }, diff --git a/src/plugins/CMakeLists.txt b/src/plugins/CMakeLists.txt index 6d57eb1..2d93e9a 100644 --- a/src/plugins/CMakeLists.txt +++ b/src/plugins/CMakeLists.txt @@ -29,3 +29,4 @@ if (PARTMAN_DUMMYBACKEND) add_subdirectory(dummy) endif (PARTMAN_DUMMYBACKEND) +add_subdirectory(sfdisk) diff --git a/src/plugins/pmcorebackendplugin.desktop b/src/plugins/pmcorebackendplugin.desktop index 7e42ba6..e9bcb21 100644 --- a/src/plugins/pmcorebackendplugin.desktop +++ b/src/plugins/pmcorebackendplugin.desktop @@ -47,4 +47,3 @@ Name[uk]=Додаток Керування розділами KDE Name[x-test]=xxKDE Partition Manager Pluginxx Name[zh_CN]=KDE 分区管理器插件 Name[zh_TW]=KDE 磁碟分割區管理員外掛程式 - diff --git a/src/plugins/sfdisk/CMakeLists.txt b/src/plugins/sfdisk/CMakeLists.txt new file mode 100644 index 0000000..a795063 --- /dev/null +++ b/src/plugins/sfdisk/CMakeLists.txt @@ -0,0 +1,29 @@ +# Copyright (C) 2010 by Volker Lanz +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 3 of +# the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +set (pmsfdiskbackendplugin_SRCS + sfdiskbackend.cpp + sfdiskdevice.cpp + sfdiskpartition.cpp + sfdiskpartitiontable.cpp +) + +add_library(pmsfdiskbackendplugin SHARED ${pmsfdiskbackendplugin_SRCS}) + +target_link_libraries(pmsfdiskbackendplugin kpmcore) + +install(TARGETS pmsfdiskbackendplugin DESTINATION ${KDE_INSTALL_PLUGINDIR}) +kcoreaddons_desktop_to_json(pmsfdiskbackendplugin pmsfdiskbackendplugin.desktop) +install(FILES pmsfdiskbackendplugin.desktop DESTINATION ${SERVICES_INSTALL_DIR}) diff --git a/src/plugins/sfdisk/pmsfdiskbackendplugin.desktop b/src/plugins/sfdisk/pmsfdiskbackendplugin.desktop new file mode 100644 index 0000000..cc325d0 --- /dev/null +++ b/src/plugins/sfdisk/pmsfdiskbackendplugin.desktop @@ -0,0 +1,17 @@ +[Desktop Entry] +Encoding=UTF-8 +Name=KDE Partition Manager sfdisk Backend +Comment=A KDE Partition Manager sfdisk backend. +Type=Service +ServiceTypes=PartitionManager/Plugin +Icon=preferences-plugin + +X-KDE-Library=pmsfdiskbackendplugin +X-KDE-PluginInfo-Name=pmsfdiskbackendplugin +X-KDE-PluginInfo-Author=Andrius Štikonas +X-KDE-PluginInfo-Email=andrius@stikonas.eu +X-KDE-PluginInfo-License=GPL +X-KDE-PluginInfo-Category=BackendPlugin +X-KDE-PluginInfo-EnabledByDefault=true +X-KDE-PluginInfo-Version=1 +X-KDE-PluginInfo-Website=http://www.partitionmanager.org diff --git a/src/plugins/sfdisk/sfdiskbackend.cpp b/src/plugins/sfdisk/sfdiskbackend.cpp new file mode 100644 index 0000000..1989fdf --- /dev/null +++ b/src/plugins/sfdisk/sfdiskbackend.cpp @@ -0,0 +1,395 @@ +/************************************************************************* + * Copyright (C) 2017 by Andrius Štikonas * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 3 of * + * the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see .* + *************************************************************************/ + +/** @file +*/ + +#include "plugins/sfdisk/sfdiskbackend.h" +#include "plugins/sfdisk/sfdiskdevice.h" + +#include "core/diskdevice.h" +#include "core/lvmdevice.h" +#include "core/partitiontable.h" +#include "core/partitionalignment.h" + +#include "fs/filesystemfactory.h" +#include "fs/luks.h" + +#include "util/globallog.h" +#include "util/externalcommand.h" +#include "util/helpers.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +K_PLUGIN_FACTORY_WITH_JSON(SfdiskBackendFactory, "pmsfdiskbackendplugin.json", registerPlugin();) + +SfdiskBackend::SfdiskBackend(QObject*, const QList&) : + CoreBackend() +{ +} + +void SfdiskBackend::initFSSupport() +{ +} + +QList SfdiskBackend::scanDevices(bool excludeReadOnly) +{ +// TODO: add another bool option for loopDevices + QList result; + QStringList deviceNodes; + + ExternalCommand cmd(QStringLiteral("lsblk"), + { QStringLiteral("--nodeps"), + QStringLiteral("--paths"), + QStringLiteral("--sort"), QStringLiteral("name"), + QStringLiteral("--json"), + QStringLiteral("--output"), + QStringLiteral("type,name") }); + + if (cmd.run(-1) && cmd.exitCode() == 0) { + const QJsonDocument jsonDocument = QJsonDocument::fromJson(cmd.output().toUtf8()); + const QJsonObject jsonObject = jsonDocument.object(); + const QJsonArray jsonArray = jsonObject[QLatin1String("blockdevices")].toArray(); + for (const auto &deviceLine : jsonArray) { + QJsonObject deviceObject = deviceLine.toObject(); + if (deviceObject[QLatin1String("type")].toString() != QLatin1String("disk")) + continue; + + const QString deviceNode = deviceObject[QLatin1String("name")].toString(); + if (excludeReadOnly) { + QString deviceName = deviceNode; + deviceName.remove(QStringLiteral("/dev/")); + QFile f(QStringLiteral("/sys/block/%1/ro").arg(deviceName)); + if (f.open(QIODevice::ReadOnly)) + if (f.readLine().trimmed().toInt() == 1) + continue; + } + deviceNodes << deviceNode; + } + + int totalDevices = deviceNodes.length(); + for (int i = 0; i < totalDevices; ++i) { + const QString deviceNode = deviceNodes[i]; + + emitScanProgress(deviceNode, i * 100 / totalDevices); + Device* device = scanDevice(deviceNode); + if (device != nullptr) { + result.append(device); + } + } + + LvmDevice::scanSystemLVM(result); + } + + return result; +} + +/** Create a Device for the given device_node and scan it for partitions. + @param deviceNode the device node (e.g. "/dev/sda") + @return the created Device object. callers need to free this. +*/ +Device* SfdiskBackend::scanDevice(const QString& deviceNode) +{ + ExternalCommand modelCommand(QStringLiteral("lsblk"), + { QStringLiteral("--nodeps"), + QStringLiteral("--noheadings"), + QStringLiteral("--output"), QStringLiteral("model"), + deviceNode }); + ExternalCommand sizeCommand(QStringLiteral("blockdev"), { QStringLiteral("--getsize64"), deviceNode }); + ExternalCommand sizeCommand2(QStringLiteral("blockdev"), { QStringLiteral("--getss"), deviceNode }); + ExternalCommand jsonCommand(QStringLiteral("sfdisk"), { QStringLiteral("--json"), deviceNode } ); + + if ( modelCommand.run(-1) && modelCommand.exitCode() == 0 + && sizeCommand.run(-1) && sizeCommand.exitCode() == 0 + && sizeCommand2.run(-1) && sizeCommand2.exitCode() == 0 + && jsonCommand.run(-1) ) + { + QString modelName = modelCommand.output(); + modelName = modelName.left(modelName.length() - 1); + qint64 deviceSize = sizeCommand.output().trimmed().toLongLong(); + + Log(Log::information) << xi18nc("@info:status", "Device found: %1", modelName); + int logicalSectorSize = sizeCommand2.output().trimmed().toLongLong(); + DiskDevice* d = new DiskDevice(modelName, deviceNode, 255, 63, deviceSize / logicalSectorSize / 255 / 63, logicalSectorSize); + + if (jsonCommand.exitCode() != 0) + return d; + + const QJsonObject jsonObject = QJsonDocument::fromJson(jsonCommand.output().toUtf8()).object(); + const QJsonObject partitionTable = jsonObject[QLatin1String("partitiontable")].toObject(); + + QString tableType = partitionTable[QLatin1String("label")].toString(); + const PartitionTable::TableType type = PartitionTable::nameToTableType(tableType); + + qint64 firstUsableSector = 0, lastUsableSector = d->totalSectors(); + if (type == PartitionTable::gpt) { + firstUsableSector = partitionTable[QLatin1String("firstlba")].toVariant().toLongLong(); + lastUsableSector = partitionTable[QLatin1String("lastlba")].toVariant().toLongLong(); + } + if (lastUsableSector < firstUsableSector) { + return nullptr; + } + + setPartitionTableForDevice(*d, new PartitionTable(type, firstUsableSector, lastUsableSector)); + switch (type) { + case PartitionTable::gpt: + { + // Read the maximum number of GPT partitions + qint32 maxEntries; + ExternalCommand ddCommand(QStringLiteral("dd"), { QStringLiteral("skip=1"), QStringLiteral("count=1"), QStringLiteral("if=") + deviceNode}, QProcess::SeparateChannels); + if (ddCommand.run(-1) && ddCommand.exitCode() == 0 ) { + QByteArray gptHeader = ddCommand.rawOutput(); + QByteArray gptMaxEntries = gptHeader.mid(80, 4); + QDataStream stream(&gptMaxEntries, QIODevice::ReadOnly); + stream.setByteOrder(QDataStream::LittleEndian); + stream >> maxEntries; + } + else + maxEntries = 128; + CoreBackend::setPartitionTableMaxPrimaries(*d->partitionTable(), maxEntries); + } + default: + break; + } + + scanDevicePartitions(*d, partitionTable[QLatin1String("partitions")].toArray()); + return d; + } + return nullptr; +} + +/** Scans a Device for Partitions. + + This method will scan a Device for all Partitions on it, detect the FileSystem for each Partition, + try to determine the FileSystem usage, read the FileSystem label and store it all in newly created + objects that are in the end added to the Device's PartitionTable. +*/ +void SfdiskBackend::scanDevicePartitions(Device& d, const QJsonArray& jsonPartitions) +{ + Q_ASSERT(d.partitionTable()); + + QList partitions; + for (const auto &partition : jsonPartitions) { + const QJsonObject partitionObject = partition.toObject(); + const QString partitionNode = partitionObject[QLatin1String("node")].toString(); + const qint64 start = partitionObject[QLatin1String("start")].toVariant().toLongLong(); + const qint64 size = partitionObject[QLatin1String("size")].toVariant().toLongLong(); + const QString partitionType = partitionObject[QLatin1String("type")].toString(); + PartitionTable::Flag activeFlags = partitionObject[QLatin1String("bootable")].toBool() ? PartitionTable::FlagBoot : PartitionTable::FlagNone; + + if (partitionType == QStringLiteral("C12A7328-F81F-11D2-BA4B-00A0C93EC93B")) + activeFlags = PartitionTable::FlagEsp; + else if (partitionType == QStringLiteral("21686148-6449-6E6F-744E-656564454649")) + activeFlags = PartitionTable::FlagBiosGrub; + + FileSystem::Type type = FileSystem::Unknown; + type = detectFileSystem(partitionNode); + PartitionRole::Roles r = PartitionRole::Primary; + + if ( (d.partitionTable()->type() == PartitionTable::msdos || d.partitionTable()->type() == PartitionTable::msdos_sectorbased) && partitionType.toInt() == 5 ) { + r = PartitionRole::Extended; + type = FileSystem::Extended; + } + + // Find an extended partition this partition is in. + PartitionNode* parent = d.partitionTable()->findPartitionBySector(start, PartitionRole(PartitionRole::Extended)); + + // None found, so it's a primary in the device's partition table. + if (parent == nullptr) + parent = d.partitionTable(); + else + r = PartitionRole::Logical; + + FileSystem* fs = FileSystemFactory::create(type, start, start + size - 1, d.logicalSize()); + fs->scan(partitionNode); + + QString mountPoint; + bool mounted; + // sfdisk does not handle LUKS partitions + if (fs->type() == FileSystem::Luks) { + r |= PartitionRole::Luks; + FS::luks* luksFs = static_cast(fs); + luksFs->initLUKS(); + QString mapperNode = luksFs->mapperName(); + mountPoint = FileSystem::detectMountPoint(fs, mapperNode); + mounted = FileSystem::detectMountStatus(fs, mapperNode); + } else { + mountPoint = FileSystem::detectMountPoint(fs, partitionNode); + mounted = FileSystem::detectMountStatus(fs, partitionNode); + } + + Partition* part = new Partition(parent, d, PartitionRole(r), fs, start, start + size - 1, partitionNode, availableFlags(d.partitionTable()->type()), mountPoint, mounted, activeFlags); + + if (!part->roles().has(PartitionRole::Luks)) + readSectorsUsed(d, *part, mountPoint); + + if (fs->supportGetLabel() != FileSystem::cmdSupportNone) + fs->setLabel(fs->readLabel(part->deviceNode())); + + if (d.partitionTable()->type() == PartitionTable::TableType::gpt) { + part->setLabel(partitionObject[QLatin1String("name")].toString()); + part->setUUID(partitionObject[QLatin1String("uuid")].toString()); + } + + if (fs->supportGetUUID() != FileSystem::cmdSupportNone) + fs->setUUID(fs->readUUID(part->deviceNode())); + + parent->append(part); + partitions.append(part); + } + + d.partitionTable()->updateUnallocated(d); + + if (d.partitionTable()->isSectorBased(d)) + d.partitionTable()->setType(d, PartitionTable::msdos_sectorbased); + + for (const Partition * part : qAsConst(partitions)) + PartitionAlignment::isAligned(d, *part); +} + +/** Reads the sectors used in a FileSystem and stores the result in the Partition's FileSystem object. + @param p the Partition the FileSystem is on + @param mountPoint mount point of the partition in question +*/ +void SfdiskBackend::readSectorsUsed(const Device& d, Partition& p, const QString& mountPoint) +{ + if (!mountPoint.isEmpty() && p.fileSystem().type() != FileSystem::LinuxSwap && p.fileSystem().type() != FileSystem::Lvm2_PV) { + const QStorageInfo storage = QStorageInfo(mountPoint); + if (p.isMounted() && storage.isValid()) + p.fileSystem().setSectorsUsed( (storage.bytesTotal() - storage.bytesFree()) / d.logicalSize()); + } + else if (p.fileSystem().supportGetUsed() == FileSystem::cmdSupportFileSystem) + p.fileSystem().setSectorsUsed(p.fileSystem().readUsedCapacity(p.deviceNode()) / d.logicalSize()); +} + +FileSystem::Type SfdiskBackend::detectFileSystem(const QString& partitionPath) +{ + FileSystem::Type rval = FileSystem::Unknown; + + ExternalCommand lsblkCommand(QStringLiteral("lsblk"), { + QStringLiteral("--json"), + QStringLiteral("--paths"), + QStringLiteral("--output=name,fstype"), + partitionPath }); + + if (lsblkCommand.run(-1) && lsblkCommand.exitCode() == 0) { + const QJsonArray partitionArray = QJsonDocument::fromJson(lsblkCommand.output().toUtf8()).object()[QLatin1String("blockdevices")].toArray(); + + for (const auto &partition : partitionArray) { + QJsonObject partitionObject = partition.toObject(); + QString node = partitionObject[QLatin1String("name")].toString(); + + QString s = partitionObject[QLatin1String("fstype")].toString(); + + if (s == QStringLiteral("ext2")) rval = FileSystem::Ext2; + else if (s == QStringLiteral("ext3")) rval = FileSystem::Ext3; + else if (s.startsWith(QStringLiteral("ext4"))) rval = FileSystem::Ext4; + else if (s == QStringLiteral("swap")) rval = FileSystem::LinuxSwap; + else if (s == QStringLiteral("ntfs")) rval = FileSystem::Ntfs; + else if (s == QStringLiteral("reiserfs")) rval = FileSystem::ReiserFS; + else if (s == QStringLiteral("reiser4")) rval = FileSystem::Reiser4; + else if (s == QStringLiteral("xfs")) rval = FileSystem::Xfs; + else if (s == QStringLiteral("jfs")) rval = FileSystem::Jfs; + else if (s == QStringLiteral("hfs")) rval = FileSystem::Hfs; + else if (s == QStringLiteral("hfsplus")) rval = FileSystem::HfsPlus; + else if (s == QStringLiteral("ufs")) rval = FileSystem::Ufs; + else if (s == QStringLiteral("vfat")) { + ExternalCommand blkidCommand(QStringLiteral("blkid"), { + QStringLiteral("--output=value"), + QStringLiteral("--match-tag"), + QStringLiteral("SEC_TYPE"), + partitionPath }); + // blkid uses SEC_TYPE to distinguish between FAT16 and FAT32 + if (blkidCommand.run(-1) && blkidCommand.exitCode() == 0 && blkidCommand.output().trimmed() == QStringLiteral("msdos")) + rval = FileSystem::Fat16; + else + rval = FileSystem::Fat32; + } else if (s == QStringLiteral("btrfs")) rval = FileSystem::Btrfs; + else if (s == QStringLiteral("ocfs2")) rval = FileSystem::Ocfs2; + else if (s == QStringLiteral("zfs_member")) rval = FileSystem::Zfs; + else if (s == QStringLiteral("hpfs")) rval = FileSystem::Hpfs; + else if (s == QStringLiteral("crypto_LUKS")) rval = FileSystem::Luks; + else if (s == QStringLiteral("exfat")) rval = FileSystem::Exfat; + else if (s == QStringLiteral("nilfs2")) rval = FileSystem::Nilfs2; + else if (s == QStringLiteral("LVM2_member")) rval = FileSystem::Lvm2_PV; + else if (s == QStringLiteral("f2fs")) rval = FileSystem::F2fs; + else if (s == QStringLiteral("udf")) rval = FileSystem::Udf; + else if (s == QStringLiteral("iso9660")) rval = FileSystem::Iso9660; + else + qWarning() << "lsblk: unknown file system type " << s << " on " << partitionPath; + } + } + + return rval; +} + +PartitionTable::Flags SfdiskBackend::availableFlags(PartitionTable::TableType type) +{ + PartitionTable::Flags flags; + if (type == PartitionTable::gpt) { + // These are not really flags but for now keep them for compatibility + // We should implement changing partition type + flags = PartitionTable::FlagBiosGrub | + PartitionTable::FlagEsp; + } + else if (type == PartitionTable::msdos || type == PartitionTable::msdos_sectorbased) + flags = PartitionTable::FlagBoot; + + return flags; +} + +CoreBackendDevice* SfdiskBackend::openDevice(const QString& deviceNode) +{ + SfdiskDevice* device = new SfdiskDevice(deviceNode); + + if (device == nullptr || !device->open()) { + delete device; + device = nullptr; + } + + return device; +} + +CoreBackendDevice* SfdiskBackend::openDeviceExclusive(const QString& deviceNode) +{ + SfdiskDevice* device = new SfdiskDevice(deviceNode); + + if (device == nullptr || !device->openExclusive()) { + delete device; + device = nullptr; + } + + return device; +} + +bool SfdiskBackend::closeDevice(CoreBackendDevice* coreDevice) +{ + return coreDevice->close(); +} + +#include "sfdiskbackend.moc" diff --git a/src/plugins/sfdisk/sfdiskbackend.h b/src/plugins/sfdisk/sfdiskbackend.h new file mode 100644 index 0000000..acb0f63 --- /dev/null +++ b/src/plugins/sfdisk/sfdiskbackend.h @@ -0,0 +1,62 @@ +/************************************************************************* + * Copyright (C) 2017 by Andrius Štikonas * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 3 of * + * the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see .* + *************************************************************************/ + +#if !defined(SFDISKBACKEND__H) + +#define SFDISKBACKEND__H + +#include "backend/corebackend.h" +#include "core/partition.h" +#include "fs/filesystem.h" + +#include +#include + +class Device; +class KPluginFactory; +class QString; + +/** Backend plugin for sfdisk + + @author Andrius Štikonas +*/ +class SfdiskBackend : public CoreBackend +{ + friend class KPluginFactory; + + Q_DISABLE_COPY(SfdiskBackend) + +private: + SfdiskBackend(QObject* parent, const QList& args); + +public: + void initFSSupport() override; + + QList scanDevices(bool excludeReadOnly = false) override; + CoreBackendDevice* openDevice(const QString& deviceNode) override; + CoreBackendDevice* openDeviceExclusive(const QString& deviceNode) override; + bool closeDevice(CoreBackendDevice* coreDevice) override; + Device* scanDevice(const QString& deviceNode) override; + FileSystem::Type detectFileSystem(const QString& partitionPath) override; + +private: + static void readSectorsUsed(const Device& d, Partition& p, const QString& mountPoint); + void scanDevicePartitions(Device& d, const QJsonArray& jsonPartitions); + static PartitionTable::Flags availableFlags(PartitionTable::TableType type); +}; + +#endif diff --git a/src/plugins/sfdisk/sfdiskdevice.cpp b/src/plugins/sfdisk/sfdiskdevice.cpp new file mode 100644 index 0000000..6f80850 --- /dev/null +++ b/src/plugins/sfdisk/sfdiskdevice.cpp @@ -0,0 +1,120 @@ +/************************************************************************* + * Copyright (C) 2017 by Andrius Štikonas * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 3 of * + * the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see .* + *************************************************************************/ + +#include "plugins/sfdisk/sfdiskdevice.h" +#include "plugins/sfdisk/sfdiskpartitiontable.h" + +#include "core/partitiontable.h" + +#include "util/externalcommand.h" +#include "util/report.h" + +SfdiskDevice::SfdiskDevice(const QString& deviceNode) : + CoreBackendDevice(deviceNode), + m_deviceNode(deviceNode) +{ +} + +SfdiskDevice::~SfdiskDevice() +{ +} + +bool SfdiskDevice::open() +{ + return true; +} + +bool SfdiskDevice::openExclusive() +{ + setExclusive(true); + + return true; +} + +bool SfdiskDevice::close() +{ + if (isExclusive()) + setExclusive(false); + + return true; +} + +CoreBackendPartitionTable* SfdiskDevice::openPartitionTable() +{ + CoreBackendPartitionTable* ptable = new SfdiskPartitionTable(m_deviceNode); + + if (ptable == nullptr || !ptable->open()) { + delete ptable; + ptable = nullptr; + } + + return ptable; +} + +bool SfdiskDevice::createPartitionTable(Report& report, const PartitionTable& ptable) +{ + QByteArray tableType; + if (ptable.type() == PartitionTable::msdos || ptable.type() == PartitionTable::msdos_sectorbased) + tableType = QByteArrayLiteral("dos"); + else + tableType = ptable.typeName().toLatin1(); + + ExternalCommand createCommand(report, QStringLiteral("sfdisk"), { m_deviceNode } ); + if ( createCommand.start(-1) && createCommand.write(QByteArrayLiteral("label: ") + tableType + + QByteArrayLiteral("\nwrite\n")) && createCommand.waitFor() ) { + return createCommand.output().contains(QStringLiteral("Script header accepted.")); + } + + return false; +} + +bool SfdiskDevice::readData(QByteArray& buffer, qint64 offset, qint64 size) +{ + if (!isExclusive()) + return false; + + ExternalCommand ddCommand(QStringLiteral("dd"), { + QStringLiteral("skip=") + QString::number(offset), + QStringLiteral("bs=") + QString::number(size), + QStringLiteral("count=1"), + QStringLiteral("iflag=skip_bytes"), + QStringLiteral("if=") + m_deviceNode }, QProcess::SeparateChannels); + if (ddCommand.run(-1) && ddCommand.exitCode() == 0) { + buffer = ddCommand.rawOutput(); + return true; + } + + return false; +} + +bool SfdiskDevice::writeData(QByteArray& buffer, qint64 offset) +{ + if (!isExclusive()) + return false; + + ExternalCommand ddCommand(QStringLiteral("dd"), { + QStringLiteral("of=") + m_deviceNode, + QStringLiteral("seek=") + QString::number(offset), + QStringLiteral("bs=1M"), + QStringLiteral("oflag=seek_bytes"), + QStringLiteral("conv=fsync") }, QProcess::SeparateChannels); + if ( ddCommand.start(-1) && ddCommand.write(buffer) == buffer.size() && ddCommand.waitFor() && ddCommand.exitCode() == 0 ) { + return true; + } + + return false; +} diff --git a/src/plugins/sfdisk/sfdiskdevice.h b/src/plugins/sfdisk/sfdiskdevice.h new file mode 100644 index 0000000..daf0108 --- /dev/null +++ b/src/plugins/sfdisk/sfdiskdevice.h @@ -0,0 +1,55 @@ +/************************************************************************* + * Copyright (C) 2017 by Andrius Štikonas * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 3 of * + * the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see .* + *************************************************************************/ + +#if !defined(SFDISKDEVICE__H) + +#define SFDISKDEVICE__H + +#include "backend/corebackenddevice.h" + +#include + +class Partition; +class PartitionTable; +class Report; +class CoreBackendPartitionTable; + +class SfdiskDevice : public CoreBackendDevice +{ + Q_DISABLE_COPY(SfdiskDevice); + +public: + SfdiskDevice(const QString& deviceNode); + ~SfdiskDevice(); + +public: + bool open() override; + bool openExclusive() override; + bool close() override; + + CoreBackendPartitionTable* openPartitionTable() override; + + bool createPartitionTable(Report& report, const PartitionTable& ptable) override; + + bool readData(QByteArray& buffer, qint64 offset, qint64 size) override; + bool writeData(QByteArray& buffer, qint64 offset) override; + +private: + QString m_deviceNode; +}; + +#endif diff --git a/src/plugins/sfdisk/sfdiskpartition.cpp b/src/plugins/sfdisk/sfdiskpartition.cpp new file mode 100644 index 0000000..09c5b9d --- /dev/null +++ b/src/plugins/sfdisk/sfdiskpartition.cpp @@ -0,0 +1,35 @@ +/************************************************************************* + * Copyright (C) 2017 by Andrius Štikonas * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 3 of * + * the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see .* + *************************************************************************/ + +#include "plugins/sfdisk/sfdiskpartition.h" +#include "plugins/sfdisk/sfdiskbackend.h" + +#include "util/report.h" + +SfdiskPartition::SfdiskPartition() : + CoreBackendPartition() +{ +} + +bool SfdiskPartition::setFlag(Report& report, PartitionTable::Flag partitionManagerFlag, bool state) +{ + Q_UNUSED(report); + Q_UNUSED(partitionManagerFlag); + Q_UNUSED(state); + + return true; +} diff --git a/src/plugins/sfdisk/sfdiskpartition.h b/src/plugins/sfdisk/sfdiskpartition.h new file mode 100644 index 0000000..35a0a03 --- /dev/null +++ b/src/plugins/sfdisk/sfdiskpartition.h @@ -0,0 +1,40 @@ +/************************************************************************* + * Copyright (C) 2017 by Andrius Štikonas * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 3 of * + * the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see .* + *************************************************************************/ + +#if !defined(SFDISKPARTITION__H) + +#define SFDISKPARTITION__H + +#include "backend/corebackendpartition.h" + +#include "core/partitiontable.h" + +class Report; + +class SfdiskPartition : public CoreBackendPartition +{ + Q_DISABLE_COPY(SfdiskPartition); + +public: + SfdiskPartition(); + +public: + bool setFlag(Report& report, PartitionTable::Flag flag, bool state) override; +}; + + +#endif diff --git a/src/plugins/sfdisk/sfdiskpartitiontable.cpp b/src/plugins/sfdisk/sfdiskpartitiontable.cpp new file mode 100644 index 0000000..befe761 --- /dev/null +++ b/src/plugins/sfdisk/sfdiskpartitiontable.cpp @@ -0,0 +1,178 @@ +/************************************************************************* + * Copyright (C) 2017 by Andrius Štikonas * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 3 of * + * the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see .* + *************************************************************************/ + +#include "plugins/sfdisk/sfdiskpartitiontable.h" +#include "plugins/sfdisk/sfdiskpartition.h" + +#include "backend/corebackend.h" +#include "backend/corebackendmanager.h" + +#include "core/partition.h" +#include "core/device.h" + +#include "fs/filesystem.h" + +#include "util/report.h" +#include "util/externalcommand.h" + +#include + +#include +#include +#include +#include + +#include + +SfdiskPartitionTable::SfdiskPartitionTable(const QString& deviceNode) : + CoreBackendPartitionTable(), + m_deviceNode(deviceNode) +{ +} + +SfdiskPartitionTable::~SfdiskPartitionTable() +{ +} + +bool SfdiskPartitionTable::open() +{ + return true; +} + +bool SfdiskPartitionTable::commit(quint32 timeout) +{ + if (!ExternalCommand(QStringLiteral("udevadm"), QStringList() << QStringLiteral("settle") << QStringLiteral("--timeout=") + QString::number(timeout)).run() && + !ExternalCommand(QStringLiteral("udevsettle"), QStringList() << QStringLiteral("--timeout=") + QString::number(timeout)).run()) + sleep(timeout); + + return true; +} + +CoreBackendPartition* SfdiskPartitionTable::getExtendedPartition() +{ + return new SfdiskPartition(); +} + +CoreBackendPartition* SfdiskPartitionTable::getPartitionBySector(qint64 sector) +{ + Q_UNUSED(sector); + + return nullptr; +} + +QString SfdiskPartitionTable::createPartition(Report& report, const Partition& partition) +{ + if ( !(partition.roles().has(PartitionRole::Extended) || partition.roles().has(PartitionRole::Logical) || partition.roles().has(PartitionRole::Primary) ) ) { + report.line() << xi18nc("@info:progress", "Unknown partition role for new partition %1 (roles: %2)", partition.deviceNode(), partition.roles().toString()); + return QString(); + } + + QByteArray type = QByteArray(); // FIXME add map between fs types and default partition types + if (partition.roles().has(PartitionRole::Extended)) + type = QByteArrayLiteral(" type=5"); + + // NOTE: at least on GPT partition types "are" partition flags + ExternalCommand createCommand(report, QStringLiteral("sfdisk"), { QStringLiteral("--force"), QStringLiteral("--append"), partition.devicePath() } ); + if ( createCommand.start(-1) && createCommand.write(QByteArrayLiteral("start=") + QByteArray::number(partition.firstSector()) + + type + + QByteArrayLiteral(" size=") + QByteArray::number(partition.length()) + QByteArrayLiteral("\nwrite\n")) && createCommand.waitFor() ) { + QRegularExpression re(QStringLiteral("Created a new partition (\\d)")); + QRegularExpressionMatch rem = re.match(createCommand.output()); + if (rem.hasMatch()) + return partition.devicePath() + rem.captured(1); + } + + report.line() << xi18nc("@info:progress", "Failed to add partition %1 to device %2.", partition.deviceNode(), m_deviceNode); + + return QString(); +} + +bool SfdiskPartitionTable::deletePartition(Report& report, const Partition& partition) +{ + ExternalCommand deleteCommand(report, QStringLiteral("sfdisk"), { QStringLiteral("--force"), QStringLiteral("--delete"), partition.devicePath(), QString::number(partition.number()) } ); + if (deleteCommand.run(-1) && deleteCommand.exitCode() == 0) + return true; + + report.line() << xi18nc("@info:progress", "Could not delete partition %1.", partition.devicePath()); + return false; +} + +bool SfdiskPartitionTable::updateGeometry(Report& report, const Partition& partition, qint64 sectorStart, qint64 sectorEnd) +{ + ExternalCommand sfdiskCommand(report, QStringLiteral("sfdisk"), { QStringLiteral("--force"), partition.devicePath(), QStringLiteral("-N"), QString::number(partition.number()) } ); + if ( sfdiskCommand.start(-1) && sfdiskCommand.write(QByteArrayLiteral("start=") + QByteArray::number(sectorStart) + + QByteArrayLiteral(" size=") + QByteArray::number(sectorEnd - sectorStart + 1) + + QByteArrayLiteral("\nY\n")) + && sfdiskCommand.waitFor() && sfdiskCommand.exitCode() == 0) { + return true; + } + + report.line() << xi18nc("@info:progress", "Could not set geometry for partition %1 while trying to resize/move it.", partition.devicePath()); + return false; +} + +bool SfdiskPartitionTable::clobberFileSystem(Report& report, const Partition& partition) +{ + ExternalCommand wipeCommand(report, QStringLiteral("wipefs"), { QStringLiteral("--all"), partition.partitionPath() } ); + if (wipeCommand.run(-1) && wipeCommand.exitCode() == 0) + return true; + + report.line() << xi18nc("@info:progress", "Failed to erase filesystem signature on partition %1.", partition.partitionPath()); + + return false; +} + +bool SfdiskPartitionTable::resizeFileSystem(Report& report, const Partition& partition, qint64 newLength) +{ + // sfdisk does not have any partition resize capabilities + Q_UNUSED(report); + Q_UNUSED(partition); + Q_UNUSED(newLength); + + return false; +} + +FileSystem::Type SfdiskPartitionTable::detectFileSystemBySector(Report& report, const Device& device, qint64 sector) +{ + FileSystem::Type type = FileSystem::Unknown; + + ExternalCommand jsonCommand(QStringLiteral("sfdisk"), { QStringLiteral("--json"), device.deviceNode() } ); + if (jsonCommand.run(-1) && jsonCommand.exitCode() == 0) { + const QJsonArray partitionTable = QJsonDocument::fromJson(jsonCommand.output().toUtf8()).object()[QLatin1String("partitiontable")].toObject()[QLatin1String("partitions")].toArray(); + for (const auto &partition : partitionTable) { + const QJsonObject partitionObject = partition.toObject(); + const qint64 start = partitionObject[QLatin1String("start")].toVariant().toLongLong(); + if (start == sector) { + const QString deviceNode = partitionObject[QLatin1String("node")].toString(); + type = CoreBackendManager::self()->backend()->detectFileSystem(deviceNode); + return type; + } + } + } + + report.line() << xi18nc("@info:progress", "Could not determine file system of partition at sector %1 on device %2.", sector, device.deviceNode()); + + return type; +} + +bool SfdiskPartitionTable::setPartitionSystemType(Report& report, const Partition& partition) +{ + Q_UNUSED(report); + Q_UNUSED(partition); + + return true; +} diff --git a/src/plugins/sfdisk/sfdiskpartitiontable.h b/src/plugins/sfdisk/sfdiskpartitiontable.h new file mode 100644 index 0000000..9fcbb6e --- /dev/null +++ b/src/plugins/sfdisk/sfdiskpartitiontable.h @@ -0,0 +1,58 @@ +/************************************************************************* + * Copyright (C) 2017 by Andrius Štikonas * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 3 of * + * the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see .* + *************************************************************************/ + +#if !defined(SFDISKPARTITIONTABLE__H) + +#define SFDISKPARTITIONTABLE__H + +#include "backend/corebackendpartitiontable.h" + +#include "fs/filesystem.h" + +#include + +class CoreBackendPartition; +class Report; +class Partition; + +class SfdiskPartitionTable : public CoreBackendPartitionTable +{ +public: + SfdiskPartitionTable(const QString& deviceNode); + ~SfdiskPartitionTable(); + +public: + bool open() override; + + bool commit(quint32 timeout = 10) override; + + CoreBackendPartition* getExtendedPartition() override; + CoreBackendPartition* getPartitionBySector(qint64 sector) override; + + QString createPartition(Report& report, const Partition& partition) override; + bool deletePartition(Report& report, const Partition& partition) override; + bool updateGeometry(Report& report, const Partition& partition, qint64 sector_start, qint64 sector_end) override; + bool clobberFileSystem(Report& report, const Partition& partition) override; + bool resizeFileSystem(Report& report, const Partition& partition, qint64 newLength) override; + FileSystem::Type detectFileSystemBySector(Report& report, const Device& device, qint64 sector) override; + bool setPartitionSystemType(Report& report, const Partition& partition) override; + +private: + QString m_deviceNode; +}; + +#endif