File indexing completed on 2024-05-05 05:48:40
0001 /* 0002 SPDX-FileCopyrightText: 2017-2020 Andrius Štikonas <andrius@stikonas.eu> 0003 SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org> 0004 0005 SPDX-License-Identifier: GPL-3.0-or-later 0006 */ 0007 0008 #include "core/fstab.h" 0009 #include "util/externalcommand.h" 0010 #include "util/report.h" 0011 0012 #include <algorithm> 0013 #include <array> 0014 0015 #if defined(Q_OS_LINUX) 0016 #include <blkid/blkid.h> 0017 #endif 0018 0019 #include <QChar> 0020 #include <QDebug> 0021 #include <QFileInfo> 0022 #include <QRegularExpression> 0023 #include <QTemporaryFile> 0024 #include <QTextStream> 0025 0026 using namespace Qt::StringLiterals; 0027 0028 static void parseFsSpec(const QString& m_fsSpec, FstabEntry::Type& m_entryType, QString& m_deviceNode); 0029 static QString findBlkIdDevice(const char *token, const QString& value); 0030 static void writeEntry(QTextStream& s, const FstabEntry& entry, std::array<unsigned int, 4> columnWidth); 0031 std::array<unsigned int, 4> fstabColumnWidth(const FstabEntryList& fstabEntries); 0032 0033 struct FstabEntryPrivate 0034 { 0035 QString m_fsSpec; 0036 QString m_deviceNode; 0037 QString m_mountPoint; 0038 QString m_type; 0039 QStringList m_options; 0040 int m_dumpFreq; 0041 int m_passNumber; 0042 QString m_comment; 0043 FstabEntry::Type m_entryType; 0044 }; 0045 0046 FstabEntry::FstabEntry(const QString& fsSpec, const QString& mountPoint, const QString& type, const QString& options, int dumpFreq, int passNumber, const QString& comment) : 0047 d(std::make_unique<FstabEntryPrivate>()) 0048 { 0049 d->m_fsSpec = fsSpec; 0050 d->m_mountPoint = mountPoint; 0051 d->m_type = type; 0052 d->m_dumpFreq = dumpFreq; 0053 d->m_passNumber = passNumber; 0054 d->m_comment = comment; 0055 0056 d->m_options = options.split(QLatin1Char(',')); 0057 d->m_options.removeAll(QStringLiteral("defaults")); 0058 parseFsSpec(d->m_fsSpec, d->m_entryType, d->m_deviceNode); 0059 } 0060 0061 FstabEntryList readFstabEntries( const QString& fstabPath ) 0062 { 0063 FstabEntryList fstabEntries; 0064 QFile fstabFile( fstabPath ); 0065 if ( fstabFile.open( QIODevice::ReadOnly | QIODevice::Text ) ) 0066 { 0067 const QStringList fstabLines = QString::fromLocal8Bit(fstabFile.readAll()).split( QLatin1Char('\n') ); 0068 for ( const QString& rawLine : fstabLines ) 0069 { 0070 QString line = rawLine.trimmed(); 0071 if ( line.startsWith( QLatin1Char('#') ) || line.isEmpty()) { 0072 fstabEntries.push_back( { {}, {}, {}, {}, {}, {}, line } ); 0073 continue; 0074 } 0075 0076 QString comment = line.section( QLatin1Char('#'), 1 ); 0077 QStringList splitLine = line.section( QLatin1Char('#'), 0, 0 ).split( QRegularExpression(QStringLiteral("[\\s]+")), Qt::SkipEmptyParts ); 0078 0079 // We now split the standard components of /etc/fstab entry: 0080 // (0) path, or UUID, or LABEL, etc, 0081 // (1) mount point, 0082 // (2) file system type, 0083 // (3) options, 0084 // (4) dump frequency (optional, defaults to 0), no comment is allowed if omitted, 0085 // (5) pass number (optional, defaults to 0), no comment is allowed if omitted, 0086 // (#) comment (optional). 0087 0088 // Handle deprecated subtypes, e.g. sshfs#example. They are not relevant for partitioning, ignore them. 0089 if (splitLine.size() < 3) { 0090 continue; 0091 } 0092 0093 auto fsSpec = splitLine.at(0); 0094 auto mountPoint = unescapeSpaces(splitLine.at(1)); 0095 auto fsType = splitLine.at(2); 0096 // Options may be omitted in some rare cases like NixOS generated fstab. 0097 auto options = splitLine.length() >= 4 ? splitLine.at(3) : QStringLiteral("defaults"); 0098 0099 switch (splitLine.length()) { 0100 case 4: 0101 fstabEntries.push_back( {fsSpec, mountPoint, fsType, options } ); 0102 break; 0103 case 5: 0104 fstabEntries.push_back( {fsSpec, mountPoint, fsType, options, splitLine.at(4).toInt() } ); 0105 break; 0106 case 6: 0107 fstabEntries.push_back( {fsSpec, mountPoint, fsType, options, splitLine.at(4).toInt(), splitLine.at(5).toInt(), comment.isEmpty() ? QString() : QLatin1Char('#') + comment } ); 0108 break; 0109 default: 0110 fstabEntries.push_back( { {}, {}, {}, {}, {}, {}, QLatin1Char('#') + line } ); 0111 } 0112 } 0113 0114 fstabFile.close(); 0115 if (fstabEntries.back().entryType() == FstabEntry::Type::comment && fstabEntries.back().comment().isEmpty()) 0116 fstabEntries.pop_back(); 0117 } 0118 0119 return fstabEntries; 0120 } 0121 0122 QString escapeSpaces(const QString& mountPoint) 0123 { 0124 QString tmp = mountPoint; 0125 tmp.replace(QStringLiteral(" "), QStringLiteral("\\040")); 0126 tmp.replace(QStringLiteral("\t"), QStringLiteral("\\011")); 0127 return tmp; 0128 } 0129 0130 QString unescapeSpaces(const QString& mountPoint) 0131 { 0132 QString tmp = mountPoint; 0133 tmp.replace(QStringLiteral("\\040"), QStringLiteral(" ")); 0134 tmp.replace(QStringLiteral("\\011"), QStringLiteral("\t")); 0135 return tmp; 0136 } 0137 0138 void FstabEntry::setFsSpec(const QString& s) 0139 { 0140 d->m_fsSpec = s; 0141 parseFsSpec(d->m_fsSpec, d->m_entryType, d->m_deviceNode); 0142 } 0143 0144 const QString& FstabEntry::fsSpec() const 0145 { 0146 return d->m_fsSpec; 0147 } 0148 0149 const QString& FstabEntry::deviceNode() const 0150 { 0151 return d->m_deviceNode; 0152 } 0153 0154 const QString& FstabEntry::mountPoint() const 0155 { 0156 return d->m_mountPoint; 0157 } 0158 0159 const QString& FstabEntry::type() const 0160 { 0161 return d->m_type; 0162 } 0163 0164 const QStringList& FstabEntry::options() const 0165 { 0166 return d->m_options; 0167 } 0168 0169 const QString FstabEntry::optionsString() const 0170 { 0171 return options().size() > 0 ? options().join(QLatin1Char(',')) : QStringLiteral("defaults"); 0172 } 0173 0174 int FstabEntry::dumpFreq() const 0175 { 0176 return d->m_dumpFreq; 0177 } 0178 0179 int FstabEntry::passNumber() const 0180 { 0181 return d->m_passNumber; 0182 } 0183 0184 const QString& FstabEntry::comment() const 0185 { 0186 return d->m_comment; 0187 } 0188 0189 FstabEntry::Type FstabEntry::entryType() const 0190 { 0191 return d->m_entryType; 0192 } 0193 0194 void FstabEntry::setMountPoint(const QString& s) 0195 { 0196 d->m_mountPoint = s; 0197 } 0198 0199 void FstabEntry::setOptions(const QStringList& s) 0200 { 0201 d->m_options = s; 0202 } 0203 0204 void FstabEntry::setDumpFreq(int s) 0205 { 0206 d->m_dumpFreq = s; 0207 } 0208 0209 void FstabEntry::setPassNumber(int s) 0210 { 0211 d->m_passNumber = s; 0212 } 0213 0214 QStringList possibleMountPoints(const QString& deviceNode, const QString& fstabPath) 0215 { 0216 QStringList mountPoints; 0217 QString canonicalPath = QFileInfo(deviceNode).canonicalFilePath(); 0218 const FstabEntryList fstabEntryList = readFstabEntries( fstabPath ); 0219 for (const FstabEntry &entry : fstabEntryList) 0220 if (QFileInfo(entry.deviceNode()).canonicalFilePath() == canonicalPath) 0221 mountPoints.append(entry.mountPoint()); 0222 0223 return mountPoints; 0224 } 0225 0226 static QString findBlkIdDevice(const char *token, const QString& value) 0227 { 0228 QString rval; 0229 0230 #if defined(Q_OS_LINUX) 0231 if (char* c = blkid_evaluate_tag(token, value.toLocal8Bit().constData(), nullptr)) { 0232 rval = QString::fromLocal8Bit(c); 0233 free(c); 0234 } 0235 #else 0236 Q_UNUSED(token) 0237 Q_UNUSED(value) 0238 #endif 0239 0240 return rval; 0241 } 0242 0243 static void parseFsSpec(const QString& m_fsSpec, FstabEntry::Type& m_entryType, QString& m_deviceNode) 0244 { 0245 m_entryType = FstabEntry::Type::other; 0246 m_deviceNode = m_fsSpec; 0247 if (m_fsSpec.startsWith(QStringLiteral("UUID="))) { 0248 m_entryType = FstabEntry::Type::uuid; 0249 m_deviceNode = findBlkIdDevice("UUID", QString(m_fsSpec).remove(QStringLiteral("UUID="))); 0250 } else if (m_fsSpec.startsWith(QStringLiteral("LABEL="))) { 0251 m_entryType = FstabEntry::Type::label; 0252 m_deviceNode = findBlkIdDevice("LABEL", QString(m_fsSpec).remove(QStringLiteral("LABEL="))); 0253 } else if (m_fsSpec.startsWith(QStringLiteral("PARTUUID="))) { 0254 m_entryType = FstabEntry::Type::uuid; 0255 m_deviceNode = findBlkIdDevice("PARTUUID", QString(m_fsSpec).remove(QStringLiteral("PARTUUID="))); 0256 } else if (m_fsSpec.startsWith(QStringLiteral("PARTLABEL="))) { 0257 m_entryType = FstabEntry::Type::label; 0258 m_deviceNode = findBlkIdDevice("PARTLABEL", QString(m_fsSpec).remove(QStringLiteral("PARTLABEL="))); 0259 } else if (m_fsSpec.startsWith(QStringLiteral("/"))) { 0260 m_entryType = FstabEntry::Type::deviceNode; 0261 } else if (m_fsSpec.isEmpty()) { 0262 m_entryType = FstabEntry::Type::comment; 0263 } 0264 } 0265 0266 0267 // Used to nicely format fstab file 0268 std::array<unsigned int, 4> fstabColumnWidth(const FstabEntryList& fstabEntries) 0269 { 0270 std::array<unsigned int, 4> columnWidth; 0271 0272 #define FIELD_WIDTH(x) 3 + escapeSpaces(std::max_element(fstabEntries.begin(), fstabEntries.end(), [](const FstabEntry& a, const FstabEntry& b) {return escapeSpaces(a.x()).length() < escapeSpaces(b.x()).length(); })->x()).length(); 0273 0274 columnWidth[0] = FIELD_WIDTH(fsSpec); 0275 columnWidth[1] = FIELD_WIDTH(mountPoint); 0276 columnWidth[2] = FIELD_WIDTH(type); 0277 columnWidth[3] = FIELD_WIDTH(optionsString); 0278 0279 return columnWidth; 0280 } 0281 0282 static void writeEntry(QTextStream& s, const FstabEntry& entry, std::array<unsigned int, 4> columnWidth) 0283 { 0284 if (entry.entryType() == FstabEntry::Type::comment) { 0285 s << entry.comment() << "\n"; 0286 return; 0287 } 0288 0289 // "none" is only valid as mount point for swap partitions 0290 if ((entry.mountPoint().isEmpty() || entry.mountPoint() == u"none"_s) && entry.type() != QStringLiteral("swap")) { 0291 return; 0292 } 0293 0294 s.setFieldAlignment(QTextStream::AlignLeft); 0295 s.setFieldWidth(columnWidth[0]); 0296 s << entry.fsSpec() 0297 << qSetFieldWidth(columnWidth[1]) << (entry.mountPoint().isEmpty() ? QStringLiteral("none") : escapeSpaces(entry.mountPoint())) 0298 << qSetFieldWidth(columnWidth[2]) << entry.type() 0299 << qSetFieldWidth(columnWidth[3]) << entry.optionsString() << qSetFieldWidth(0) 0300 << entry.dumpFreq() << " " 0301 << entry.passNumber() << " " 0302 << entry.comment() << "\n"; 0303 } 0304 0305 QString generateFstab(const FstabEntryList& fstabEntries) 0306 { 0307 QString fstabContents; 0308 QTextStream out(&fstabContents); 0309 0310 std::array<unsigned int, 4> columnWidth = fstabColumnWidth(fstabEntries); 0311 0312 for (const auto &e : fstabEntries) 0313 writeEntry(out, e, columnWidth); 0314 0315 out.flush(); 0316 return fstabContents; 0317 } 0318 0319 bool writeMountpoints(const FstabEntryList& fstabEntries) 0320 { 0321 auto fstab = generateFstab(fstabEntries); 0322 0323 ExternalCommand cmd; 0324 return cmd.writeFstab(fstab.toLocal8Bit()); 0325 }