diff options
author | Thiago Macieira <thiago.macieira@intel.com> | 2023-10-19 08:30:46 -0700 |
---|---|---|
committer | Thiago Macieira <thiago.macieira@intel.com> | 2023-10-21 15:21:21 -0700 |
commit | 1cd6c6c69e9813c791f8bebb6c0c9214ce765060 (patch) | |
tree | e84c2f8d4a425df16177e41f487e3adbb65dae4d /src/corelib/io | |
parent | 9df2b7ffa4fd99c39e73263c0ee07c203053096b (diff) |
QStorageInfo/Linux: fix setPath() for paths mounted over
Linux allows admins to mount new filesystems over non-empty paths
(though I managed to do so on FreeBSD and macOS too), so we must find
the most recent mount that applies to this path instead of the longest
matching mountpoint. We do that by scanning the /proc/self/mountinfo
list backwards and thus any matching isParentOf() must be the correct
one.
# mkdir -p /tmp/foo/bar
# mount -t tmpfs tmpfs /tmp/foo/bar
# mount -t tmpfs -o size=1M tmpfs /tmp/foo
$ ./tests/manual/qstorageinfo/qstorageinfo /tmp/foo/bar
Filesystem (Type) Size Available BSize Label Mounted on
tmpfs RW 1024 1024 4096 /tmp/foo
But we must guard against an earlier mount still being (somehow)
accessible. We've seen this in the CI, where /run is earlier than / but
still somehow accessible -- I guess this is one or a pair of mount
--move.
An additional benefit is that don't even attempt to compare to the
virtual filesystems mounted by the system early after boot, if what
we're looking for isn't the root.
See next commit for a fix for QStorageInfo::mountedVolumes().
Change-Id: I79e700614d034281bf55fffd178f8befc5e80edb
Reviewed-by: Ahmad Samir <a.samirh78@gmail.com>
Diffstat (limited to 'src/corelib/io')
-rw-r--r-- | src/corelib/io/qstorageinfo_linux.cpp | 45 |
1 files changed, 27 insertions, 18 deletions
diff --git a/src/corelib/io/qstorageinfo_linux.cpp b/src/corelib/io/qstorageinfo_linux.cpp index 99143a5975..fab5a44fad 100644 --- a/src/corelib/io/qstorageinfo_linux.cpp +++ b/src/corelib/io/qstorageinfo_linux.cpp @@ -59,6 +59,14 @@ static QString decodeFsEncString(const QString &str) return decoded; } +static inline dev_t deviceIdForPath(const QString &device) +{ + QT_STATBUF st; + if (QT_STAT(QFile::encodeName(device), &st) < 0) + return 0; + return st.st_dev; +} + static inline QString retrieveLabel(const QByteArray &device) { static const char pathDiskByLabel[] = "/dev/disk/by-label"; @@ -136,24 +144,25 @@ void QStorageInfoPrivate::initRootPath() return; } - qsizetype maxLength = 0; - const QString oldRootPath = rootPath; - rootPath.clear(); - - MountInfo *bestInfo = nullptr; - for (MountInfo &info : infos) { - // we try to find most suitable entry - qsizetype mpSize = info.mountPoint.size(); - if (maxLength < mpSize && isParentOf(info.mountPoint, oldRootPath)) { - bestInfo = &info; - maxLength = mpSize; - } - } - if (bestInfo) { - rootPath = std::move(bestInfo->mountPoint); - device = std::move(bestInfo->device); - fileSystemType = std::move(bestInfo->fsType); - subvolume = std::move(bestInfo->fsRoot); + // We iterate over the /proc/self/mountinfo list backwards because then any + // matching isParentOf must be the actual mount point because it's the most + // recent mount on that path. Linux does allow mounting over non-empty + // directories, such as in: + // # mount | tail -2 + // tmpfs on /tmp/foo/bar type tmpfs (rw,relatime,inode64) + // tmpfs on /tmp/foo type tmpfs (rw,relatime,inode64) + // But just in case there's a mount --move, we ensure the device ID does + // match. + const QString oldRootPath = std::exchange(rootPath, QString()); + const dev_t rootPathDevId = deviceIdForPath(oldRootPath); + for (auto it = infos.rbegin(); it != infos.rend(); ++it) { + if (rootPathDevId != it->stDev || !isParentOf(it->mountPoint, oldRootPath)) + continue; + rootPath = std::move(it->mountPoint); + device = std::move(it->device); + fileSystemType = std::move(it->fsType); + subvolume = std::move(it->fsRoot); + return; } } |