SoftwareRAID initial implementation.

This commit is contained in:
Caio Carvalho 2018-07-09 20:01:34 -03:00
parent 9e3386a53b
commit 998b1292c0
6 changed files with 391 additions and 48 deletions

View File

@ -23,6 +23,7 @@ set(CORE_SRC
core/smartparser.cpp
core/smartattributeparseddata.cpp
core/smartdiskinformation.cpp
core/softwareraid.cpp
core/volumemanagerdevice.cpp
)
@ -41,5 +42,6 @@ set(CORE_LIB_HDRS
core/partitiontable.h
core/smartattribute.h
core/smartstatus.h
core/softwareraid.h
core/volumemanagerdevice.h
)

View File

@ -54,7 +54,8 @@ public:
Unknown_Device,
Disk_Device,
LVM_Device, /* VG */
RAID_Device, /* software RAID device */
SoftwareRAID_Device, /* software RAID device, i.e. mdraid */
HardwareRAID_Device, /* hardware RAID device (or fake raid), i.e. dmraid */
};
explicit Device(std::shared_ptr<DevicePrivate> d_ptr, const QString& name, const QString& deviceNode, const qint64 logicalSectorSize, const qint64 totalLogicalSectors, const QString& iconName = QString(), Device::Type type = Device::Type::Disk_Device);

216
src/core/softwareraid.cpp Normal file
View File

@ -0,0 +1,216 @@
/*************************************************************************
* Copyright (C) 2018 by Caio Carvalho <caiojcarvalho@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>.*
*************************************************************************/
#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 <QRegularExpression>
#define d_ptr std::static_pointer_cast<SoftwareRAIDPrivate>(d)
class SoftwareRAIDPrivate : public VolumeManagerDevicePrivate
{
public:
qint32 m_raidLevel;
qint64 m_chunkSize;
qint64 m_totalChunk;
qint64 m_arraySize;
QString m_UUID;
QStringList m_devicePathList;
};
SoftwareRAID::SoftwareRAID(const QString& name, const QString& iconName)
: VolumeManagerDevice(std::make_shared<SoftwareRAIDPrivate>(),
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());
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;
}
void SoftwareRAID::scanSoftwareRAID(QList<Device*>& devices)
{
ExternalCommand scanRaid(QStringLiteral("cat"), { QStringLiteral("/proc/mdstat") });
if (scanRaid.run(-1) && scanRaid.exitCode() == 0) {
QRegularExpression re(QStringLiteral("md(\\d+)\\s+:"));
QRegularExpressionMatchIterator i = re.globalMatch(scanRaid.output());
while (i.hasNext()) {
QRegularExpressionMatch reMatch = i.next();
QString deviceNode = QStringLiteral("/dev/") + QStringLiteral("md") + reMatch.captured(1).trimmed();
Device* d = CoreBackendManager::self()->backend()->scanDevice(deviceNode);
if ( d )
devices << d;
}
}
}
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() * 1024;
}
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();
}
QStringList SoftwareRAID::getDevicePathList(const QString &path)
{
Q_UNUSED(path);
return {};
}
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(Report &report, const SoftwareRAID &raidDevice)
{
Q_UNUSED(report);
Q_UNUSED(raidDevice);
return false;
}
bool SoftwareRAID::stopSoftwareRAID(Report &report, const SoftwareRAID &raidDevice)
{
Q_UNUSED(report);
Q_UNUSED(raidDevice);
return false;
}
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("--detail"),
path });
return (cmd.run(-1) && cmd.exitCode() == 0) ? cmd.output() : QStringLiteral();
}

74
src/core/softwareraid.h Normal file
View File

@ -0,0 +1,74 @@
/*************************************************************************
* Copyright (C) 2018 by Caio Carvalho <caiojcarvalho@gmail.com> *
* *
* 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 <http://www.gnu.org/licenses/>.*
*************************************************************************/
#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());
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);
public:
static void scanSoftwareRAID(QList<Device*>& 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 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(Report& report,
const SoftwareRAID& raidDevice);
static bool stopSoftwareRAID(Report& report,
const SoftwareRAID& raidDevice);
protected:
void initPartitions() override;
qint64 mappedSector(const QString &partitionPath, qint64 sector) const override;
private:
static QString getDetail(const QString& path);
};
#endif // SOFTWARERAID_H

