File indexing completed on 2024-05-19 05:48:51

0001 /*
0002     SPDX-FileCopyrightText: 2018 Caio Jordão Carvalho <caiojcarvalho@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-3.0-or-later
0005 */
0006 
0007 #include "softwareraid.h"
0008 
0009 #include "backend/corebackend.h"
0010 #include "backend/corebackendmanager.h"
0011 #include "core/partition.h"
0012 #include "core/volumemanagerdevice_p.h"
0013 #include "fs/filesystem.h"
0014 #include "fs/filesystemfactory.h"
0015 #include "util/externalcommand.h"
0016 
0017 #include <utility>
0018 
0019 #include <KLocalizedString>
0020 #include <QFile>
0021 #include <QRegularExpression>
0022 
0023 #define d_ptr std::static_pointer_cast<SoftwareRAIDPrivate>(d)
0024 
0025 class SoftwareRAIDPrivate : public VolumeManagerDevicePrivate
0026 {
0027 public:
0028     qint32 m_raidLevel;
0029     qint64 m_chunkSize;
0030     qint64 m_totalChunk;
0031     qint64 m_arraySize;
0032     QString m_UUID;
0033     QStringList m_devicePathList;
0034     QStringList m_partitionPathList;
0035     SoftwareRAID::Status m_status;
0036 };
0037 
0038 SoftwareRAID::SoftwareRAID(const QString& name, SoftwareRAID::Status status, const QString& iconName)
0039     : VolumeManagerDevice(std::make_shared<SoftwareRAIDPrivate>(),
0040                           name,
0041                           (QStringLiteral("/dev/") + name),
0042                           getChunkSize(QStringLiteral("/dev/") + name),
0043                           getTotalChunk(QStringLiteral("/dev/") + name),
0044                           iconName,
0045                           Device::Type::SoftwareRAID_Device)
0046 {
0047     d_ptr->m_raidLevel = getRaidLevel(deviceNode());
0048     d_ptr->m_chunkSize = logicalSize();
0049     d_ptr->m_totalChunk = totalLogical();
0050     d_ptr->m_arraySize = getArraySize(deviceNode());
0051     d_ptr->m_UUID = getUUID(deviceNode());
0052     d_ptr->m_devicePathList = getDevicePathList(deviceNode());
0053     d_ptr->m_status = status;
0054 
0055     initPartitions();
0056 }
0057 
0058 const QStringList SoftwareRAID::deviceNodes() const
0059 {
0060     return d_ptr->m_devicePathList;
0061 }
0062 
0063 const QStringList& SoftwareRAID::partitionNodes() const
0064 {
0065     return d_ptr->m_partitionPathList;
0066 }
0067 
0068 qint64 SoftwareRAID::partitionSize(QString &partitionPath) const
0069 {
0070     Q_UNUSED(partitionPath)
0071     return 0;
0072 }
0073 
0074 bool SoftwareRAID::growArray(Report &report, const QStringList &devices)
0075 {
0076     Q_UNUSED(report)
0077     Q_UNUSED(devices)
0078     return false;
0079 }
0080 
0081 bool SoftwareRAID::shrinkArray(Report &report, const QStringList &devices)
0082 {
0083     Q_UNUSED(report)
0084     Q_UNUSED(devices)
0085     return false;
0086 }
0087 
0088 QString SoftwareRAID::prettyName() const
0089 {
0090     QString raidInfo;
0091 
0092     if (status() == SoftwareRAID::Status::Active)
0093         raidInfo = xi18nc("@item:inlistbox [RAID level]", " [RAID %1]", raidLevel());
0094     else if (status() == SoftwareRAID::Status::Recovery)
0095         raidInfo = xi18nc("@item:inlistbox [RAID level - Recovering]", " [RAID %1 - Recovering]", raidLevel());
0096     else if (status() == SoftwareRAID::Status::Resync)
0097         raidInfo = xi18nc("@item:inlistbox [RAID level - Resyncing]", " [RAID %1 - Resyncing]", raidLevel());
0098     else
0099         raidInfo = QStringLiteral(" [RAID]");
0100 
0101     return VolumeManagerDevice::prettyName() + raidInfo;
0102 }
0103 
0104 bool SoftwareRAID::operator ==(const Device& other) const
0105 {
0106     bool equalDeviceNode = Device::operator ==(other);
0107 
0108     if (other.type() == Device::Type::SoftwareRAID_Device) {
0109         const SoftwareRAID& raid = static_cast<const SoftwareRAID&>(other);
0110 
0111         if (!equalDeviceNode)
0112             return raid.uuid() == uuid();
0113     }
0114 
0115     return equalDeviceNode;
0116 }
0117 
0118 qint32 SoftwareRAID::raidLevel() const
0119 {
0120     return d_ptr->m_raidLevel;
0121 }
0122 
0123 qint64 SoftwareRAID::chunkSize() const
0124 {
0125     return d_ptr->m_chunkSize;
0126 }
0127 
0128 qint64 SoftwareRAID::totalChunk() const
0129 {
0130     return d_ptr->m_totalChunk;
0131 }
0132 
0133 qint64 SoftwareRAID::arraySize() const
0134 {
0135     return d_ptr->m_arraySize;
0136 }
0137 
0138 QString SoftwareRAID::uuid() const
0139 {
0140     return d_ptr->m_UUID;
0141 }
0142 
0143 QStringList SoftwareRAID::devicePathList() const
0144 {
0145     return d_ptr->m_devicePathList;
0146 }
0147 
0148 SoftwareRAID::Status SoftwareRAID::status() const
0149 {
0150     return d_ptr->m_status;
0151 }
0152 
0153 void SoftwareRAID::setStatus(SoftwareRAID::Status status)
0154 {
0155     d_ptr->m_status = status;
0156 }
0157 
0158 void SoftwareRAID::scanSoftwareRAID(QList<Device*>& devices)
0159 {
0160     QStringList availableInConf;
0161 
0162     // TODO: Support custom config files.
0163     QString config = getRAIDConfiguration(QStringLiteral("/etc/mdadm.conf"));
0164 
0165     if (!config.isEmpty()) {
0166         QRegularExpression re(QStringLiteral("([\\t\\r\\n\\f\\s]|INACTIVE-)ARRAY \\/dev\\/([\\/\\w-]+)"));
0167         QRegularExpressionMatchIterator i  = re.globalMatch(config);
0168 
0169         while (i.hasNext()) {
0170             QRegularExpressionMatch reMatch = i.next();
0171             QString deviceName = reMatch.captured(2).trimmed();
0172 
0173             availableInConf << deviceName;
0174         }
0175     }
0176 
0177     QFile mdstat(QStringLiteral("/proc/mdstat"));
0178 
0179     if (mdstat.open(QIODevice::ReadOnly)) {
0180         QTextStream stream(&mdstat);
0181 
0182         QString content = stream.readAll();
0183 
0184         mdstat.close();
0185 
0186         QRegularExpression re(QStringLiteral("md([\\/\\w]+)\\s+:\\s+([\\w]+)"));
0187         QRegularExpressionMatchIterator i  = re.globalMatch(content);
0188         while (i.hasNext()) {
0189             QRegularExpressionMatch reMatch = i.next();
0190 
0191             QString deviceNode = QStringLiteral("/dev/md") + reMatch.captured(1).trimmed();
0192             QString status = reMatch.captured(2).trimmed();
0193 
0194             SoftwareRAID* d = static_cast<SoftwareRAID *>(CoreBackendManager::self()->backend()->scanDevice(deviceNode));
0195 
0196             // Just to prevent segfault in some case
0197             if (d == nullptr)
0198                 continue;
0199 
0200             const QStringList constAvailableInConf = availableInConf;
0201 
0202             for (const QString& path : constAvailableInConf)
0203                 if (getUUID(QStringLiteral("/dev/") + path) == d->uuid())
0204                     availableInConf.removeAll(path);
0205 
0206             devices << d;
0207 
0208             if (status == QStringLiteral("inactive"))
0209                 d->setStatus(SoftwareRAID::Status::Inactive);
0210 
0211             if (d->raidLevel() > 0) {
0212                 QRegularExpression reMirrorStatus(d->name() + QStringLiteral("\\s+:\\s+(.*\\n\\s+)+\\[[=>.]+\\]\\s+(resync|recovery)"));
0213 
0214                 QRegularExpressionMatch reMirrorStatusMatch = reMirrorStatus.match(content);
0215 
0216                 if (reMirrorStatusMatch.hasMatch()) {
0217                     if (reMirrorStatusMatch.captured(2) == QStringLiteral("resync"))
0218                         d->setStatus(SoftwareRAID::Status::Resync);
0219                     else if (reMirrorStatusMatch.captured(2) == QStringLiteral("recovery"))
0220                         d->setStatus(SoftwareRAID::Status::Recovery);
0221                 }
0222             }
0223         }
0224     }
0225 
0226     for (const QString& name : std::as_const(availableInConf)) {
0227         SoftwareRAID *raidDevice = new SoftwareRAID(name, SoftwareRAID::Status::Inactive);
0228         devices << raidDevice;
0229     }
0230 }
0231 
0232 qint32 SoftwareRAID::getRaidLevel(const QString &path)
0233 {
0234     QString output = getDetail(path);
0235 
0236     if (!output.isEmpty()) {
0237         QRegularExpression re(QStringLiteral("Raid Level :\\s+\\w+(\\d+)"));
0238         QRegularExpressionMatch reMatch = re.match(output);
0239         if (reMatch.hasMatch())
0240             return reMatch.captured(1).toLongLong();
0241     }
0242 
0243     return -1;
0244 }
0245 
0246 qint64 SoftwareRAID::getChunkSize(const QString &path)
0247 {
0248     if (getRaidLevel(path) == 1) {
0249         QStringList devices = getDevicePathList(path);
0250 
0251         if (!devices.isEmpty()) {
0252             QString device = devices[0];
0253             // Look sector size for the first device/partition on the list, as RAID 1 is composed by mirrored devices
0254             ExternalCommand sectorSize(QStringLiteral("blockdev"), { QStringLiteral("--getss"), device });
0255 
0256             if (sectorSize.run(-1) && sectorSize.exitCode() == 0) {
0257                 int sectors = sectorSize.output().trimmed().toLongLong();
0258                 return sectors;
0259             }
0260         }
0261     }
0262     else {
0263         QString output = getDetail(path);
0264         if (!output.isEmpty()) {
0265             QRegularExpression re(QStringLiteral("Chunk Size :\\s+(\\d+)"));
0266             QRegularExpressionMatch reMatch = re.match(output);
0267             if (reMatch.hasMatch())
0268                 return reMatch.captured(1).toLongLong();
0269         }
0270     }
0271     return -1;
0272 
0273 }
0274 
0275 qint64 SoftwareRAID::getTotalChunk(const QString &path)
0276 {
0277     return getArraySize(path) / getChunkSize(path);
0278 }
0279 
0280 qint64 SoftwareRAID::getArraySize(const QString &path)
0281 {
0282     QString output = getDetail(path);
0283     if (!output.isEmpty()) {
0284         QRegularExpression re(QStringLiteral("Array Size :\\s+(\\d+)"));
0285         QRegularExpressionMatch reMatch = re.match(output);
0286         if (reMatch.hasMatch())
0287             return reMatch.captured(1).toLongLong() * 1024;
0288     }
0289     return -1;
0290 
0291 }
0292 
0293 QString SoftwareRAID::getUUID(const QString &path)
0294 {
0295     QString output = getDetail(path);
0296 
0297     if (!output.isEmpty()) {
0298         QRegularExpression re(QStringLiteral("UUID :\\s+([\\w:]+)"));
0299         QRegularExpressionMatch reMatch = re.match(output);
0300 
0301         if (reMatch.hasMatch())
0302             return reMatch.captured(1);
0303     }
0304 
0305     // If UUID was not found in detail output, it should be searched in config file
0306 
0307     // TODO: Support custom config files.
0308     QString config = getRAIDConfiguration(QStringLiteral("/etc/mdadm.conf"));
0309 
0310     if (!config.isEmpty()) {
0311         QRegularExpression re(QStringLiteral("([\\t\\r\\n\\f\\s]|INACTIVE-)ARRAY \\/dev\\/md([\\/\\w-]+)(.*)"));
0312         QRegularExpressionMatchIterator i  = re.globalMatch(config);
0313 
0314         while (i.hasNext()) {
0315             QRegularExpressionMatch reMatch = i.next();
0316             QString deviceNode = QStringLiteral("/dev/md") + reMatch.captured(2).trimmed();
0317             QString otherInfo = reMatch.captured(3).trimmed();
0318 
0319             // Consider device node as name=host:deviceNode when the captured device node string has '-' character
0320             // It happens when user have included the device to config file using 'mdadm --examine --scan'
0321             if (deviceNode.contains(QLatin1Char('-'))) {
0322                 QRegularExpression reName(QStringLiteral("name=[\\w:]+\\/dev\\/md\\/([\\/\\w]+)"));
0323                 QRegularExpressionMatch nameMatch = reName.match(otherInfo);
0324 
0325                 if (nameMatch.hasMatch())
0326                     deviceNode = nameMatch.captured(1);
0327             }
0328 
0329             if (deviceNode == path) {
0330                 QRegularExpression reUUID(QStringLiteral("(UUID=|uuid=)([\\w:]+)"));
0331                 QRegularExpressionMatch uuidMatch = reUUID.match(otherInfo);
0332 
0333                 if (uuidMatch.hasMatch())
0334                     return uuidMatch.captured(2);
0335             }
0336         }
0337     }
0338 
0339     return QString();
0340 }
0341 
0342 QStringList SoftwareRAID::getDevicePathList(const QString &path)
0343 {
0344     QStringList result;
0345 
0346     QString detail = getDetail(path);
0347 
0348     if (!detail.isEmpty()) {
0349         QRegularExpression re(QStringLiteral("\\s+\\/dev\\/(\\w+)"));
0350         QRegularExpressionMatchIterator i = re.globalMatch(detail);
0351 
0352         while (i.hasNext()) {
0353             QRegularExpressionMatch match = i.next();
0354 
0355             QString device = QStringLiteral("/dev/") + match.captured(1);
0356             if (device != path)
0357                 result << device;
0358         }
0359     }
0360 
0361     return result;
0362 }
0363 
0364 bool SoftwareRAID::isRaidPath(const QString &path)
0365 {
0366     return !getDetail(path).isEmpty();
0367 }
0368 
0369 bool SoftwareRAID::createSoftwareRAID(Report &report,
0370                                       const QString &name,
0371                                       const QStringList devicePathList,
0372                                       const qint32 raidLevel,
0373                                       const qint32 chunkSize)
0374 {
0375     Q_UNUSED(report)
0376     Q_UNUSED(name)
0377     Q_UNUSED(devicePathList)
0378     Q_UNUSED(raidLevel)
0379     Q_UNUSED(chunkSize)
0380     return false;
0381 }
0382 
0383 bool SoftwareRAID::deleteSoftwareRAID(Report &report,
0384                                       SoftwareRAID &raidDevice)
0385 {
0386     Q_UNUSED(report)
0387     Q_UNUSED(raidDevice)
0388     return false;
0389 }
0390 
0391 bool SoftwareRAID::assembleSoftwareRAID(const QString& deviceNode)
0392 {
0393     if (!isRaidPath(deviceNode))
0394         return false;
0395 
0396     ExternalCommand cmd(QStringLiteral("mdadm"),
0397                         { QStringLiteral("--assemble"), QStringLiteral("--scan"), deviceNode });
0398 
0399     return cmd.run(-1) && cmd.exitCode() == 0;
0400 }
0401 
0402 bool SoftwareRAID::stopSoftwareRAID(const QString& deviceNode)
0403 {
0404     if (!isRaidPath(deviceNode))
0405         return false;
0406 
0407     ExternalCommand cmd(QStringLiteral("mdadm"),
0408                         { QStringLiteral("--manage"), QStringLiteral("--stop"), deviceNode });
0409 
0410     return cmd.run(-1) && cmd.exitCode() == 0;
0411 }
0412 
0413 bool SoftwareRAID::reassembleSoftwareRAID(const QString &deviceNode)
0414 {
0415     return stopSoftwareRAID(deviceNode) && assembleSoftwareRAID(deviceNode);
0416 }
0417 
0418 bool SoftwareRAID::isRaidMember(const QString &path)
0419 {
0420     QFile mdstat(QStringLiteral("/proc/mdstat"));
0421 
0422     if (!mdstat.open(QIODevice::ReadOnly))
0423         return false;
0424 
0425     QTextStream stream(&mdstat);
0426 
0427     QString content = stream.readAll();
0428 
0429     mdstat.close();
0430 
0431     QRegularExpression re(QStringLiteral("(\\w+)\\[\\d+\\]"));
0432     QRegularExpressionMatchIterator i  = re.globalMatch(content);
0433 
0434     while (i.hasNext()) {
0435         QRegularExpressionMatch reMatch = i.next();
0436 
0437         QString match = QStringLiteral("/dev/") + reMatch.captured(1);
0438 
0439         if (match == path)
0440             return true;
0441     }
0442 
0443     return false;
0444 }
0445 
0446 void SoftwareRAID::initPartitions()
0447 {
0448 
0449 }
0450 
0451 qint64 SoftwareRAID::mappedSector(const QString &partitionPath, qint64 sector) const
0452 {
0453     Q_UNUSED(partitionPath)
0454     Q_UNUSED(sector)
0455     return -1;
0456 }
0457 
0458 QString SoftwareRAID::getDetail(const QString &path)
0459 {
0460     ExternalCommand cmd(QStringLiteral("mdadm"),
0461                        { QStringLiteral("--misc"), QStringLiteral("--detail"), path });
0462     return (cmd.run(-1) && cmd.exitCode() == 0) ? cmd.output() : QString();
0463 }
0464 
0465 QString SoftwareRAID::getRAIDConfiguration(const QString &configurationPath)
0466 {
0467     QFile config(configurationPath);
0468 
0469     if (!config.open(QIODevice::ReadOnly))
0470         return QString();
0471 
0472     QTextStream stream(&config);
0473 
0474     QString result = stream.readAll();
0475 
0476     config.close();
0477 
0478     return result;
0479 }