summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThiago Macieira <thiago.macieira@intel.com>2017-11-07 18:08:54 -0800
committerThiago Macieira <thiago.macieira@intel.com>2017-11-30 08:30:21 +0000
commitf984c453737bde9b39f14bd5ddabcde292b667d5 (patch)
treecee419fcd649b4d8db7106009e41707b2bb88841
parent498ef7f9d77fca75bcba2f657aaac38c8dc78aea (diff)
QStorageInfo: use Linux's /proc/self/mountinfo to get the subvolume
This allows us to get "subvolumes" on all filesystem types. We do that by detecting the subdirectory that was bind-mounted. /proc/self/mountinfo has been in the kernel since 2.6.26. Since btrfs was only added on 2.6.29, there is no loss of functionality for btrfs users. I've tested this with subvolume or mountpoint names containing spaces, tabs and newlines. Change-Id: I57a1bd6e0c194530b732fffd14f4fa418255d839 Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
-rw-r--r--src/corelib/io/qstorageinfo_unix.cpp235
1 files changed, 207 insertions, 28 deletions
diff --git a/src/corelib/io/qstorageinfo_unix.cpp b/src/corelib/io/qstorageinfo_unix.cpp
index cdaa6329f9..6d8b6ee1a0 100644
--- a/src/corelib/io/qstorageinfo_unix.cpp
+++ b/src/corelib/io/qstorageinfo_unix.cpp
@@ -45,6 +45,7 @@
#include <QtCore/qtextstream.h>
#include <QtCore/private/qcore_unix_p.h>
+#include <QtCore/private/qlocale_tools_p.h>
#include <errno.h>
#include <sys/stat.h>
@@ -121,6 +122,7 @@ public:
inline QByteArray fileSystemType() const;
inline QByteArray device() const;
inline QByteArray options() const;
+ inline QByteArray subvolume() const;
private:
#if defined(Q_OS_BSD4)
QT_STATFSBUF *stat_buf;
@@ -136,9 +138,36 @@ private:
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;
+ };
+
FILE *fp;
- mntent mnt;
QByteArray buffer;
+ mountinfoent mnt;
+ bool usingMountinfo;
#elif defined(Q_OS_HAIKU)
BVolumeRoster m_volumeRoster;
@@ -239,6 +268,10 @@ inline QByteArray QStorageIterator::options() const
return QByteArray();
}
+inline QByteArray QStorageIterator::subvolume() const
+{
+ return QByteArray();
+}
#elif defined(Q_OS_SOLARIS)
static const char pathMounted[] = "/etc/mnttab";
@@ -280,6 +313,10 @@ inline QByteArray QStorageIterator::device() const
return QByteArray(mnt.mnt_mntopts);
}
+inline QByteArray QStorageIterator::subvolume() const
+{
+ return QByteArray();
+}
#elif defined(Q_OS_ANDROID)
static const QLatin1String pathMounted("/proc/mounts");
@@ -337,6 +374,10 @@ inline QByteArray QStorageIterator::options() const
return m_options;
}
+inline QByteArray QStorageIterator::subvolume() const
+{
+ return QByteArray();
+}
#elif defined(Q_OS_LINUX) || defined(Q_OS_HURD)
static const char pathMounted[] = "/etc/mtab";
@@ -346,13 +387,28 @@ static const int bufferSize = 1024; // 2 paths (mount point+device) and metainfo
inline QStorageIterator::QStorageIterator() :
buffer(QByteArray(bufferSize, 0))
{
- fp = ::setmntent(pathMounted, "r");
+ 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(pathMounted, "r");
+ }
}
inline QStorageIterator::~QStorageIterator()
{
- if (fp)
- ::endmntent(fp);
+ if (fp) {
+ if (usingMountinfo)
+ ::fclose(fp);
+ else
+ ::endmntent(fp);
+ }
}
inline bool QStorageIterator::isValid() const
@@ -362,7 +418,127 @@ inline bool QStorageIterator::isValid() const
inline bool QStorageIterator::next()
{
- return ::getmntent_r(fp, &mnt, buffer.data(), buffer.size()) != nullptr;
+ 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(buffer.data());
+ if (len == 0)
+ return false;
+ if (ptr[len - 1] == '\n')
+ ptr[len - 1] = '\0';
+
+ // parse the line
+ bool ok;
+ mnt.mnt_freq = 0;
+ mnt.mnt_passno = 0;
+
+ mnt.mount_id = qstrtoll(ptr, const_cast<const char **>(&ptr), 10, &ok);
+ if (!ptr || !ok)
+ return false;
+
+ int parent_id = qstrtoll(ptr, const_cast<const char **>(&ptr), 10, &ok);
+ Q_UNUSED(parent_id);
+ if (!ptr || !ok)
+ return false;
+
+ int rdevmajor = qstrtoll(ptr, const_cast<const char **>(&ptr), 10, &ok);
+ if (!ptr || !ok)
+ return false;
+ if (*ptr != ':')
+ return false;
+ int rdevminor = qstrtoll(ptr + 1, const_cast<const char **>(&ptr), 10, &ok);
+ if (!ptr || !ok)
+ return false;
+ Q_UNUSED(rdevmajor);
+ Q_UNUSED(rdevminor);
+
+ 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
@@ -382,9 +558,28 @@ inline QByteArray QStorageIterator::device() const
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);
+}
#elif defined(Q_OS_HAIKU)
inline QStorageIterator::QStorageIterator()
{
@@ -447,6 +642,10 @@ inline QByteArray QStorageIterator::options() const
return QByteArray();
}
+inline QByteArray QStorageIterator::subvolume() const
+{
+ return QByteArray();
+}
#else
inline QStorageIterator::QStorageIterator()
@@ -487,31 +686,11 @@ inline QByteArray QStorageIterator::options() const
return QByteArray();
}
-#endif
-
-static QByteArray extractSubvolume(const QStorageIterator &it)
+inline QByteArray QStorageIterator::subvolume() const
{
-#ifdef Q_OS_LINUX
- if (it.fileSystemType() == "btrfs") {
- const QByteArrayList opts = it.options().split(',');
- QByteArray id;
- for (const QByteArray &opt : opts) {
- static const char subvol[] = "subvol=";
- static const char subvolid[] = "subvolid=";
- if (opt.startsWith(subvol))
- return std::move(opt).mid(strlen(subvol));
- if (opt.startsWith(subvolid))
- id = std::move(opt).mid(strlen(subvolid));
- }
-
- // if we didn't find the subvolume name, return the subvolume ID
- return id;
- }
-#else
- Q_UNUSED(it);
-#endif
return QByteArray();
}
+#endif
void QStorageInfoPrivate::initRootPath()
{
@@ -539,7 +718,7 @@ void QStorageInfoPrivate::initRootPath()
rootPath = mountDir;
device = it.device();
fileSystemType = fsName;
- subvolume = extractSubvolume(it);
+ subvolume = it.subvolume();
}
}
}