Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qstorageinfo_linux.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// Copyright (C) 2014 Ivan Komissarov <ABBAPOH@gmail.com>
3// Copyright (C) 2016 Intel Corporation.
4// Copyright (C) 2023 Ahmad Samir <a.samirh78@gmail.com>
5// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
6
8
9#include "qdirlisting.h"
10#include <private/qcore_unix_p.h>
11#include <private/qtools_p.h>
12
13#include <q20memory.h>
14
15#include <sys/ioctl.h>
16#include <sys/statfs.h>
17
18// so we don't have to #include <linux/fs.h>, which is known to cause conflicts
19#ifndef FSLABEL_MAX
20# define FSLABEL_MAX 256
21#endif
22#ifndef FS_IOC_GETFSLABEL
23# define FS_IOC_GETFSLABEL _IOR(0x94, 49, char[FSLABEL_MAX])
24#endif
25
26// or <linux/statfs.h>
27#ifndef ST_RDONLY
28# define ST_RDONLY 0x0001 /* mount read-only */
29#endif
30
32
33using namespace Qt::StringLiterals;
34
35// udev encodes the labels with ID_LABEL_FS_ENC which is done with
36// blkid_encode_string(). Within this function some 1-byte utf-8
37// characters not considered safe (e.g. '\' or ' ') are encoded as hex
39{
40 using namespace QtMiscUtils;
41 qsizetype start = str.indexOf(u'\\');
42 if (start < 0)
43 return std::move(str);
44
45 // decode in-place
46 QString decoded = std::move(str);
47 auto ptr = reinterpret_cast<char16_t *>(decoded.begin());
50 qsizetype size = decoded.size();
51
52 while (in < size) {
53 Q_ASSERT(ptr[in] == u'\\');
54 if (size - in >= 4 && ptr[in + 1] == u'x') { // we need four characters: \xAB
55 int c = fromHex(ptr[in + 2]) << 4;
56 c |= fromHex(ptr[in + 3]);
57 if (Q_UNLIKELY(c < 0))
58 c = QChar::ReplacementCharacter; // bad hex sequence
59 ptr[out++] = c;
60 in += 4;
61 }
62
63 for ( ; in < size; ++in) {
64 char16_t c = ptr[in];
65 if (c == u'\\')
66 break;
67 ptr[out++] = c;
68 }
69 }
70 decoded.resize(out);
71 return decoded;
72}
73
74static inline dev_t deviceIdForPath(const QString &device)
75{
76 QT_STATBUF st;
77 if (QT_STAT(QFile::encodeName(device), &st) < 0)
78 return 0;
79 return st.st_dev;
80}
81
82static inline quint64 retrieveDeviceId(const QByteArray &device, quint64 deviceId = 0)
83{
84 // major = 0 implies an anonymous block device, so we need to stat() the
85 // actual device to get its dev_t. This is required for btrfs (and possibly
86 // others), which always uses them for all the subvolumes (including the
87 // root):
88 // https://codebrowser.dev/linux/linux/fs/btrfs/disk-io.c.html#btrfs_init_fs_root
89 // https://codebrowser.dev/linux/linux/fs/super.c.html#get_anon_bdev
90 // For everything else, we trust the parameter.
91 if (major(deviceId) != 0)
92 return deviceId;
93
94 // don't even try to stat() a relative path or "/"
95 if (device.size() < 2 || !device.startsWith('/'))
96 return 0;
97
98 QT_STATBUF st;
99 if (QT_STAT(device, &st) < 0)
100 return 0;
101 if (!S_ISBLK(st.st_mode))
102 return 0;
103 return st.st_rdev;
104}
105
107{
108 static const char pathDiskByLabel[] = "/dev/disk/by-label";
109 static constexpr auto LabelFileFilter =
111
112 return QDirListing(QLatin1StringView(pathDiskByLabel), LabelFileFilter);
113}
114
115static inline auto retrieveLabels()
116{
117 struct Entry {
119 quint64 deviceId;
120 };
121 QList<Entry> result;
122
123 for (const auto &dirEntry : devicesByLabel()) {
124 quint64 deviceId = retrieveDeviceId(QFile::encodeName(dirEntry.filePath()));
125 if (!deviceId)
126 continue;
127 result.emplaceBack(Entry{ decodeFsEncString(dirEntry.fileName()), deviceId });
128 }
129 return result;
130}
131
132static std::optional<QString> retrieveLabelViaIoctl(const QString &path)
133{
134 // FS_IOC_GETFSLABEL was introduced in v4.18; previously it was btrfs-specific.
135 int fd = qt_safe_open(QFile::encodeName(path).constData(), QT_OPEN_RDONLY);
136 if (fd < 0)
137 return std::nullopt;
138
139 // Note: it doesn't append the null terminator (despite what the man page
140 // says) and the return code on success (0) does not indicate the length.
141 char label[FSLABEL_MAX] = {};
142 int r = ioctl(fd, FS_IOC_GETFSLABEL, &label);
143 close(fd);
144 if (r < 0)
145 return std::nullopt;
146 return QString::fromUtf8(label);
147}
148
149static inline QString retrieveLabel(const QStorageInfoPrivate &d, quint64 deviceId)
150{
151 if (auto label = retrieveLabelViaIoctl(d.rootPath))
152 return *label;
153
154 deviceId = retrieveDeviceId(d.device, deviceId);
155 if (!deviceId)
156 return QString();
157
158 for (const auto &dirEntry : devicesByLabel()) {
159 if (retrieveDeviceId(QFile::encodeName(dirEntry.filePath())) == deviceId)
160 return decodeFsEncString(dirEntry.fileName());
161 }
162 return QString();
163}
164
165void QStorageInfoPrivate::retrieveVolumeInfo()
166{
167 struct statfs64 statfs_buf;
168 int result;
169 QT_EINTR_LOOP(result, statfs64(QFile::encodeName(rootPath).constData(), &statfs_buf));
170 if (result == 0) {
171 valid = true;
172 ready = true;
173
174 bytesTotal = statfs_buf.f_blocks * statfs_buf.f_frsize;
175 bytesFree = statfs_buf.f_bfree * statfs_buf.f_frsize;
176 bytesAvailable = statfs_buf.f_bavail * statfs_buf.f_frsize;
177 blockSize = int(statfs_buf.f_bsize);
178 readOnly = (statfs_buf.f_flags & ST_RDONLY) != 0;
179 }
180}
181
183{
184 QFile file(u"/proc/self/mountinfo"_s);
186 return {};
187
188 QByteArray mountinfo = file.readAll();
189 file.close();
190
191 return doParseMountInfo(mountinfo, filter);
192}
193
194void QStorageInfoPrivate::doStat()
195{
196 retrieveVolumeInfo();
197 if (!ready)
198 return;
199
200 rootPath = QFileInfo(rootPath).canonicalFilePath();
201 if (rootPath.isEmpty())
202 return;
203
204 std::vector<MountInfo> infos = parseMountInfo();
205 if (infos.empty()) {
206 rootPath = u'/';
207 return;
208 }
209
210 // We iterate over the /proc/self/mountinfo list backwards because then any
211 // matching isParentOf must be the actual mount point because it's the most
212 // recent mount on that path. Linux does allow mounting over non-empty
213 // directories, such as in:
214 // # mount | tail -2
215 // tmpfs on /tmp/foo/bar type tmpfs (rw,relatime,inode64)
216 // tmpfs on /tmp/foo type tmpfs (rw,relatime,inode64)
217 //
218 // We try to match the device ID in case there's a mount --move.
219 // We can't *rely* on it because some filesystems like btrfs will assign
220 // device IDs to subvolumes that aren't listed in /proc/self/mountinfo.
221
222 const QString oldRootPath = std::exchange(rootPath, QString());
223 const dev_t rootPathDevId = deviceIdForPath(oldRootPath);
224 MountInfo *best = nullptr;
225 for (auto it = infos.rbegin(); it != infos.rend(); ++it) {
226 if (!isParentOf(it->mountPoint, oldRootPath))
227 continue;
228 if (rootPathDevId == it->stDev) {
229 // device ID matches; this is definitely the best option
230 best = q20::to_address(it);
231 break;
232 }
233 if (!best) {
234 // if we can't find a device ID match, this parent path is probably
235 // the correct one
236 best = q20::to_address(it);
237 }
238 }
239 if (best) {
240 auto stDev = best->stDev;
241 setFromMountInfo(std::move(*best));
242 name = retrieveLabel(*this, stDev);
243 }
244}
245
246QList<QStorageInfo> QStorageInfoPrivate::mountedVolumes()
247{
248 std::vector<MountInfo> infos = parseMountInfo(FilterMountInfo::Filtered);
249 if (infos.empty())
250 return QList{root()};
251
252 std::optional<decltype(retrieveLabels())> labelMap;
253 auto labelForDevice = [&labelMap](const QStorageInfoPrivate &d, quint64 devid) {
254 if (d.fileSystemType == "tmpfs")
255 return QString();
256
257 if (auto label = retrieveLabelViaIoctl(d.rootPath))
258 return *label;
259
260 devid = retrieveDeviceId(d.device, devid);
261 if (!devid)
262 return QString();
263
264 if (!labelMap)
265 labelMap = retrieveLabels();
266 for (auto &[deviceLabel, deviceId] : std::as_const(*labelMap)) {
267 if (devid == deviceId)
268 return deviceLabel;
269 }
270 return QString();
271 };
272
273 QList<QStorageInfo> volumes;
274 for (MountInfo &info : infos) {
275 const auto infoStDev = info.stDev;
276 QStorageInfoPrivate d(std::move(info));
277 d.retrieveVolumeInfo();
278 if (d.bytesTotal <= 0 && d.rootPath != u'/')
279 continue;
280 if (infoStDev != deviceIdForPath(d.rootPath))
281 continue; // probably something mounted over this mountpoint
282 d.name = labelForDevice(d, infoStDev);
283 volumes.emplace_back(QStorageInfo(*new QStorageInfoPrivate(std::move(d))));
284 }
285 return volumes;
286}
287
IOBluetoothDevice * device
\inmodule QtCore
Definition qbytearray.h:57
The QDirListing class provides an STL-style iterator for directory entries.
Definition qdirlisting.h:18
@ Hidden
Definition qdir.h:35
@ AllEntries
Definition qdir.h:26
@ System
Definition qdir.h:36
@ NoDotAndDotDot
Definition qdir.h:44
void close() override
Calls QFileDevice::flush() and closes the file.
QString canonicalFilePath() const
Returns the file system entry's canonical path, including the entry's name, that is,...
\inmodule QtCore
Definition qfile.h:93
QFILE_MAYBE_NODISCARD bool open(OpenMode flags) override
Opens the file using OpenMode mode, returning true if successful; otherwise false.
Definition qfile.cpp:904
static QByteArray encodeName(const QString &fileName)
Converts fileName to an 8-bit encoding that you can use in native APIs.
Definition qfile.h:158
QByteArray readAll()
Reads all remaining data from the device, and returns it as a byte array.
Definition qlist.h:75
\inmodule QtCore
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
iterator begin()
Returns an \l{STL-style iterators}{STL-style iterator} pointing to the first character in the string.
Definition qstring.h:1349
qsizetype indexOf(QLatin1StringView s, qsizetype from=0, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
Definition qstring.cpp:4517
qsizetype size() const noexcept
Returns the number of characters in this string.
Definition qstring.h:186
static QString fromUtf8(QByteArrayView utf8)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:6018
void resize(qsizetype size)
Sets the size of the string to size characters.
Definition qstring.cpp:2668
QString str
[2]
QSet< QString >::iterator it
Combined button and popup list for selecting options.
constexpr T * to_address(T *p) noexcept
Definition q20memory.h:57
const int blockSize
#define Q_UNLIKELY(x)
static int qt_safe_open(const char *pathname, int flags, mode_t mode=0777)
#define QT_EINTR_LOOP(var, cmd)
static ControlElement< T > * ptr(QWidget *widget)
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLboolean r
[2]
GLuint GLsizei const GLchar * label
[43]
GLint GLint GLint GLint GLint GLint GLint GLbitfield GLenum filter
GLuint start
GLuint64 GLenum GLint fd
GLuint name
const GLubyte * c
GLuint in
GLsizei const GLchar *const * path
GLuint64EXT * result
[6]
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
static std::optional< QString > retrieveLabelViaIoctl(const QString &path)
static QString retrieveLabel(const QStorageInfoPrivate &d, quint64 deviceId)
static quint64 retrieveDeviceId(const QByteArray &device, quint64 deviceId=0)
#define FS_IOC_GETFSLABEL
#define ST_RDONLY
static QDirListing devicesByLabel()
static QString decodeFsEncString(QString &&str)
static std::vector< MountInfo > parseMountInfo(FilterMountInfo filter=FilterMountInfo::All)
static auto retrieveLabels()
static dev_t deviceIdForPath(const QString &device)
#define FSLABEL_MAX
static std::vector< MountInfo > doParseMountInfo(const QByteArray &mountinfo, FilterMountInfo filter=FilterMountInfo::All)
QStorageInfoPrivate::MountInfo MountInfo
QT_BEGIN_NAMESPACE static Q_LOGGING_CATEGORY(lcStorageInfo, "qt.core.qstorageinfo", QtWarningMsg) class QStorageInfoPrivate bool isParentOf(const String &parent, const QString &dirName)
unsigned long long quint64
Definition qtypes.h:61
ptrdiff_t qsizetype
Definition qtypes.h:165
QFile file
[0]
QTextStream out(stdout)
[7]
QHostInfo info
[0]