diff --git a/src/core/raid/softwareraid.cpp b/src/core/raid/softwareraid.cpp index cb22899..6cb0eaa 100644 --- a/src/core/raid/softwareraid.cpp +++ b/src/core/raid/softwareraid.cpp @@ -1,270 +1,370 @@ /************************************************************************* * Copyright (C) 2018 by Caio Carvalho * * * * 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 "softwareraid.h" #include "backend/corebackend.h" #include "backend/corebackendmanager.h" #include "core/partition.h" #include "core/volumemanagerdevice_p.h" #include "fs/filesystem.h" #include "fs/filesystemfactory.h" #include "util/externalcommand.h" #include #include #define d_ptr std::static_pointer_cast(d) class SoftwareRAIDPrivate : public VolumeManagerDevicePrivate { public: qint32 m_raidLevel; qint64 m_chunkSize; qint64 m_totalChunk; qint64 m_arraySize; QString m_UUID; QStringList m_devicePathList; + bool m_active; }; -SoftwareRAID::SoftwareRAID(const QString& name, const QString& iconName) +SoftwareRAID::SoftwareRAID(const QString& name, bool active, const QString& iconName) : VolumeManagerDevice(std::make_shared(), name, (QStringLiteral("/dev/") + name), getChunkSize(QStringLiteral("/dev/") + name), getTotalChunk(QStringLiteral("/dev/") + name), iconName, Device::Type::SoftwareRAID_Device) { d_ptr->m_raidLevel = getRaidLevel(deviceNode()); d_ptr->m_chunkSize = logicalSize(); d_ptr->m_totalChunk = totalLogical(); d_ptr->m_arraySize = getArraySize(deviceNode()); d_ptr->m_UUID = getUUID(deviceNode()); d_ptr->m_devicePathList = getDevicePathList(deviceNode()); + d_ptr->m_active = active; initPartitions(); } const QStringList SoftwareRAID::deviceNodes() const { return d_ptr->m_devicePathList; } const QStringList& SoftwareRAID::partitionNodes() const { return {}; } qint64 SoftwareRAID::partitionSize(QString &partitionPath) const { Q_UNUSED(partitionPath) return 0; } bool SoftwareRAID::growArray(Report &report, const QStringList &devices) { Q_UNUSED(report) Q_UNUSED(devices) return false; } bool SoftwareRAID::shrinkArray(Report &report, const QStringList &devices) { Q_UNUSED(report) Q_UNUSED(devices) return false; } QString SoftwareRAID::prettyName() const { - return VolumeManagerDevice::prettyName() + xi18nc("@item:inlistbox [RAID level]", " [RAID %1]", raidLevel()); + return VolumeManagerDevice::prettyName() + + (isActive() ? xi18nc("@item:inlistbox [RAID level]", " [RAID %1]", raidLevel()) : + QStringLiteral(" [RAID]")); +} + +bool SoftwareRAID::operator ==(const Device& other) const +{ + bool equalDeviceNode = Device::operator ==(other); + + if (other.type() == Device::Type::SoftwareRAID_Device) { + const SoftwareRAID& raid = static_cast(other); + + if (!equalDeviceNode) + return raid.uuid() == uuid(); + } + + return equalDeviceNode; } qint32 SoftwareRAID::raidLevel() const { return d_ptr->m_raidLevel; } qint64 SoftwareRAID::chunkSize() const { return d_ptr->m_chunkSize; } qint64 SoftwareRAID::totalChunk() const { return d_ptr->m_totalChunk; } qint64 SoftwareRAID::arraySize() const { return d_ptr->m_arraySize; } QString SoftwareRAID::uuid() const { return d_ptr->m_UUID; } QStringList SoftwareRAID::devicePathList() const { return d_ptr->m_devicePathList; } +bool SoftwareRAID::isActive() const +{ + return d_ptr->m_active; +} + +void SoftwareRAID::setActive(bool active) +{ + d_ptr->m_active = active; +} + void SoftwareRAID::scanSoftwareRAID(QList& devices) { - // TODO: Check configuration file and load all the devices that aren't in /proc/mdstat as innactive + QList scannedRaid; + + // TODO: Support custom config files. + QString config = getRAIDConfiguration(QStringLiteral("/etc/mdadm.conf")); + + if (!config.isEmpty()) { + QRegularExpression re(QStringLiteral("[\\t\\r\\n\\f\\s]ARRAY \\/dev\\/([\\/\\w-]+)")); + QRegularExpressionMatchIterator i = re.globalMatch(config); + + while (i.hasNext()) { + QRegularExpressionMatch reMatch = i.next(); + QString deviceName = reMatch.captured(1).trimmed(); + + SoftwareRAID *raidDevice = new SoftwareRAID(deviceName, false); + + scannedRaid << raidDevice; + } + } + ExternalCommand scanRaid(QStringLiteral("cat"), { QStringLiteral("/proc/mdstat") }); if (scanRaid.run(-1) && scanRaid.exitCode() == 0) { QRegularExpression re(QStringLiteral("md([\\/\\w]+)\\s+:")); QRegularExpressionMatchIterator i = re.globalMatch(scanRaid.output()); while (i.hasNext()) { QRegularExpressionMatch reMatch = i.next(); - QString deviceNode = QStringLiteral("/dev/") + QStringLiteral("md") + reMatch.captured(1).trimmed(); + QString deviceNode = QStringLiteral("/dev/md") + reMatch.captured(1).trimmed(); - Device* d = CoreBackendManager::self()->backend()->scanDevice(deviceNode); + SoftwareRAID* d = static_cast(CoreBackendManager::self()->backend()->scanDevice(deviceNode)); - if ( d ) - devices << d; + if (scannedRaid.contains(d)) + d->setActive(true); + else + scannedRaid << d; } } + + devices << scannedRaid; } qint32 SoftwareRAID::getRaidLevel(const QString &path) { QString output = getDetail(path); if (!output.isEmpty()) { QRegularExpression re(QStringLiteral("Raid Level :\\s+\\w+(\\d+)")); QRegularExpressionMatch reMatch = re.match(output); if (reMatch.hasMatch()) return reMatch.captured(1).toLongLong(); } return -1; } qint64 SoftwareRAID::getChunkSize(const QString &path) { QString output = getDetail(path); if (!output.isEmpty()) { QRegularExpression re(QStringLiteral("Chunk Size :\\s+(\\d+)")); QRegularExpressionMatch reMatch = re.match(output); if (reMatch.hasMatch()) return reMatch.captured(1).toLongLong(); } return -1; } qint64 SoftwareRAID::getTotalChunk(const QString &path) { return getArraySize(path) / getChunkSize(path); } qint64 SoftwareRAID::getArraySize(const QString &path) { QString output = getDetail(path); if (!output.isEmpty()) { QRegularExpression re(QStringLiteral("Array Size :\\s+(\\d+)")); QRegularExpressionMatch reMatch = re.match(output); if (reMatch.hasMatch()) return reMatch.captured(1).toLongLong() * 1024; } return -1; } QString SoftwareRAID::getUUID(const QString &path) { - Q_UNUSED(path) - return QStringLiteral(); + QString output = getDetail(path); + + if (!output.isEmpty()) { + QRegularExpression re(QStringLiteral("UUID :\\s+([\\w:]+)")); + QRegularExpressionMatch reMatch = re.match(output); + + if (reMatch.hasMatch()) + return reMatch.captured(1); + } + + // If UUID was not found in detail output, it should be searched in config file + + // TODO: Support custom config files. + QString config = getRAIDConfiguration(QStringLiteral("/etc/mdadm.conf")); + + if (!config.isEmpty()) { + QRegularExpression re(QStringLiteral("[\\t\\r\\n\\f\\s]ARRAY \\/dev\\/md([\\/\\w-]+)(.*)")); + QRegularExpressionMatchIterator i = re.globalMatch(config); + + while (i.hasNext()) { + QRegularExpressionMatch reMatch = i.next(); + QString deviceNode = QStringLiteral("/dev/md") + reMatch.captured(1).trimmed(); + QString otherInfo = reMatch.captured(2).trimmed(); + + // Consider device node as name=host:deviceNode when the captured device node string has '-' character + // It happens when user have included the device to config file using 'mdadm --examine --scan' + if (deviceNode.contains(QLatin1Char('-'))) { + QRegularExpression reName(QStringLiteral("name=[\\w:]+\\/dev\\/md\\/([\\/\\w]+)")); + QRegularExpressionMatch nameMatch = reName.match(otherInfo); + + if (nameMatch.hasMatch()) + deviceNode = nameMatch.captured(1); + } + + if (deviceNode == path) { + QRegularExpression reUUID(QStringLiteral("(UUID=|uuid=)([\\w:]+)")); + QRegularExpressionMatch uuidMatch = reUUID.match(otherInfo); + + if (uuidMatch.hasMatch()) + return uuidMatch.captured(2); + } + } + } + + return QString(); } QStringList SoftwareRAID::getDevicePathList(const QString &path) { Q_UNUSED(path) return {}; } bool SoftwareRAID::isRaidPath(const QString &path) { return !getDetail(path).isEmpty(); } bool SoftwareRAID::createSoftwareRAID(Report &report, const QString &name, const QStringList devicePathList, const qint32 raidLevel, const qint32 chunkSize) { return false; } bool SoftwareRAID::deleteSoftwareRAID(Report &report, SoftwareRAID &raidDevice) { Q_UNUSED(report) Q_UNUSED(raidDevice) return false; } bool SoftwareRAID::assembleSoftwareRAID(const QString& deviceNode) { if (!isRaidPath(deviceNode)) return false; ExternalCommand cmd(QStringLiteral("mdadm"), { QStringLiteral("--assemble"), QStringLiteral("--scan"), deviceNode }); return cmd.run(-1) && cmd.exitCode() == 0; } bool SoftwareRAID::stopSoftwareRAID(const QString& deviceNode) { if (!isRaidPath(deviceNode)) return false; ExternalCommand cmd(QStringLiteral("mdadm"), { QStringLiteral("--manage"), QStringLiteral("--stop"), deviceNode }); return cmd.run(-1) && cmd.exitCode() == 0; } bool SoftwareRAID::reassembleSoftwareRAID(const QString &deviceNode) { return stopSoftwareRAID(deviceNode) && assembleSoftwareRAID(deviceNode); } void SoftwareRAID::initPartitions() { } qint64 SoftwareRAID::mappedSector(const QString &partitionPath, qint64 sector) const { return -1; } QString SoftwareRAID::getDetail(const QString &path) { ExternalCommand cmd(QStringLiteral("mdadm"), { QStringLiteral("--misc"), QStringLiteral("--detail"), path }); - return (cmd.run(-1) && cmd.exitCode() == 0) ? cmd.output() : QStringLiteral(); + return (cmd.run(-1) && cmd.exitCode() == 0) ? cmd.output() : QString(); +} + +QString SoftwareRAID::getRAIDConfiguration(const QString &configurationPath) +{ + ExternalCommand cmd(QStringLiteral("cat"), { configurationPath }); + + return (cmd.run(-1) && cmd.exitCode() == 0) ? cmd.output() : QString(); } diff --git a/src/core/raid/softwareraid.h b/src/core/raid/softwareraid.h index 0951d37..6fd56ee 100644 --- a/src/core/raid/softwareraid.h +++ b/src/core/raid/softwareraid.h @@ -1,85 +1,92 @@ /************************************************************************* * Copyright (C) 2018 by Caio Carvalho * * * * 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(KPMCORE_SOFTWARERAID_H) #define KPMCORE_SOFTWARERAID_H #include "core/volumemanagerdevice.h" #include "util/libpartitionmanagerexport.h" #include "util/report.h" class LIBKPMCORE_EXPORT SoftwareRAID : public VolumeManagerDevice { Q_DISABLE_COPY(SoftwareRAID) public: - SoftwareRAID(const QString& name, const QString& iconName = QString()); + SoftwareRAID(const QString& name, bool active = true, const QString& iconName = QString()); const QStringList deviceNodes() const override; const QStringList& partitionNodes() const override; qint64 partitionSize(QString &partitionPath) const override; virtual bool growArray(Report& report, const QStringList& devices); virtual bool shrinkArray(Report& report, const QStringList& devices); virtual QString prettyName() const override; + virtual bool operator==(const Device& other) const override; + qint32 raidLevel() const; qint64 chunkSize() const; qint64 totalChunk() const; qint64 arraySize() const; QString uuid() const; QStringList devicePathList() const; + bool isActive() const; + + void setActive(bool active); public: static void scanSoftwareRAID(QList& devices); static qint32 getRaidLevel(const QString& path); static qint64 getChunkSize(const QString& path); static qint64 getTotalChunk(const QString& path); static qint64 getArraySize(const QString& path); static QString getUUID(const QString& path); static QStringList getDevicePathList(const QString& path); static bool isRaidPath(const QString& path); static bool createSoftwareRAID(Report& report, const QString& name, const QStringList devicePathList, const qint32 raidLevel, const qint32 chunkSize); static bool deleteSoftwareRAID(Report& report, SoftwareRAID& raidDevice); static bool assembleSoftwareRAID(const QString& deviceNode); static bool stopSoftwareRAID(const QString& deviceNode); static bool reassembleSoftwareRAID(const QString& deviceNode); protected: void initPartitions() override; qint64 mappedSector(const QString &partitionPath, qint64 sector) const override; private: static QString getDetail(const QString& path); + + static QString getRAIDConfiguration(const QString& configurationPath); }; #endif // SOFTWARERAID_H diff --git a/src/ops/createpartitiontableoperation.cpp b/src/ops/createpartitiontableoperation.cpp index f3ed270..57b5738 100644 --- a/src/ops/createpartitiontableoperation.cpp +++ b/src/ops/createpartitiontableoperation.cpp @@ -1,102 +1,114 @@ /************************************************************************* * Copyright (C) 2008, 2010 by Volker Lanz * * Copyright (C) 2016 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 "ops/createpartitiontableoperation.h" #include "core/device.h" #include "core/partitiontable.h" #include "core/partition.h" +#include "core/raid/softwareraid.h" #include "jobs/createpartitiontablejob.h" #include #include /** Creates a new CreatePartitionTableOperation. @param d the Device to create the new PartitionTable on @param t the type for the new PartitionTable */ CreatePartitionTableOperation::CreatePartitionTableOperation(Device& d, PartitionTable::TableType t) : Operation(), m_TargetDevice(d), m_OldPartitionTable(targetDevice().partitionTable()), m_PartitionTable(new PartitionTable(t, PartitionTable::defaultFirstUsable(d, t), PartitionTable::defaultLastUsable(d, t))), m_CreatePartitionTableJob(new CreatePartitionTableJob(targetDevice())) { addJob(createPartitionTableJob()); } /** Creates a new CreatePartitionTableOperation. @param d the Device to create the new PartitionTable on @param ptable pointer to the new partition table object. the operation takes ownership. */ CreatePartitionTableOperation::CreatePartitionTableOperation(Device& d, PartitionTable* ptable) : Operation(), m_TargetDevice(d), m_OldPartitionTable(targetDevice().partitionTable()), m_PartitionTable(ptable), m_CreatePartitionTableJob(new CreatePartitionTableJob(targetDevice())) { addJob(createPartitionTableJob()); } CreatePartitionTableOperation::~CreatePartitionTableOperation() { if (status() == StatusPending) delete m_PartitionTable; } bool CreatePartitionTableOperation::targets(const Device& d) const { return d == targetDevice(); } void CreatePartitionTableOperation::preview() { targetDevice().setPartitionTable(partitionTable()); targetDevice().partitionTable()->updateUnallocated(targetDevice()); } void CreatePartitionTableOperation::undo() { targetDevice().setPartitionTable(oldPartitionTable()); if (targetDevice().partitionTable()) targetDevice().partitionTable()->updateUnallocated(targetDevice()); } bool CreatePartitionTableOperation::execute(Report& parent) { targetDevice().setPartitionTable(partitionTable()); return Operation::execute(parent); } /** Can a new partition table be created on a device? @param device pointer to the device, can be nullptr @return true if a new partition table can be created on @p device */ bool CreatePartitionTableOperation::canCreate(const Device* device) { - return (device != nullptr) && (device->partitionTable() == nullptr || !device->partitionTable()->isChildMounted()) && (device->type() != Device::Type::LVM_Device); + if (device == nullptr) + return false; + + if (device->type() == Device::Type::SoftwareRAID_Device) { + const SoftwareRAID* raid = static_cast(device); + + if (!raid->isActive()) + return false; + } + + return (device->partitionTable() == nullptr || !device->partitionTable()->isChildMounted()) + && (device->type() != Device::Type::LVM_Device); } QString CreatePartitionTableOperation::description() const { return xi18nc("@info:status", "Create a new partition table (type: %1) on %2", partitionTable()->typeName(), targetDevice().deviceNode()); }