View File

@ -23,6 +23,7 @@
#include "core/diskdevice.h"
#include "core/lvmdevice.h"
#include "core/softwareraid.h"
#include "core/partitiontable.h"
#include "core/partitionalignment.h"
@ -106,6 +107,7 @@ QList<Device*> SfdiskBackend::scanDevices(bool excludeReadOnly)
}
LvmDevice::scanSystemLVM(result);
SoftwareRAID::scanSoftwareRAID(result);
}
return result;
@ -126,61 +128,50 @@ Device* SfdiskBackend::scanDevice(const QString& 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) )
if ( 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);
Device* d = nullptr;
qint64 deviceSize = sizeCommand.output().trimmed().toLongLong();
Log(Log::Level::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.rawOutput()).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:
if ( modelCommand.run(-1) && modelCommand.exitCode() == 0 && !modelCommand.output().trimmed().isEmpty() )
{
// 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);
QString modelName = modelCommand.output();
modelName = modelName.left(modelName.length() - 1);
Log(Log::Level::information) << xi18nc("@info:status", "Disk Device found: %1", modelName);
d = new DiskDevice(modelName, deviceNode, 255, 63, deviceSize / logicalSectorSize / 255 / 63, logicalSectorSize);
}
default:
break;
else // check for software raid
{
ExternalCommand softwareRaidCommand(QStringLiteral("mdadm"), { QStringLiteral("--detail"), deviceNode });
if ( softwareRaidCommand.run(-1) && softwareRaidCommand.exitCode() == 0 )
{
Log(Log::Level::information) << xi18nc("@info:status", "Software RAID Device found: %1", deviceNode);
QString deviceName = deviceNode.mid(5);
d = new SoftwareRAID( deviceName );
}
}
scanDevicePartitions(*d, partitionTable[QLatin1String("partitions")].toArray());
return d;
if ( d )
{
if (jsonCommand.exitCode() != 0)
return d;
const QJsonObject jsonObject = QJsonDocument::fromJson(jsonCommand.rawOutput()).object();
const QJsonObject partitionTable = jsonObject[QLatin1String("partitiontable")].toObject();
if (!updateDevicePartitionTable(*d, partitionTable))
return nullptr;
return d;
}
}
else
{
@ -291,6 +282,64 @@ void SfdiskBackend::scanDevicePartitions(Device& d, const QJsonArray& jsonPartit
PartitionAlignment::isAligned(d, *part);
}
bool SfdiskBackend::updateDevicePartitionTable(Device &d, const QJsonObject &jsonPartitionTable)
{
QString tableType = jsonPartitionTable[QLatin1String("label")].toString();
const PartitionTable::TableType type = PartitionTable::nameToTableType(tableType);
qint64 firstUsableSector = 0, lastUsableSector;
if ( d.type() == Device::Type::Disk_Device )
{
const DiskDevice* diskDevice = static_cast<const DiskDevice*>(&d);
lastUsableSector = diskDevice->totalSectors();
}
else if ( d.type() == Device::Type::SoftwareRAID_Device )
{
const SoftwareRAID* raidDevice = static_cast<const SoftwareRAID*>(&d);
lastUsableSector = raidDevice->totalLogical() - 1;
}
if (type == PartitionTable::gpt) {
firstUsableSector = jsonPartitionTable[QLatin1String("firstlba")].toVariant().toLongLong();
lastUsableSector = jsonPartitionTable[QLatin1String("lastlba")].toVariant().toLongLong();
}
if (lastUsableSector < firstUsableSector) {
return false;
}
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=") + d.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, jsonPartitionTable[QLatin1String("partitions")].toArray());
return true;
}
/** 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

View File

@ -58,6 +58,7 @@ public:
private:
static void readSectorsUsed(const Device& d, Partition& p, const QString& mountPoint);
void scanDevicePartitions(Device& d, const QJsonArray& jsonPartitions);
bool updateDevicePartitionTable(Device& d, const QJsonObject& jsonPartitionTable);
static PartitionTable::Flags availableFlags(PartitionTable::TableType type);
};