QStorageInfo/Linux: use readAll() with /proc/self/mountinfo

This minimizes any multi-threading / file-locking issues as the file is
closed once the contents are read.

This change assumes /proc/self/mountinfo is available on Linux systems,
and doesn't fallback to setmntent(). It's been around since at least
Linux Kernel 2.4.0.

This requires exporting qstrntoll() for the unittests (using
QT_AUTOTEST_EXPORT and wrapping the those unittests in "#ifdef
QT_BUILD_INTERNAL"), otherwise linking fails.

Fixes: QTBUG-77059
Change-Id: I0363258a9979ea6dadfe5e36c02534ffbd3386c5
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Axel Spoerl <axel.spoerl@qt.io>
This commit is contained in:
Ahmad Samir 2023-05-01 03:48:24 +03:00
parent 74bd7a7019
commit 543ae6e6a4
5 changed files with 583 additions and 296 deletions

View File

@ -0,0 +1,253 @@
// Copyright (C) 2021 The Qt Company Ltd.
// Copyright (C) 2014 Ivan Komissarov <ABBAPOH@gmail.com>
// Copyright (C) 2016 Intel Corporation.
// Copyright (C) 2023 Ahmad Samir <a.samirh78@gmail.com>
// 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 <QtCore/qsystemdetection.h>
#include <QtCore/private/qlocale_tools_p.h>
#include <sys/sysmacros.h> // makedev()
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
static const char MountInfoPath[] = "/proc/self/mountinfo";
static std::optional<dev_t> 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<QByteArrayView, FieldCount> &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({});
}
}
namespace {
struct MountInfo {
QString mountPoint;
QByteArray fsType;
QByteArray device;
QByteArray fsRoot;
dev_t stDev = 0;
};
}
// 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<MountInfo>
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<MountInfo> infos;
std::array<QByteArrayView, FieldCount> 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<dev_t> 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

View File

@ -15,11 +15,14 @@
// We mean it.
//
#include <QtCore/qloggingcategory.h>
#include <QtCore/private/qglobal_p.h>
#include "qstorageinfo.h"
QT_BEGIN_NAMESPACE
inline Q_LOGGING_CATEGORY(lcStorageInfo, "qt.core.qstorageinfo", QtWarningMsg)
class QStorageInfoPrivate : public QSharedData
{
public:
@ -65,6 +68,58 @@ public:
bool valid;
};
// Common helper functions
template <typename String>
static bool isParentOf(const String &parent, const QString &dirName)
{
return dirName.startsWith(parent) &&
(dirName.size() == parent.size() || dirName.at(parent.size()) == u'/' ||
parent.size() == 1);
}
static inline bool shouldIncludeFs(const QString &mountDir, const QByteArray &fsType)
{
#if defined(Q_OS_ANDROID)
// "rootfs" is the filesystem type of "/" on Android
static constexpr char RootFsStr[] = "";
#else
// "rootfs" is a type of ramfs on Linux, used in the initrd on some distros
static constexpr char RootFsStr[] = "rootfs";
#endif
using namespace Qt::StringLiterals;
/*
* This function implements a heuristic algorithm to determine whether a
* given mount should be reported to the user. Our objective is to list
* only entries that the end-user would find useful.
*
* We therefore ignore:
* - mounted in /dev, /proc, /sys: special mounts
* (this will catch /sys/fs/cgroup, /proc/sys/fs/binfmt_misc, /dev/pts,
* some of which are tmpfs on Linux)
* - mounted in /var/run or /var/lock: most likely pseudofs
* (on earlier systemd versions, /var/run was a bind-mount of /run, so
* everything would be unnecessarily duplicated)
* - filesystem type is "rootfs": artifact of the root-pivot on some Linux
* initrd
* - if the filesystem total size is zero, it's a pseudo-fs (not checked here).
*/
if (isParentOf("/dev"_L1, mountDir)
|| isParentOf("/proc"_L1, mountDir)
|| isParentOf("/sys"_L1, mountDir)
|| isParentOf("/var/run"_L1, mountDir)
|| isParentOf("/var/lock"_L1, mountDir)) {
return false;
}
if (!fsType.isEmpty() && fsType == RootFsStr)
return false;
// size checking in QStorageInfo::mountedVolumes()
return true;
}
QT_END_NAMESPACE
#endif // QSTORAGEINFO_P_H

