From 14115da183d2271dbc25446e4f9eda478722eff8 Mon Sep 17 00:00:00 2001 From: Thiago Macieira Date: Wed, 17 Jan 2024 12:54:02 -0800 Subject: [PATCH] QStorageInfo/Linux: use FS_IOC_GETFSLABEL to get the labels This ioctl was introduced in kernel version 4.18 and before that it was used for btrfs alone. It is currently (kernel v6.7) supported by: * btrfs * ext2/ext4 * f2fs * gfs2 * xfs See man ioctl_fslabel(2) This means we won't need to list the /dev/disks/by-label directory if you only have mounted filesystems like the above or tmpfs (which doesn't support labels). On my system, there are still two cases that cause the listing: * a mount point not readable by my user, such as /.snapshots and /root * a FAT32 filesystem mounted on /boot/efi Pick-to: 6.7 Change-Id: I76ffba14ece04f24b43efffd17ab3dced74ffd2c Reviewed-by: Ahmad Samir Reviewed-by: Edward Welbourne --- src/corelib/io/qstorageinfo_linux.cpp | 53 +++++++++++++++++++++++---- 1 file changed, 46 insertions(+), 7 deletions(-) diff --git a/src/corelib/io/qstorageinfo_linux.cpp b/src/corelib/io/qstorageinfo_linux.cpp index 9cd6a8da5a8..db69903c481 100644 --- a/src/corelib/io/qstorageinfo_linux.cpp +++ b/src/corelib/io/qstorageinfo_linux.cpp @@ -13,8 +13,17 @@ #include #include +#include #include +// so we don't have to #include , which is known to cause conflicts +#ifndef FSLABEL_MAX +# define FSLABEL_MAX 256 +#endif +#ifndef FS_IOC_GETFSLABEL +# define FS_IOC_GETFSLABEL _IOR(0x94, 49, char[FSLABEL_MAX]) +#endif + QT_BEGIN_NAMESPACE using namespace Qt::StringLiterals; @@ -118,9 +127,29 @@ static inline auto retrieveLabels() return result; } -static inline QString retrieveLabel(const QByteArray &device, quint64 deviceId) +static std::optional retrieveLabelViaIoctl(const QString &path) { - deviceId = retrieveDeviceId(device, deviceId); + // FS_IOC_GETFSLABEL was introduced in v4.18; previously it was btrfs-specific. + int fd = qt_safe_open(QFile::encodeName(path).constData(), QT_OPEN_RDONLY); + if (fd < 0) + return std::nullopt; + + // Note: it doesn't append the null terminator (despite what the man page + // says) and the return code on success (0) does not indicate the length. + char label[FSLABEL_MAX] = {}; + int r = ioctl(fd, FS_IOC_GETFSLABEL, &label); + close(fd); + if (r < 0) + return std::nullopt; + return QString::fromUtf8(label); +} + +static inline QString retrieveLabel(const QStorageInfoPrivate &d, quint64 deviceId) +{ + if (auto label = retrieveLabelViaIoctl(d.rootPath)) + return *label; + + deviceId = retrieveDeviceId(d.device, deviceId); if (!deviceId) return QString(); @@ -211,7 +240,7 @@ void QStorageInfoPrivate::doStat() if (best) { auto stDev = best->stDev; setFromMountInfo(std::move(*best)); - name = retrieveLabel(device, stDev); + name = retrieveLabel(*this, stDev); } } @@ -221,11 +250,21 @@ QList QStorageInfoPrivate::mountedVolumes() if (infos.empty()) return QList{root()}; - auto labelForDevice = [labelMap = retrieveLabels()](const QByteArray &device, quint64 devid) { - devid = retrieveDeviceId(device, devid); + std::optional labelMap; + auto labelForDevice = [&labelMap](const QStorageInfoPrivate &d, quint64 devid) { + if (d.fileSystemType == "tmpfs") + return QString(); + + if (auto label = retrieveLabelViaIoctl(d.rootPath)) + return *label; + + devid = retrieveDeviceId(d.device, devid); if (!devid) return QString(); - for (auto &[deviceLabel, deviceId] : labelMap) { + + if (!labelMap) + labelMap = retrieveLabels(); + for (auto &[deviceLabel, deviceId] : std::as_const(*labelMap)) { if (devid == deviceId) return deviceLabel; } @@ -240,7 +279,7 @@ QList QStorageInfoPrivate::mountedVolumes() continue; if (info.stDev != deviceIdForPath(d.rootPath)) continue; // probably something mounted over this mountpoint - d.name = labelForDevice(d.device, info.stDev); + d.name = labelForDevice(d, info.stDev); volumes.emplace_back(QStorageInfo(*new QStorageInfoPrivate(std::move(d)))); } return volumes;