// Copyright (C) 2021 The Qt Company Ltd. // Copyright (C) 2014 Ivan Komissarov // Copyright (C) 2016 Intel Corporation. // Copyright (C) 2023 Ahmad Samir // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #ifndef QSTORAGEINFO_LINUX_P_H #define QSTORAGEINFO_LINUX_P_H // // W A R N I N G // ------------- // // This file is not part of the Qt API. // This header file may change from version to // version without notice, or even be removed. // // We mean it. // #include "qstorageinfo_p.h" #include #include #include // makedev() QT_BEGIN_NAMESPACE using namespace Qt::StringLiterals; using MountInfo = QStorageInfoPrivate::MountInfo; static const char MountInfoPath[] = "/proc/self/mountinfo"; static std::optional deviceNumber(QByteArrayView devno) { // major:minor auto it = devno.cbegin(); auto r = qstrntoll(it, devno.size(), 10); if (!r.ok()) return std::nullopt; int rdevmajor = int(r.result); it += r.used; if (*it != ':') return std::nullopt; r = qstrntoll(++it, devno.size() - r.used + 1, 10); if (!r.ok()) return std::nullopt; return makedev(rdevmajor, r.result); } // Helper function to parse paths that the kernel inserts escape sequences // for. static QByteArray parseMangledPath(QByteArrayView path) { // The kernel escapes with octal the following characters: // space ' ', tab '\t', backslash '\\', and newline '\n' // See: // https://codebrowser.dev/linux/linux/fs/proc_namespace.c.html#show_mountinfo // https://codebrowser.dev/linux/linux/fs/seq_file.c.html#mangle_path QByteArray ret(path.size(), '\0'); char *dst = ret.data(); const char *src = path.data(); const char *srcEnd = path.data() + path.size(); while (src != srcEnd) { switch (*src) { case ' ': // Shouldn't happen return {}; case '\\': { // It always uses exactly three octal characters. ++src; char c = (*src++ - '0') << 6; c |= (*src++ - '0') << 3; c |= (*src++ - '0'); *dst++ = c; break; } default: *dst++ = *src++; break; } } // If "path" contains any of the characters this method is demangling, // "ret" would be oversized with extra '\0' characters at the end. ret.resize(dst - ret.data()); return ret; } // Indexes into the "fields" std::array in parseMountInfo() // static constexpr short MountId = 0; // static constexpr short ParentId = 1; static constexpr short DevNo = 2; static constexpr short FsRoot = 3; static constexpr short MountPoint = 4; static constexpr short MountOptions = 5; // static constexpr short OptionalFields = 6; // static constexpr short Separator = 7; static constexpr short FsType = 8; static constexpr short MountSource = 9; static constexpr short SuperOptions = 10; static constexpr short FieldCount = 11; // Splits a line from /proc/self/mountinfo into fields; fields are separated // by a single space. static void tokenizeLine(std::array &fields, QByteArrayView line) { size_t fieldIndex = 0; qsizetype from = 0; const char *begin = line.data(); const qsizetype len = line.size(); qsizetype spaceIndex = -1; while ((spaceIndex = line.indexOf(' ', from)) != -1 && fieldIndex < FieldCount) { fields[fieldIndex] = QByteArrayView{begin + from, begin + spaceIndex}; from = spaceIndex; // Skip "OptionalFields" and Separator fields if (fieldIndex == MountOptions) { static constexpr char separatorField[] = " - "; const qsizetype sepIndex = line.indexOf(separatorField, from); if (sepIndex == -1) { qCWarning(lcStorageInfo, "Malformed line (missing '-' separator field) while parsing '%s':\n%s", MountInfoPath, line.constData()); fields.fill({}); return; } from = sepIndex + strlen(separatorField); // Continue parsing at FsType field fieldIndex = FsType; continue; } if (from + 1 < len) ++from; // Skip the space at spaceIndex ++fieldIndex; } // Currently we don't use the last field, so just check the index if (fieldIndex != SuperOptions) { qCInfo(lcStorageInfo, "Expected %d fields while parsing line from '%s', but found %zu instead:\n%.*s", FieldCount, MountInfoPath, fieldIndex, int(line.size()), line.data()); fields.fill({}); } } // parseMountInfo() is called from: // - QStorageInfoPrivate::initRootPath(), where a list of all mounted volumes is needed // - QStorageInfoPrivate::mountedVolumes(), where some filesystem types are ignored // (see shouldIncludefs()) enum class FilterMountInfo { All, Filtered, }; [[maybe_unused]] static std::vector doParseMountInfo(const QByteArray &mountinfo, FilterMountInfo filter = FilterMountInfo::All) { // https://www.kernel.org/doc/Documentation/filesystems/proc.txt: // 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue // (1)(2)(3) (4) (5) (6) (7) (8) (9) (10) (11) auto it = mountinfo.cbegin(); const auto end = mountinfo.cend(); auto nextLine = [&it, &end]() -> QByteArrayView { auto nIt = std::find(it, end, '\n'); if (nIt != end) { QByteArrayView ba(it, nIt); it = ++nIt; // Advance return ba; } return {}; }; std::vector infos; std::array fields; QByteArrayView line; auto checkField = [&line](QByteArrayView field) { if (field.isEmpty()) { qDebug("Failed to parse line from %s:\n%.*s", MountInfoPath, int(line.size()), line.data()); return false; } return true; }; // mountinfo has a stable format, no empty lines while (!(line = nextLine()).isEmpty()) { fields.fill({}); tokenizeLine(fields, line); MountInfo info; QByteArray mountP = parseMangledPath(fields[MountPoint]); if (!checkField(mountP)) continue; info.mountPoint = QFile::decodeName(mountP); if (!checkField(fields[FsType])) continue; info.fsType = fields[FsType].toByteArray(); if (filter == FilterMountInfo::Filtered && !shouldIncludeFs(info.mountPoint, info.fsType)) continue; std::optional devno = deviceNumber(fields[DevNo]); if (!devno) { checkField({}); continue; } info.stDev = *devno; QByteArrayView fsRootView = fields[FsRoot]; if (!checkField(fsRootView)) continue; // If the filesystem root is "/" -- it's not a *sub*-volume/bind-mount, // in that case we leave info.fsRoot empty if (fsRootView != "/") { info.fsRoot = parseMangledPath(fsRootView); if (!checkField(info.fsRoot)) continue; } info.device = parseMangledPath(fields[MountSource]); if (!checkField(info.device)) continue; infos.push_back(std::move(info)); } return infos; } QT_END_NAMESPACE #endif // QSTORAGEINFO_LINUX_P_H