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

Change-Id: I76ffba14ece04f24b43efffd17ab3dced74ffd2c
Reviewed-by: Ahmad Samir <a.samirh78@gmail.com>
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
(cherry picked from commit 14115da183d2271dbc25446e4f9eda478722eff8)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
Thiago Macieira 2024-01-17 12:54:02 -08:00 committed by Qt Cherry-pick Bot
parent a3b4b060cf
commit 59b94a3158

View File

@ -13,8 +13,17 @@
#include <q20memory.h>
#include <linux/mount.h>
#include <sys/ioctl.h>
#include <sys/statfs.h>
// so we don't have to #include <linux/fs.h>, 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<QString> 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<QStorageInfo> QStorageInfoPrivate::mountedVolumes()
if (infos.empty())
return QList{root()};
auto labelForDevice = [labelMap = retrieveLabels()](const QByteArray &device, quint64 devid) {
devid = retrieveDeviceId(device, devid);
std::optional<decltype(retrieveLabels())> 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<QStorageInfo> 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;