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
7#ifndef QSTORAGEINFO_LINUX_P_H
8#define QSTORAGEINFO_LINUX_P_H
9
10//
11// W A R N I N G
12// -------------
13//
14// This file is not part of the Qt API.
15// This header file may change from version to
16// version without notice, or even be removed.
17//
18// We mean it.
19//
20
21#include "qstorageinfo_p.h"
22
23#include <QtCore/qsystemdetection.h>
24#include <QtCore/private/qlocale_tools_p.h>
25
26#include <sys/sysmacros.h> // makedev()
27
28QT_BEGIN_NAMESPACE
29
30using namespace Qt::StringLiterals;
31using MountInfo = QStorageInfoPrivate::MountInfo;
32
33static const char MountInfoPath[] = "/proc/self/mountinfo";
34
35static std::optional<dev_t> deviceNumber(QByteArrayView devno)
36{
37 // major:minor
38 auto it = devno.cbegin();
39 auto r = qstrntoll(nptr: it, size: devno.size(), base: 10);
40 if (!r.ok())
41 return std::nullopt;
42 int rdevmajor = int(r.result);
43 it += r.used;
44
45 if (*it != ':')
46 return std::nullopt;
47
48 r = qstrntoll(nptr: ++it, size: devno.size() - r.used + 1, base: 10);
49 if (!r.ok())
50 return std::nullopt;
51
52 return makedev(rdevmajor, r.result);
53}
54
55// Helper function to parse paths that the kernel inserts escape sequences
56// for.
57static QByteArray parseMangledPath(QByteArrayView path)
58{
59 // The kernel escapes with octal the following characters:
60 // space ' ', tab '\t', backslash '\\', and newline '\n'
61 // See:
62 // https://codebrowser.dev/linux/linux/fs/proc_namespace.c.html#show_mountinfo
63 // https://codebrowser.dev/linux/linux/fs/seq_file.c.html#mangle_path
64
65 QByteArray ret(path.size(), '\0');
66 char *dst = ret.data();
67 const char *src = path.data();
68 const char *srcEnd = path.data() + path.size();
69 while (src != srcEnd) {
70 switch (*src) {
71 case ' ': // Shouldn't happen
72 return {};
73
74 case '\\': {
75 // It always uses exactly three octal characters.
76 ++src;
77 char c = (*src++ - '0') << 6;
78 c |= (*src++ - '0') << 3;
79 c |= (*src++ - '0');
80 *dst++ = c;
81 break;
82 }
83
84 default:
85 *dst++ = *src++;
86 break;
87 }
88 }
89 // If "path" contains any of the characters this method is demangling,
90 // "ret" would be oversized with extra '\0' characters at the end.
91 ret.resize(size: dst - ret.data());
92 return ret;
93}
94
95// Indexes into the "fields" std::array in parseMountInfo()
96static constexpr short MountId = 0;
97// static constexpr short ParentId = 1;
98static constexpr short DevNo = 2;
99static constexpr short FsRoot = 3;
100static constexpr short MountPoint = 4;
101static constexpr short MountOptions = 5;
102// static constexpr short OptionalFields = 6;
103// static constexpr short Separator = 7;
104static constexpr short FsType = 8;
105static constexpr short MountSource = 9;
106static constexpr short SuperOptions = 10;
107static constexpr short FieldCount = 11;
108
109// Splits a line from /proc/self/mountinfo into fields; fields are separated
110// by a single space.
111static void tokenizeLine(std::array<QByteArrayView, FieldCount> &fields, QByteArrayView line)
112{
113 size_t fieldIndex = 0;
114 qsizetype from = 0;
115 const char *begin = line.data();
116 const qsizetype len = line.size();
117 qsizetype spaceIndex = -1;
118 while ((spaceIndex = line.indexOf(ch: ' ', from)) != -1 && fieldIndex < FieldCount) {
119 fields[fieldIndex] = QByteArrayView{begin + from, begin + spaceIndex};
120 from = spaceIndex;
121
122 // Skip "OptionalFields" and Separator fields
123 if (fieldIndex == MountOptions) {
124 static constexpr char separatorField[] = " - ";
125 const qsizetype sepIndex = line.indexOf(a: separatorField, from);
126 if (sepIndex == -1) {
127 qCWarning(lcStorageInfo,
128 "Malformed line (missing '-' separator field) while parsing '%s':\n%s",
129 MountInfoPath, line.constData());
130 fields.fill(u: {});
131 return;
132 }
133
134 from = sepIndex + strlen(s: separatorField);
135 // Continue parsing at FsType field
136 fieldIndex = FsType;
137 continue;
138 }
139
140 if (from + 1 < len)
141 ++from; // Skip the space at spaceIndex
142
143 ++fieldIndex;
144 }
145
146 // Currently we don't use the last field, so just check the index
147 if (fieldIndex != SuperOptions) {
148 qCInfo(lcStorageInfo,
149 "Expected %d fields while parsing line from '%s', but found %zu instead:\n%.*s",
150 FieldCount, MountInfoPath, fieldIndex, int(line.size()), line.data());
151 fields.fill(u: {});
152 }
153}
154
155// parseMountInfo() is called from:
156// - QStorageInfoPrivate::initRootPath(), where a list of all mounted volumes is needed
157// - QStorageInfoPrivate::mountedVolumes(), where some filesystem types are ignored
158// (see shouldIncludefs())
159enum class FilterMountInfo {
160 All,
161 Filtered,
162};
163
164[[maybe_unused]] static std::vector<MountInfo>
165doParseMountInfo(const QByteArray &mountinfo, FilterMountInfo filter = FilterMountInfo::All)
166{
167 // https://www.kernel.org/doc/Documentation/filesystems/proc.txt:
168 // 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue
169 // (1)(2)(3) (4) (5) (6) (7) (8) (9) (10) (11)
170
171 auto it = mountinfo.cbegin();
172 const auto end = mountinfo.cend();
173 auto nextLine = [&it, &end]() -> QByteArrayView {
174 auto nIt = std::find(first: it, last: end, val: '\n');
175 if (nIt != end) {
176 QByteArrayView ba(it, nIt);
177 it = ++nIt; // Advance
178 return ba;
179 }
180 return {};
181 };
182
183 std::vector<MountInfo> infos;
184 std::array<QByteArrayView, FieldCount> fields;
185 QByteArrayView line;
186
187 auto checkField = [&line](QByteArrayView field) {
188 if (field.isEmpty()) {
189 qDebug(msg: "Failed to parse line from %s:\n%.*s", MountInfoPath, int(line.size()),
190 line.data());
191 return false;
192 }
193 return true;
194 };
195
196 // mountinfo has a stable format, no empty lines
197 while (!(line = nextLine()).isEmpty()) {
198 fields.fill(u: {});
199 tokenizeLine(fields, line);
200
201 MountInfo info;
202 if (auto r = qstrntoll(nptr: fields[MountId].data(), size: fields[MountId].size(), base: 10); r.ok()) {
203 info.mntid = r.result;
204 } else {
205 checkField({});
206 continue;
207 }
208
209 QByteArray mountP = parseMangledPath(path: fields[MountPoint]);
210 if (!checkField(mountP))
211 continue;
212 info.mountPoint = QFile::decodeName(localFileName: mountP);
213
214 if (!checkField(fields[FsType]))
215 continue;
216 info.fsType = fields[FsType].toByteArray();
217
218 if (filter == FilterMountInfo::Filtered
219 && !QStorageInfoPrivate::shouldIncludeFs(mountDir: info.mountPoint, fsType: info.fsType))
220 continue;
221
222 std::optional<dev_t> devno = deviceNumber(devno: fields[DevNo]);
223 if (!devno) {
224 checkField({});
225 continue;
226 }
227 info.stDev = *devno;
228
229 QByteArrayView fsRootView = fields[FsRoot];
230 if (!checkField(fsRootView))
231 continue;
232
233 // If the filesystem root is "/" -- it's not a *sub*-volume/bind-mount,
234 // in that case we leave info.fsRoot empty
235 if (fsRootView != "/") {
236 info.fsRoot = parseMangledPath(path: fsRootView);
237 if (!checkField(info.fsRoot))
238 continue;
239 }
240
241 info.device = parseMangledPath(path: fields[MountSource]);
242 if (!checkField(info.device))
243 continue;
244
245 infos.push_back(x: std::move(info));
246 }
247 return infos;
248}
249
250QT_END_NAMESPACE
251
252#endif // QSTORAGEINFO_LINUX_P_H
253

source code of qtbase/src/corelib/io/qstorageinfo_linux_p.h