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 }