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 }