View File

@ -15,6 +15,10 @@
#include <errno.h>
#include <sys/stat.h>
#if defined(Q_OS_LINUX)
# include "qstorageinfo_linux_p.h"
#endif
#if defined(Q_OS_BSD4)
# include <sys/mount.h>
# include <sys/statvfs.h>
@ -22,7 +26,9 @@
# include <sys/mount.h>
# include <sys/vfs.h>
# include <mntent.h>
#elif defined(Q_OS_LINUX) || defined(Q_OS_HURD)
#elif defined(Q_OS_LINUX)
# include <sys/statvfs.h>
#elif defined(Q_OS_HURD)
# include <mntent.h>
# include <sys/statvfs.h>
# include <sys/sysmacros.h>
@ -112,37 +118,10 @@ private:
QByteArray m_fileSystemType;
QByteArray m_device;
QByteArray m_options;
#elif defined(Q_OS_LINUX) || defined(Q_OS_HURD)
struct mountinfoent : public mntent {
// Details from proc(5) section from /proc/<pid>/mountinfo:
//(1) mount ID: a unique ID for the mount (may be reused after umount(2)).
int mount_id;
//(2) parent ID: the ID of the parent mount (or of self for the top of the mount tree).
// int parent_id;
//(3) major:minor: the value of st_dev for files on this filesystem (see stat(2)).
dev_t rdev;
//(4) root: the pathname of the directory in the filesystem which forms the root of this mount.
char *subvolume;
//(5) mount point: the pathname of the mount point relative to the process's root directory.
// char *mnt_dir; // in mntent
//(6) mount options: per-mount options.
// char *mnt_opts; // in mntent
//(7) optional fields: zero or more fields of the form "tag[:value]"; see below.
// int flags;
//(8) separator: the end of the optional fields is marked by a single hyphen.
//(9) filesystem type: the filesystem type in the form "type[.subtype]".
// char *mnt_type; // in mntent
//(10) mount source: filesystem-specific information or "none".
// char *mnt_fsname; // in mntent
//(11) super options: per-superblock options.
char *superopts;
};
#elif defined(Q_OS_HURD)
FILE *fp;
QByteArray buffer;
mountinfoent mnt;
bool usingMountinfo;
#elif defined(Q_OS_HAIKU)
BVolumeRoster m_volumeRoster;
@ -152,51 +131,6 @@ private:
#endif
};
template <typename String>
static bool isParentOf(const String &parent, const QString &dirName)
{
return dirName.startsWith(parent) &&
(dirName.size() == parent.size() || dirName.at(parent.size()) == u'/' ||
parent.size() == 1);
}
static bool shouldIncludeFs(const QStorageIterator &it)
{
/*
* This function implements a heuristic algorithm to determine whether a
* given mount should be reported to the user. Our objective is to list
* only entries that the end-user would find useful.
*
* We therefore ignore:
* - mounted in /dev, /proc, /sys: special mounts
* (this will catch /sys/fs/cgroup, /proc/sys/fs/binfmt_misc, /dev/pts,
* some of which are tmpfs on Linux)
* - mounted in /var/run or /var/lock: most likely pseudofs
* (on earlier systemd versions, /var/run was a bind-mount of /run, so
* everything would be unnecessarily duplicated)
* - filesystem type is "rootfs": artifact of the root-pivot on some Linux
* initrd
* - if the filesystem total size is zero, it's a pseudo-fs (not checked here).
*/
QString mountDir = it.rootPath();
if (isParentOf("/dev"_L1, mountDir)
|| isParentOf("/proc"_L1, mountDir)
|| isParentOf("/sys"_L1, mountDir)
|| isParentOf("/var/run"_L1, mountDir)
|| isParentOf("/var/lock"_L1, mountDir)) {
return false;
}
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
if (it.fileSystemType() == "rootfs")
return false;
#endif
// size checking in mountedVolumes()
return true;
}
#if defined(Q_OS_BSD4)
#ifndef MNT_NOWAIT
@ -350,7 +284,8 @@ inline QByteArray QStorageIterator::subvolume() const
{
return QByteArray();
}
#elif defined(Q_OS_LINUX) || defined(Q_OS_HURD)
#elif defined(Q_OS_HURD)
static const int bufferSize = 1024; // 2 paths (mount point+device) and metainfo;
// should be enough
@ -358,28 +293,13 @@ static const int bufferSize = 1024; // 2 paths (mount point+device) and metainfo
inline QStorageIterator::QStorageIterator() :
buffer(QByteArray(bufferSize, 0))
{
fp = nullptr;
#ifdef Q_OS_LINUX
// first, try to open /proc/self/mountinfo, which has more details
fp = ::fopen("/proc/self/mountinfo", "re");
#endif
if (fp) {
usingMountinfo = true;
} else {
usingMountinfo = false;
fp = ::setmntent(_PATH_MOUNTED, "r");
}
}
inline QStorageIterator::~QStorageIterator()
{
if (fp) {
if (usingMountinfo)
::fclose(fp);
else
if (fp)
::endmntent(fp);
}
}
inline bool QStorageIterator::isValid() const
@ -389,143 +309,7 @@ inline bool QStorageIterator::isValid() const
inline bool QStorageIterator::next()
{
mnt.subvolume = nullptr;
mnt.superopts = nullptr;
if (!usingMountinfo)
return ::getmntent_r(fp, &mnt, buffer.data(), buffer.size()) != nullptr;
// Helper function to parse paths that the kernel inserts escape sequences
// for. The unescaped string is left at \a src and is properly
// NUL-terminated. Returns a pointer to the delimiter that terminated the
// path, or nullptr if it failed.
auto parseMangledPath = [](char *src) {
// The kernel escapes with octal the following characters:
// space ' ', tab '\t', backslask '\\', and newline '\n'
char *dst = src;
while (*src) {
switch (*src) {
case ' ':
// Unescaped space: end of the field.
*dst = '\0';
return src;
default:
*dst++ = *src++;
break;
case '\\':
// It always uses exactly three octal characters.
++src;
char c = (*src++ - '0') << 6;
c |= (*src++ - '0') << 3;
c |= (*src++ - '0');
*dst++ = c;
break;
}
}
// Found a NUL before the end of the field.
src = nullptr;
return src;
};
char *ptr = buffer.data();
if (fgets(ptr, buffer.size(), fp) == nullptr)
return false;
size_t len = strlen(ptr);
if (len == 0)
return false;
while (Q_UNLIKELY(ptr[len - 1] != '\n' && !feof(fp))) {
// buffer wasn't large enough. Enlarge and try again.
// (we're readidng from the kernel, so OOM is unlikely)
buffer.resize((buffer.size() + 4096) & ~4095);
ptr = buffer.data();
if (fgets(ptr + len, buffer.size() - len, fp) == nullptr)
return false;
len += strlen(ptr + len);
Q_ASSERT(len < size_t(buffer.size()));
}
ptr[len - 1] = '\0';
const char *const stop = ptr + len - 1;
// parse the line
mnt.mnt_freq = 0;
mnt.mnt_passno = 0;
auto r = qstrntoll(ptr, stop - ptr, 10);
if (!r.ok())
return false;
mnt.mount_id = r.result;
ptr += r.used;
r = qstrntoll(ptr, stop - ptr, 10);
if (!r.ok())
return false;
// parent_id = r.result; // member currently not in use
ptr += r.used;
r = qstrntoll(ptr, stop - ptr, 10);
if (!r.ok())
return false;
ptr += r.used;
if (*ptr != ':')
return false;
int rdevmajor = r.result;
++ptr; // Skip over the ':'
r = qstrntoll(ptr, stop - ptr, 10);
if (!r.ok())
return false;
mnt.rdev = makedev(rdevmajor, r.result);
ptr += r.used;
if (*ptr != ' ')
return false;
mnt.subvolume = ++ptr;
ptr = parseMangledPath(ptr);
if (!ptr)
return false;
// unset a subvolume of "/" -- it's not a *sub* volume
if (mnt.subvolume + 1 == ptr)
*mnt.subvolume = '\0';
mnt.mnt_dir = ++ptr;
ptr = parseMangledPath(ptr);
if (!ptr)
return false;
mnt.mnt_opts = ++ptr;
ptr = strchr(ptr, ' ');
if (!ptr)
return false;
// we don't parse the flags, so just find the separator
if (char *const dashed = strstr(ptr, " - ")) {
*ptr = '\0';
ptr = dashed + strlen(" - ") - 1;
} else {
return false;
}
mnt.mnt_type = ++ptr;
ptr = strchr(ptr, ' ');
if (!ptr)
return false;
*ptr = '\0';
mnt.mnt_fsname = ++ptr;
ptr = parseMangledPath(ptr);
if (!ptr)
return false;
mnt.superopts = ++ptr;
ptr += strcspn(ptr, " \n");
*ptr = '\0';
return true;
}
inline QString QStorageIterator::rootPath() const
@ -540,49 +324,17 @@ inline QByteArray QStorageIterator::fileSystemType() const
inline QByteArray QStorageIterator::device() const
{
#ifdef Q_OS_LINUX
// check that the device exists
if (mnt.mnt_fsname[0] == '/' && access(mnt.mnt_fsname, F_OK) != 0) {
// It doesn't, so let's try to resolve the dev_t from /dev/block.
// Note how strlen("4294967295") == digits10 + 1, so we need to add 1
// for each number, plus the ':'.
char buf[sizeof("/dev/block/") + 2 * std::numeric_limits<unsigned>::digits10 + 3];
QByteArray dev(PATH_MAX, Qt::Uninitialized);
char *devdata = dev.data();
snprintf(buf, sizeof(buf), "/dev/block/%u:%u", major(mnt.rdev), minor(mnt.rdev));
if (realpath(buf, devdata)) {
dev.truncate(strlen(devdata));
return dev;
}
}
#endif
return QByteArray(mnt.mnt_fsname);
}
inline QByteArray QStorageIterator::options() const
{
// Merge the two options, starting with the superblock options and letting
// the per-mount options override.
const char *superopts = mnt.superopts;
// Both mnt_opts and superopts start with "ro" or "rw", so we can skip the
// superblock's field (see show_mountinfo() in fs/proc_namespace.c).
if (superopts && superopts[0] == 'r') {
if (superopts[2] == '\0') // no other superopts besides "ro" / "rw"?
superopts = nullptr;
else if (superopts[2] == ',')
superopts += 3;
}
if (superopts)
return QByteArray(superopts) + ',' + mnt.mnt_opts;
return QByteArray(mnt.mnt_opts);
}
inline QByteArray QStorageIterator::subvolume() const
{
return QByteArray(mnt.subvolume);
return QByteArray();
}
#elif defined(Q_OS_HAIKU)
inline QStorageIterator::QStorageIterator()
@ -650,6 +402,7 @@ inline QByteArray QStorageIterator::subvolume() const
{
return QByteArray();
}
#else
inline QStorageIterator::QStorageIterator()
@ -696,37 +449,6 @@ inline QByteArray QStorageIterator::subvolume() const
}
#endif
void QStorageInfoPrivate::initRootPath()
{
rootPath = QFileInfo(rootPath).canonicalFilePath();
if (rootPath.isEmpty())
return;
QStorageIterator it;
if (!it.isValid()) {
rootPath = QStringLiteral("/");
return;
}
int maxLength = 0;
const QString oldRootPath = rootPath;
rootPath.clear();
while (it.next()) {
const QString mountDir = it.rootPath();
const QByteArray fsName = it.fileSystemType();
// we try to find most suitable entry
if (isParentOf(mountDir, oldRootPath) && maxLength < mountDir.size()) {
maxLength = mountDir.size();
rootPath = mountDir;
device = it.device();
fileSystemType = fsName;
subvolume = it.subvolume();
}
}
}
#ifdef Q_OS_LINUX
// udev encodes the labels with ID_LABEL_FS_ENC which is done with
// blkid_encode_string(). Within this function some 1-byte utf-8
@ -831,6 +553,101 @@ void QStorageInfoPrivate::retrieveVolumeInfo()
}
}
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
static std::vector<MountInfo> parseMountInfo(FilterMountInfo filter = FilterMountInfo::All)
{
QFile file(u"/proc/self/mountinfo"_s);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
return {};
QByteArray mountinfo = file.readAll();
file.close();
return doParseMountInfo(mountinfo, filter);
}
void QStorageInfoPrivate::initRootPath()
{
rootPath = QFileInfo(rootPath).canonicalFilePath();
if (rootPath.isEmpty())
return;
std::vector<MountInfo> infos = parseMountInfo();
if (infos.empty()) {
rootPath = u'/';
return;
}
qsizetype maxLength = 0;
const QString oldRootPath = rootPath;
rootPath.clear();
for (auto &info : infos) {
// we try to find most suitable entry
qsizetype mpSize = info.mountPoint.size();
if (isParentOf(info.mountPoint, oldRootPath) && maxLength < mpSize) {
maxLength = mpSize;
rootPath = info.mountPoint;
device = info.device;
fileSystemType = info.fsType;
subvolume = info.fsRoot;
}
}
}
QList<QStorageInfo> QStorageInfoPrivate::mountedVolumes()
{
std::vector<MountInfo> infos = parseMountInfo(FilterMountInfo::Filtered);
if (infos.empty())
return QList{root()};
QList<QStorageInfo> volumes;
for (MountInfo &info : infos) {
QStorageInfo storage(info.mountPoint);
storage.d->device = info.device;
storage.d->fileSystemType = info.fsType;
storage.d->subvolume = info.fsRoot;
if (storage.bytesTotal() == 0 && storage != root())
continue;
volumes.push_back(storage);
}
return volumes;
}
#else // defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
void QStorageInfoPrivate::initRootPath()
{
rootPath = QFileInfo(rootPath).canonicalFilePath();
if (rootPath.isEmpty())
return;
QStorageIterator it;
if (!it.isValid()) {
rootPath = QStringLiteral("/");
return;
}
int maxLength = 0;
const QString oldRootPath = rootPath;
rootPath.clear();
while (it.next()) {
const QString mountDir = it.rootPath();
const QByteArray fsName = it.fileSystemType();
// we try to find most suitable entry
if (isParentOf(mountDir, oldRootPath) && maxLength < mountDir.size()) {
maxLength = mountDir.size();
rootPath = mountDir;
device = it.device();
fileSystemType = fsName;
subvolume = it.subvolume();
}
}
}
QList<QStorageInfo> QStorageInfoPrivate::mountedVolumes()
{
QStorageIterator it;
@ -840,7 +657,7 @@ QList<QStorageInfo> QStorageInfoPrivate::mountedVolumes()
QList<QStorageInfo> volumes;
while (it.next()) {
if (!shouldIncludeFs(it))
if (!shouldIncludeFs(it.rootPath(), it.fileSystemType()))
continue;
const QString mountDir = it.rootPath();
@ -855,6 +672,7 @@ QList<QStorageInfo> QStorageInfoPrivate::mountedVolumes()
return volumes;
}
#endif // defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
QStorageInfo QStorageInfoPrivate::root()
{

View File

@ -90,7 +90,8 @@ template <typename UcsInt>
return qstrntod(s00, len, se, ok);
}
[[nodiscard]] QSimpleParsedNumber<qlonglong> qstrntoll(const char *nptr, qsizetype size, int base);
[[nodiscard]] Q_AUTOTEST_EXPORT
QSimpleParsedNumber<qlonglong> qstrntoll(const char *nptr, qsizetype size, int base);
[[nodiscard]] QSimpleParsedNumber<qulonglong> qstrntoull(const char *nptr, qsizetype size, int base);
QT_END_NAMESPACE

View File

@ -9,6 +9,10 @@
#include "../../../../manual/qstorageinfo/printvolumes.cpp"
#ifdef Q_OS_LINUX
# include "../../../../../src/corelib/io/qstorageinfo_linux_p.h"
#endif
class tst_QStorageInfo : public QObject
{
Q_OBJECT
@ -22,6 +26,13 @@ private slots:
void storageList();
void tempFile();
void caching();
#if defined(Q_OS_LINUX) && defined(QT_BUILD_INTERNAL)
void testParseMountInfo_data();
void testParseMountInfo();
void testParseMountInfo_filtered_data();
void testParseMountInfo_filtered();
#endif
};
void tst_QStorageInfo::defaultValues()
@ -211,6 +222,155 @@ void tst_QStorageInfo::caching()
QCOMPARE_NE(free, storage2.bytesFree());
}
#if defined(Q_OS_LINUX) && defined(QT_BUILD_INTERNAL)
void tst_QStorageInfo::testParseMountInfo_data()
{
QTest::addColumn<QByteArray>("line");
QTest::addColumn<MountInfo>("expected");
QTest::newRow("tmpfs")
<< "17 25 0:18 / /dev rw,nosuid,relatime shared:2 - tmpfs tmpfs rw,seclabel,mode=755\n"_ba
<< MountInfo{"/dev", "tmpfs", "tmpfs", "", makedev(0, 18)};
QTest::newRow("proc")
<< "23 66 0:21 / /proc rw,nosuid,nodev,noexec,relatime shared:12 - proc proc rw\n"_ba
<< MountInfo{"/proc", "proc", "proc", "", makedev(0, 21)};
// E.g. on Android
QTest::newRow("rootfs")
<< "618 618 0:1 / / ro,relatime master:1 - rootfs rootfs ro,seclabel\n"_ba
<< MountInfo{"/", "rootfs", "rootfs", "", makedev(0, 1)};
QTest::newRow("ext4")
<< "47 66 8:3 / /home rw,relatime shared:50 - ext4 /dev/sda3 rw,stripe=32736\n"_ba
<< MountInfo{"/home", "ext4", "/dev/sda3", "", makedev(8, 3)};
QTest::newRow("empty-optional-field")
<< "23 25 0:22 / /apex rw,nosuid,nodev,noexec,relatime - tmpfs tmpfs rw,seclabel,mode=755\n"_ba
<< MountInfo{"/apex", "tmpfs", "tmpfs", "", makedev(0, 22)};
QTest::newRow("one-optional-field")
<< "47 66 8:3 / /home rw,relatime shared:50 - ext4 /dev/sda3 rw,stripe=32736\n"_ba
<< MountInfo{"/home", "ext4", "/dev/sda3", "", makedev(8, 3)};
QTest::newRow("multiple-optional-fields")
<< "47 66 8:3 / /home rw,relatime shared:142 master:111 - ext4 /dev/sda3 rw,stripe=32736\n"_ba
<< MountInfo{"/home", "ext4", "/dev/sda3", "", makedev(8, 3)};
QTest::newRow("mountdir-with-utf8")
<< "129 66 8:51 / /mnt/lab\xC3\xA9l rw,relatime shared:234 - ext4 /dev/sdd3 rw\n"_ba
<< MountInfo{"/mnt/labél", "ext4", "/dev/sdd3", "", makedev(8, 51)};
QTest::newRow("mountdir-with-space")
<< "129 66 8:51 / /mnt/labe\\040l rw,relatime shared:234 - ext4 /dev/sdd3 rw\n"_ba
<< MountInfo{"/mnt/labe l", "ext4", "/dev/sdd3", "", makedev(8, 51)};
QTest::newRow("mountdir-with-tab")
<< "129 66 8:51 / /mnt/labe\\011l rw,relatime shared:234 - ext4 /dev/sdd3 rw\n"_ba
<< MountInfo{"/mnt/labe\tl", "ext4", "/dev/sdd3", "", makedev(8, 51)};
QTest::newRow("mountdir-with-backslash")
<< "129 66 8:51 / /mnt/labe\\134l rw,relatime shared:234 - ext4 /dev/sdd3 rw\n"_ba
<< MountInfo{"/mnt/labe\\l", "ext4", "/dev/sdd3", "", makedev(8, 51)};
QTest::newRow("mountdir-with-newline")
<< "129 66 8:51 / /mnt/labe\\012l rw,relatime shared:234 - ext4 /dev/sdd3 rw\n"_ba
<< MountInfo{"/mnt/labe\nl", "ext4", "/dev/sdd3", "", makedev(8, 51)};
QTest::newRow("btrfs-subvol")
<< "775 503 0:49 /foo/bar / rw,relatime shared:142 master:111 - btrfs "
"/dev/mapper/vg0-stuff rw,ssd,discard,space_cache,subvolid=272,subvol=/foo/bar\n"_ba
<< MountInfo{"/", "btrfs", "/dev/mapper/vg0-stuff", "/foo/bar", makedev(0, 49)};
QTest::newRow("bind-mount")
<< "59 47 8:17 /rpmbuild /home/user/rpmbuild rw,relatime shared:48 - ext4 /dev/sdb1 rw\n"_ba
<< MountInfo{"/home/user/rpmbuild", "ext4", "/dev/sdb1", "/rpmbuild", makedev(8, 17)};
QTest::newRow("space-dash-space")
<< "47 66 8:3 / /home\\040-\\040dir rw,relatime shared:50 - ext4 /dev/sda3 rw,stripe=32736\n"_ba
<< MountInfo{"/home - dir", "ext4", "/dev/sda3", "", makedev(8, 3)};
QTest::newRow("btrfs-mount-bind-file")
<< "1799 1778 0:49 "
"/var_lib_docker/containers/81fde0fec3dd3d99765c3f7fd9cf1ab121b6ffcfd05d5d7ff434db933fe9d795/resolv.conf "
"/etc/resolv.conf rw,relatime - btrfs /dev/mapper/vg0-stuff "
"rw,ssd,discard,space_cache,subvolid=1773,subvol=/var_lib_docker\n"_ba
<< MountInfo{"/etc/resolv.conf", "btrfs", "/dev/mapper/vg0-stuff",
"/var_lib_docker/containers/81fde0fec3dd3d99765c3f7fd9cf1ab121b6ffcfd05d5d7ff434db933fe9d795/resolv.conf",
makedev(0, 49)};
QTest::newRow("very-long-line-QTBUG-77059")
<< "727 26 0:52 / "
"/var/lib/docker/overlay2/f3fbad5eedef71145f00729f0826ea8c44defcfec8c92c58aee0aa2c5ea3fa3a/merged "
"rw,relatime shared:399 - overlay overlay "
"rw,lowerdir=/var/lib/docker/overlay2/l/PUP2PIY4EQLAOEDQOZ56BHVE53:"
"/var/lib/docker/overlay2/l/6IIID3C6J3SUXZEA3GJXKQSTLD:"
"/var/lib/docker/overlay2/l/PA6N6URNR7XDBBGGOSFWSFQ2CG:"
"/var/lib/docker/overlay2/l/5EOMBTZNCPOCE4LM3I4JCTNSTT:"
"/var/lib/docker/overlay2/l/DAMINQ46P3LKX2GDDDIWQKDIWC:"
"/var/lib/docker/overlay2/l/DHR3N57AEH4OG5QER5XJW2LXIN:"
"/var/lib/docker/overlay2/l/NW26KA7QPRS2KSVQI77QJWLMHW,"
"upperdir=/var/lib/docker/overlay2/f3fbad5eedef71145f00729f0826ea8c44defcfec8c92c58aee0aa2c5ea3fa3a/diff,"
"workdir=/var/lib/docker/overlay2/f3fbad5eedef71145f00729f0826ea8c44defcfec8c92c58aee0aa2c5ea3fa3a/work,"
"index=off,xino=off\n"_ba
<< MountInfo{"/var/lib/docker/overlay2/f3fbad5eedef71145f00729f0826ea8c44defcfec8c92c58aee0aa2c5ea3fa3a/merged",
"overlay", "overlay", "", makedev(0, 52)};
QTest::newRow("sshfs-src-device-not-start-with-slash")
<< "128 92 0:64 / /mnt-point rw,nosuid,nodev,relatime shared:234 - "
"fuse.sshfs admin@192.168.1.2:/storage/emulated/0 rw,user_id=1000,group_id=1000\n"_ba
<< MountInfo{"/mnt-point", "fuse.sshfs",
"admin@192.168.1.2:/storage/emulated/0", "", makedev(0, 64)};
}
void tst_QStorageInfo::testParseMountInfo()
{
QFETCH(QByteArray, line);
QFETCH(MountInfo, expected);
const std::vector<MountInfo> result = doParseMountInfo(line);
QVERIFY(!result.empty());
const MountInfo &a = result.front();
QCOMPARE(a.mountPoint, expected.mountPoint);
QCOMPARE(a.fsType, expected.fsType);
QCOMPARE(a.device, expected.device);
QCOMPARE(a.fsRoot, expected.fsRoot);
QCOMPARE(a.stDev, expected.stDev);
}
void tst_QStorageInfo::testParseMountInfo_filtered_data()
{
QTest::addColumn<QByteArray>("line");
QTest::newRow("proc")
<< "23 66 0:21 / /proc rw,nosuid,nodev,noexec,relatime shared:12 - proc proc rw\n"_ba;
QTest::newRow("sys")
<< "24 66 0:22 / /sys rw,nosuid,nodev,noexec,relatime shared:2 - sysfs sysfs rw\n"_ba;
QTest::newRow("sys-kernel")
<< "26 24 0:6 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime "
"shared:3 - securityfs securityfs rw\n"_ba;
QTest::newRow("dev")
<< "25 66 0:5 / /dev rw,nosuid shared:8 - devtmpfs devtmpfs "
"rw,size=4096k,nr_inodes=8213017,mode=755,inode64\n"_ba;
QTest::newRow("dev-shm")
<< "27 25 0:23 / /dev/shm rw,nosuid,nodev shared:9 - tmpfs tmpfs rw,inode64\n"_ba;
QTest::newRow("var-run")
<< "46 28 0:25 / /var/run rw,nosuid,nodev,noexec,relatime shared:1 - "
"tmpfs tmpfs rw,size=32768k,mode=755,inode64\n"_ba;
QTest::newRow("var-lock")
<< "46 28 0:25 / /var/lock rw,nosuid,nodev,noexec,relatime shared:1 - "
"tmpfs tmpfs rw,size=32768k,mode=755,inode64\n"_ba;
}
void tst_QStorageInfo::testParseMountInfo_filtered()
{
QFETCH(QByteArray, line);
QVERIFY(doParseMountInfo(line, FilterMountInfo::Filtered).empty());
}
#endif // Q_OS_LINUX
QTEST_MAIN(tst_QStorageInfo)
#include "tst_qstorageinfo.moc"