1 | // Copyright (C) 2021 The Qt Company Ltd. |
2 | // Copyright (C) 2014 Ivan Komissarov <ABBAPOH@gmail.com> |
3 | // Copyright (C) 2016 Intel Corporation. |
4 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
5 | |
6 | #include "qstorageinfo_p.h" |
7 | |
8 | #include <QtCore/qdiriterator.h> |
9 | #include <QtCore/qfileinfo.h> |
10 | #include <QtCore/qtextstream.h> |
11 | |
12 | #include <QtCore/private/qcore_unix_p.h> |
13 | #include <QtCore/private/qlocale_tools_p.h> |
14 | |
15 | #include <errno.h> |
16 | #include <sys/stat.h> |
17 | |
18 | #if defined(Q_OS_BSD4) |
19 | # include <sys/mount.h> |
20 | # include <sys/statvfs.h> |
21 | #elif defined(Q_OS_ANDROID) |
22 | # include <sys/mount.h> |
23 | # include <sys/vfs.h> |
24 | # include <mntent.h> |
25 | #elif defined(Q_OS_LINUX) || defined(Q_OS_HURD) |
26 | # include <mntent.h> |
27 | # include <sys/statvfs.h> |
28 | # include <sys/sysmacros.h> |
29 | #elif defined(Q_OS_SOLARIS) |
30 | # include <sys/mnttab.h> |
31 | # include <sys/statvfs.h> |
32 | #elif defined(Q_OS_HAIKU) |
33 | # include <Directory.h> |
34 | # include <Path.h> |
35 | # include <Volume.h> |
36 | # include <VolumeRoster.h> |
37 | # include <fs_info.h> |
38 | # include <sys/statvfs.h> |
39 | #else |
40 | # include <sys/statvfs.h> |
41 | #endif |
42 | |
43 | #if defined(Q_OS_BSD4) |
44 | # if defined(Q_OS_NETBSD) |
45 | # define QT_STATFSBUF struct statvfs |
46 | # define QT_STATFS ::statvfs |
47 | # else |
48 | # define QT_STATFSBUF struct statfs |
49 | # define QT_STATFS ::statfs |
50 | # endif |
51 | |
52 | # if !defined(ST_RDONLY) |
53 | # define ST_RDONLY MNT_RDONLY |
54 | # endif |
55 | # if !defined(_STATFS_F_FLAGS) && !defined(Q_OS_NETBSD) |
56 | # define _STATFS_F_FLAGS 1 |
57 | # endif |
58 | #elif defined(Q_OS_ANDROID) |
59 | # define QT_STATFS ::statfs |
60 | # define QT_STATFSBUF struct statfs |
61 | # if !defined(ST_RDONLY) |
62 | # define ST_RDONLY 1 // hack for missing define on Android |
63 | # endif |
64 | #elif defined(Q_OS_HAIKU) |
65 | # define QT_STATFSBUF struct statvfs |
66 | # define QT_STATFS ::statvfs |
67 | #else |
68 | # if defined(QT_LARGEFILE_SUPPORT) |
69 | # define QT_STATFSBUF struct statvfs64 |
70 | # define QT_STATFS ::statvfs64 |
71 | # else |
72 | # define QT_STATFSBUF struct statvfs |
73 | # define QT_STATFS ::statvfs |
74 | # endif // QT_LARGEFILE_SUPPORT |
75 | #endif // Q_OS_BSD4 |
76 | |
77 | #if __has_include(<paths.h>) |
78 | # include <paths.h> |
79 | #endif |
80 | #ifndef _PATH_MOUNTED |
81 | # define _PATH_MOUNTED "/etc/mnttab" |
82 | #endif |
83 | |
84 | QT_BEGIN_NAMESPACE |
85 | |
86 | using namespace Qt::StringLiterals; |
87 | |
88 | class QStorageIterator |
89 | { |
90 | public: |
91 | QStorageIterator(); |
92 | ~QStorageIterator(); |
93 | |
94 | inline bool isValid() const; |
95 | inline bool next(); |
96 | inline QString rootPath() const; |
97 | inline QByteArray fileSystemType() const; |
98 | inline QByteArray device() const; |
99 | inline QByteArray options() const; |
100 | inline QByteArray subvolume() const; |
101 | private: |
102 | #if defined(Q_OS_BSD4) |
103 | QT_STATFSBUF *stat_buf; |
104 | int entryCount; |
105 | int currentIndex; |
106 | #elif defined(Q_OS_SOLARIS) |
107 | FILE *fp; |
108 | mnttab mnt; |
109 | #elif defined(Q_OS_ANDROID) |
110 | QFile file; |
111 | QByteArray m_rootPath; |
112 | QByteArray m_fileSystemType; |
113 | QByteArray m_device; |
114 | QByteArray m_options; |
115 | #elif defined(Q_OS_LINUX) || defined(Q_OS_HURD) |
116 | struct mountinfoent : public mntent { |
117 | // Details from proc(5) section from /proc/<pid>/mountinfo: |
118 | //(1) mount ID: a unique ID for the mount (may be reused after umount(2)). |
119 | int mount_id; |
120 | //(2) parent ID: the ID of the parent mount (or of self for the top of the mount tree). |
121 | // int parent_id; |
122 | //(3) major:minor: the value of st_dev for files on this filesystem (see stat(2)). |
123 | dev_t rdev; |
124 | //(4) root: the pathname of the directory in the filesystem which forms the root of this mount. |
125 | char *subvolume; |
126 | //(5) mount point: the pathname of the mount point relative to the process's root directory. |
127 | // char *mnt_dir; // in mntent |
128 | //(6) mount options: per-mount options. |
129 | // char *mnt_opts; // in mntent |
130 | //(7) optional fields: zero or more fields of the form "tag[:value]"; see below. |
131 | // int flags; |
132 | //(8) separator: the end of the optional fields is marked by a single hyphen. |
133 | |
134 | //(9) filesystem type: the filesystem type in the form "type[.subtype]". |
135 | // char *mnt_type; // in mntent |
136 | //(10) mount source: filesystem-specific information or "none". |
137 | // char *mnt_fsname; // in mntent |
138 | //(11) super options: per-superblock options. |
139 | char *superopts; |
140 | }; |
141 | |
142 | FILE *fp; |
143 | QByteArray buffer; |
144 | mountinfoent mnt; |
145 | bool usingMountinfo; |
146 | #elif defined(Q_OS_HAIKU) |
147 | BVolumeRoster m_volumeRoster; |
148 | |
149 | QByteArray m_rootPath; |
150 | QByteArray m_fileSystemType; |
151 | QByteArray m_device; |
152 | #endif |
153 | }; |
154 | |
155 | template <typename String> |
156 | static bool isParentOf(const String &parent, const QString &dirName) |
157 | { |
158 | return dirName.startsWith(parent) && |
159 | (dirName.size() == parent.size() || dirName.at(i: parent.size()) == u'/' || |
160 | parent.size() == 1); |
161 | } |
162 | |
163 | static bool shouldIncludeFs(const QStorageIterator &it) |
164 | { |
165 | /* |
166 | * This function implements a heuristic algorithm to determine whether a |
167 | * given mount should be reported to the user. Our objective is to list |
168 | * only entries that the end-user would find useful. |
169 | * |
170 | * We therefore ignore: |
171 | * - mounted in /dev, /proc, /sys: special mounts |
172 | * (this will catch /sys/fs/cgroup, /proc/sys/fs/binfmt_misc, /dev/pts, |
173 | * some of which are tmpfs on Linux) |
174 | * - mounted in /var/run or /var/lock: most likely pseudofs |
175 | * (on earlier systemd versions, /var/run was a bind-mount of /run, so |
176 | * everything would be unnecessarily duplicated) |
177 | * - filesystem type is "rootfs": artifact of the root-pivot on some Linux |
178 | * initrd |
179 | * - if the filesystem total size is zero, it's a pseudo-fs (not checked here). |
180 | */ |
181 | |
182 | QString mountDir = it.rootPath(); |
183 | if (isParentOf(parent: "/dev"_L1 , dirName: mountDir) |
184 | || isParentOf(parent: "/proc"_L1 , dirName: mountDir) |
185 | || isParentOf(parent: "/sys"_L1 , dirName: mountDir) |
186 | || isParentOf(parent: "/var/run"_L1 , dirName: mountDir) |
187 | || isParentOf(parent: "/var/lock"_L1 , dirName: mountDir)) { |
188 | return false; |
189 | } |
190 | |
191 | #if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID) |
192 | if (it.fileSystemType() == "rootfs" ) |
193 | return false; |
194 | #endif |
195 | |
196 | // size checking in mountedVolumes() |
197 | return true; |
198 | } |
199 | |
200 | #if defined(Q_OS_BSD4) |
201 | |
202 | #ifndef MNT_NOWAIT |
203 | # define MNT_NOWAIT 0 |
204 | #endif |
205 | |
206 | inline QStorageIterator::QStorageIterator() |
207 | : entryCount(::getmntinfo(&stat_buf, MNT_NOWAIT)), |
208 | currentIndex(-1) |
209 | { |
210 | } |
211 | |
212 | inline QStorageIterator::~QStorageIterator() |
213 | { |
214 | } |
215 | |
216 | inline bool QStorageIterator::isValid() const |
217 | { |
218 | return entryCount != -1; |
219 | } |
220 | |
221 | inline bool QStorageIterator::next() |
222 | { |
223 | return ++currentIndex < entryCount; |
224 | } |
225 | |
226 | inline QString QStorageIterator::rootPath() const |
227 | { |
228 | return QFile::decodeName(stat_buf[currentIndex].f_mntonname); |
229 | } |
230 | |
231 | inline QByteArray QStorageIterator::fileSystemType() const |
232 | { |
233 | return QByteArray(stat_buf[currentIndex].f_fstypename); |
234 | } |
235 | |
236 | inline QByteArray QStorageIterator::device() const |
237 | { |
238 | return QByteArray(stat_buf[currentIndex].f_mntfromname); |
239 | } |
240 | |
241 | inline QByteArray QStorageIterator::options() const |
242 | { |
243 | return QByteArray(); |
244 | } |
245 | |
246 | inline QByteArray QStorageIterator::subvolume() const |
247 | { |
248 | return QByteArray(); |
249 | } |
250 | #elif defined(Q_OS_SOLARIS) |
251 | |
252 | inline QStorageIterator::QStorageIterator() |
253 | { |
254 | const int fd = qt_safe_open(_PATH_MOUNTED, O_RDONLY); |
255 | fp = ::fdopen(fd, "r" ); |
256 | } |
257 | |
258 | inline QStorageIterator::~QStorageIterator() |
259 | { |
260 | if (fp) |
261 | ::fclose(fp); |
262 | } |
263 | |
264 | inline bool QStorageIterator::isValid() const |
265 | { |
266 | return fp != nullptr; |
267 | } |
268 | |
269 | inline bool QStorageIterator::next() |
270 | { |
271 | return ::getmntent(fp, &mnt) == 0; |
272 | } |
273 | |
274 | inline QString QStorageIterator::rootPath() const |
275 | { |
276 | return QFile::decodeName(mnt.mnt_mountp); |
277 | } |
278 | |
279 | inline QByteArray QStorageIterator::fileSystemType() const |
280 | { |
281 | return QByteArray(mnt.mnt_fstype); |
282 | } |
283 | |
284 | inline QByteArray QStorageIterator::device() const |
285 | { |
286 | return QByteArray(mnt.mnt_mntopts); |
287 | } |
288 | |
289 | inline QByteArray QStorageIterator::subvolume() const |
290 | { |
291 | return QByteArray(); |
292 | } |
293 | #elif defined(Q_OS_ANDROID) |
294 | |
295 | inline QStorageIterator::QStorageIterator() |
296 | { |
297 | file.setFileName(QString::fromUtf8(_PATH_MOUNTED)); |
298 | file.open(QIODevice::ReadOnly | QIODevice::Text); |
299 | } |
300 | |
301 | inline QStorageIterator::~QStorageIterator() |
302 | { |
303 | } |
304 | |
305 | inline bool QStorageIterator::isValid() const |
306 | { |
307 | return file.isOpen(); |
308 | } |
309 | |
310 | inline bool QStorageIterator::next() |
311 | { |
312 | QList<QByteArray> data; |
313 | // If file is virtual, file.readLine() may succeed even when file.atEnd(). |
314 | do { |
315 | const QByteArray line = file.readLine(); |
316 | if (line.isEmpty() && file.atEnd()) |
317 | return false; |
318 | data = line.split(' '); |
319 | } while (data.count() < 4); |
320 | |
321 | m_device = data.at(0); |
322 | m_rootPath = data.at(1); |
323 | m_fileSystemType = data.at(2); |
324 | m_options = data.at(3); |
325 | |
326 | return true; |
327 | } |
328 | |
329 | inline QString QStorageIterator::rootPath() const |
330 | { |
331 | return QFile::decodeName(m_rootPath); |
332 | } |
333 | |
334 | inline QByteArray QStorageIterator::fileSystemType() const |
335 | { |
336 | return m_fileSystemType; |
337 | } |
338 | |
339 | inline QByteArray QStorageIterator::device() const |
340 | { |
341 | return m_device; |
342 | } |
343 | |
344 | inline QByteArray QStorageIterator::options() const |
345 | { |
346 | return m_options; |
347 | } |
348 | |
349 | inline QByteArray QStorageIterator::subvolume() const |
350 | { |
351 | return QByteArray(); |
352 | } |
353 | #elif defined(Q_OS_LINUX) || defined(Q_OS_HURD) |
354 | |
355 | static const int bufferSize = 1024; // 2 paths (mount point+device) and metainfo; |
356 | // should be enough |
357 | |
358 | inline QStorageIterator::QStorageIterator() : |
359 | buffer(QByteArray(bufferSize, 0)) |
360 | { |
361 | fp = nullptr; |
362 | |
363 | #ifdef Q_OS_LINUX |
364 | // first, try to open /proc/self/mountinfo, which has more details |
365 | fp = ::fopen(filename: "/proc/self/mountinfo" , modes: "re" ); |
366 | #endif |
367 | if (fp) { |
368 | usingMountinfo = true; |
369 | } else { |
370 | usingMountinfo = false; |
371 | fp = ::setmntent(_PATH_MOUNTED, mode: "r" ); |
372 | } |
373 | } |
374 | |
375 | inline QStorageIterator::~QStorageIterator() |
376 | { |
377 | if (fp) { |
378 | if (usingMountinfo) |
379 | ::fclose(stream: fp); |
380 | else |
381 | ::endmntent(stream: fp); |
382 | } |
383 | } |
384 | |
385 | inline bool QStorageIterator::isValid() const |
386 | { |
387 | return fp != nullptr; |
388 | } |
389 | |
390 | inline bool QStorageIterator::next() |
391 | { |
392 | mnt.subvolume = nullptr; |
393 | mnt.superopts = nullptr; |
394 | if (!usingMountinfo) |
395 | return ::getmntent_r(stream: fp, result: &mnt, buffer: buffer.data(), bufsize: buffer.size()) != nullptr; |
396 | |
397 | // Helper function to parse paths that the kernel inserts escape sequences |
398 | // for. The unescaped string is left at \a src and is properly |
399 | // NUL-terminated. Returns a pointer to the delimiter that terminated the |
400 | // path, or nullptr if it failed. |
401 | auto parseMangledPath = [](char *src) { |
402 | // The kernel escapes with octal the following characters: |
403 | // space ' ', tab '\t', backslask '\\', and newline '\n' |
404 | char *dst = src; |
405 | while (*src) { |
406 | switch (*src) { |
407 | case ' ': |
408 | // Unescaped space: end of the field. |
409 | *dst = '\0'; |
410 | return src; |
411 | |
412 | default: |
413 | *dst++ = *src++; |
414 | break; |
415 | |
416 | case '\\': |
417 | // It always uses exactly three octal characters. |
418 | ++src; |
419 | char c = (*src++ - '0') << 6; |
420 | c |= (*src++ - '0') << 3; |
421 | c |= (*src++ - '0'); |
422 | *dst++ = c; |
423 | break; |
424 | } |
425 | } |
426 | |
427 | // Found a NUL before the end of the field. |
428 | src = nullptr; |
429 | return src; |
430 | }; |
431 | |
432 | char *ptr = buffer.data(); |
433 | if (fgets(s: ptr, n: buffer.size(), stream: fp) == nullptr) |
434 | return false; |
435 | |
436 | size_t len = strlen(s: ptr); |
437 | if (len == 0) |
438 | return false; |
439 | while (Q_UNLIKELY(ptr[len - 1] != '\n' && !feof(fp))) { |
440 | // buffer wasn't large enough. Enlarge and try again. |
441 | // (we're readidng from the kernel, so OOM is unlikely) |
442 | buffer.resize(size: (buffer.size() + 4096) & ~4095); |
443 | ptr = buffer.data(); |
444 | if (fgets(s: ptr + len, n: buffer.size() - len, stream: fp) == nullptr) |
445 | return false; |
446 | |
447 | len += strlen(s: ptr + len); |
448 | Q_ASSERT(len < size_t(buffer.size())); |
449 | } |
450 | ptr[len - 1] = '\0'; |
451 | const char *const stop = ptr + len - 1; |
452 | |
453 | // parse the line |
454 | mnt.mnt_freq = 0; |
455 | mnt.mnt_passno = 0; |
456 | |
457 | auto r = qstrntoll(nptr: ptr, size: stop - ptr, base: 10); |
458 | if (!r.ok()) |
459 | return false; |
460 | mnt.mount_id = r.result; |
461 | |
462 | ptr += r.used; |
463 | r = qstrntoll(nptr: ptr, size: stop - ptr, base: 10); |
464 | if (!r.ok()) |
465 | return false; |
466 | // parent_id = r.result; // member currently not in use |
467 | |
468 | ptr += r.used; |
469 | r = qstrntoll(nptr: ptr, size: stop - ptr, base: 10); |
470 | if (!r.ok()) |
471 | return false; |
472 | ptr += r.used; |
473 | if (*ptr != ':') |
474 | return false; |
475 | int rdevmajor = r.result; |
476 | ++ptr; // Skip over the ':' |
477 | r = qstrntoll(nptr: ptr, size: stop - ptr, base: 10); |
478 | if (!r.ok()) |
479 | return false; |
480 | mnt.rdev = makedev(rdevmajor, r.result); |
481 | |
482 | ptr += r.used; |
483 | if (*ptr != ' ') |
484 | return false; |
485 | |
486 | mnt.subvolume = ++ptr; |
487 | ptr = parseMangledPath(ptr); |
488 | if (!ptr) |
489 | return false; |
490 | |
491 | // unset a subvolume of "/" -- it's not a *sub* volume |
492 | if (mnt.subvolume + 1 == ptr) |
493 | *mnt.subvolume = '\0'; |
494 | |
495 | mnt.mnt_dir = ++ptr; |
496 | ptr = parseMangledPath(ptr); |
497 | if (!ptr) |
498 | return false; |
499 | |
500 | mnt.mnt_opts = ++ptr; |
501 | ptr = strchr(s: ptr, c: ' '); |
502 | if (!ptr) |
503 | return false; |
504 | |
505 | // we don't parse the flags, so just find the separator |
506 | if (char *const dashed = strstr(haystack: ptr, needle: " - " )) { |
507 | *ptr = '\0'; |
508 | ptr = dashed + strlen(s: " - " ) - 1; |
509 | } else { |
510 | return false; |
511 | } |
512 | |
513 | mnt.mnt_type = ++ptr; |
514 | ptr = strchr(s: ptr, c: ' '); |
515 | if (!ptr) |
516 | return false; |
517 | *ptr = '\0'; |
518 | |
519 | mnt.mnt_fsname = ++ptr; |
520 | ptr = parseMangledPath(ptr); |
521 | if (!ptr) |
522 | return false; |
523 | |
524 | mnt.superopts = ++ptr; |
525 | ptr += strcspn(s: ptr, reject: " \n" ); |
526 | *ptr = '\0'; |
527 | |
528 | return true; |
529 | } |
530 | |
531 | inline QString QStorageIterator::rootPath() const |
532 | { |
533 | return QFile::decodeName(localFileName: mnt.mnt_dir); |
534 | } |
535 | |
536 | inline QByteArray QStorageIterator::fileSystemType() const |
537 | { |
538 | return QByteArray(mnt.mnt_type); |
539 | } |
540 | |
541 | inline QByteArray QStorageIterator::device() const |
542 | { |
543 | #ifdef Q_OS_LINUX |
544 | // check that the device exists |
545 | if (mnt.mnt_fsname[0] == '/' && access(name: mnt.mnt_fsname, F_OK) != 0) { |
546 | // It doesn't, so let's try to resolve the dev_t from /dev/block. |
547 | // Note how strlen("4294967295") == digits10 + 1, so we need to add 1 |
548 | // for each number, plus the ':'. |
549 | char buf[sizeof("/dev/block/" ) + 2 * std::numeric_limits<unsigned>::digits10 + 3]; |
550 | QByteArray dev(PATH_MAX, Qt::Uninitialized); |
551 | char *devdata = dev.data(); |
552 | |
553 | snprintf(s: buf, maxlen: sizeof(buf), format: "/dev/block/%u:%u" , major(mnt.rdev), minor(mnt.rdev)); |
554 | if (realpath(name: buf, resolved: devdata)) { |
555 | dev.truncate(pos: strlen(s: devdata)); |
556 | return dev; |
557 | } |
558 | } |
559 | #endif |
560 | return QByteArray(mnt.mnt_fsname); |
561 | } |
562 | |
563 | inline QByteArray QStorageIterator::options() const |
564 | { |
565 | // Merge the two options, starting with the superblock options and letting |
566 | // the per-mount options override. |
567 | const char *superopts = mnt.superopts; |
568 | |
569 | // Both mnt_opts and superopts start with "ro" or "rw", so we can skip the |
570 | // superblock's field (see show_mountinfo() in fs/proc_namespace.c). |
571 | if (superopts && superopts[0] == 'r') { |
572 | if (superopts[2] == '\0') // no other superopts besides "ro" / "rw"? |
573 | superopts = nullptr; |
574 | else if (superopts[2] == ',') |
575 | superopts += 3; |
576 | } |
577 | |
578 | if (superopts) |
579 | return QByteArray(superopts) + ',' + mnt.mnt_opts; |
580 | return QByteArray(mnt.mnt_opts); |
581 | } |
582 | |
583 | inline QByteArray QStorageIterator::subvolume() const |
584 | { |
585 | return QByteArray(mnt.subvolume); |
586 | } |
587 | #elif defined(Q_OS_HAIKU) |
588 | inline QStorageIterator::QStorageIterator() |
589 | { |
590 | } |
591 | |
592 | inline QStorageIterator::~QStorageIterator() |
593 | { |
594 | } |
595 | |
596 | inline bool QStorageIterator::isValid() const |
597 | { |
598 | return true; |
599 | } |
600 | |
601 | inline bool QStorageIterator::next() |
602 | { |
603 | BVolume volume; |
604 | |
605 | if (m_volumeRoster.GetNextVolume(&volume) != B_OK) |
606 | return false; |
607 | |
608 | BDirectory directory; |
609 | if (volume.GetRootDirectory(&directory) != B_OK) |
610 | return false; |
611 | |
612 | const BPath path(&directory); |
613 | |
614 | fs_info fsInfo; |
615 | memset(&fsInfo, 0, sizeof(fsInfo)); |
616 | |
617 | if (fs_stat_dev(volume.Device(), &fsInfo) != 0) |
618 | return false; |
619 | |
620 | m_rootPath = path.Path(); |
621 | m_fileSystemType = QByteArray(fsInfo.fsh_name); |
622 | |
623 | const QByteArray deviceName(fsInfo.device_name); |
624 | m_device = (deviceName.isEmpty() ? QByteArray::number(qint32(volume.Device())) : deviceName); |
625 | |
626 | return true; |
627 | } |
628 | |
629 | inline QString QStorageIterator::rootPath() const |
630 | { |
631 | return QFile::decodeName(m_rootPath); |
632 | } |
633 | |
634 | inline QByteArray QStorageIterator::fileSystemType() const |
635 | { |
636 | return m_fileSystemType; |
637 | } |
638 | |
639 | inline QByteArray QStorageIterator::device() const |
640 | { |
641 | return m_device; |
642 | } |
643 | |
644 | inline QByteArray QStorageIterator::options() const |
645 | { |
646 | return QByteArray(); |
647 | } |
648 | |
649 | inline QByteArray QStorageIterator::subvolume() const |
650 | { |
651 | return QByteArray(); |
652 | } |
653 | #else |
654 | |
655 | inline QStorageIterator::QStorageIterator() |
656 | { |
657 | } |
658 | |
659 | inline QStorageIterator::~QStorageIterator() |
660 | { |
661 | } |
662 | |
663 | inline bool QStorageIterator::isValid() const |
664 | { |
665 | return false; |
666 | } |
667 | |
668 | inline bool QStorageIterator::next() |
669 | { |
670 | return false; |
671 | } |
672 | |
673 | inline QString QStorageIterator::rootPath() const |
674 | { |
675 | return QString(); |
676 | } |
677 | |
678 | inline QByteArray QStorageIterator::fileSystemType() const |
679 | { |
680 | return QByteArray(); |
681 | } |
682 | |
683 | inline QByteArray QStorageIterator::device() const |
684 | { |
685 | return QByteArray(); |
686 | } |
687 | |
688 | inline QByteArray QStorageIterator::options() const |
689 | { |
690 | return QByteArray(); |
691 | } |
692 | |
693 | inline QByteArray QStorageIterator::subvolume() const |
694 | { |
695 | return QByteArray(); |
696 | } |
697 | #endif |
698 | |
699 | void QStorageInfoPrivate::initRootPath() |
700 | { |
701 | rootPath = QFileInfo(rootPath).canonicalFilePath(); |
702 | |
703 | if (rootPath.isEmpty()) |
704 | return; |
705 | |
706 | QStorageIterator it; |
707 | if (!it.isValid()) { |
708 | rootPath = QStringLiteral("/" ); |
709 | return; |
710 | } |
711 | |
712 | int maxLength = 0; |
713 | const QString oldRootPath = rootPath; |
714 | rootPath.clear(); |
715 | |
716 | while (it.next()) { |
717 | const QString mountDir = it.rootPath(); |
718 | const QByteArray fsName = it.fileSystemType(); |
719 | // we try to find most suitable entry |
720 | if (isParentOf(parent: mountDir, dirName: oldRootPath) && maxLength < mountDir.size()) { |
721 | maxLength = mountDir.size(); |
722 | rootPath = mountDir; |
723 | device = it.device(); |
724 | fileSystemType = fsName; |
725 | subvolume = it.subvolume(); |
726 | } |
727 | } |
728 | } |
729 | |
730 | #ifdef Q_OS_LINUX |
731 | // udev encodes the labels with ID_LABEL_FS_ENC which is done with |
732 | // blkid_encode_string(). Within this function some 1-byte utf-8 |
733 | // characters not considered safe (e.g. '\' or ' ') are encoded as hex |
734 | static QString decodeFsEncString(const QString &str) |
735 | { |
736 | QString decoded; |
737 | decoded.reserve(asize: str.size()); |
738 | |
739 | int i = 0; |
740 | while (i < str.size()) { |
741 | if (i <= str.size() - 4) { // we need at least four characters \xAB |
742 | if (QStringView{str}.sliced(pos: i).startsWith(s: "\\x"_L1 )) { |
743 | bool bOk; |
744 | const int code = QStringView{str}.mid(pos: i+2, n: 2).toInt(ok: &bOk, base: 16); |
745 | if (bOk && code >= 0x20 && code < 0x80) { |
746 | decoded += QChar(code); |
747 | i += 4; |
748 | continue; |
749 | } |
750 | } |
751 | } |
752 | decoded += str.at(i); |
753 | ++i; |
754 | } |
755 | return decoded; |
756 | } |
757 | #endif |
758 | |
759 | static inline QString retrieveLabel(const QByteArray &device) |
760 | { |
761 | #ifdef Q_OS_LINUX |
762 | static const char pathDiskByLabel[] = "/dev/disk/by-label" ; |
763 | |
764 | QFileInfo devinfo(QFile::decodeName(localFileName: device)); |
765 | QString devicePath = devinfo.canonicalFilePath(); |
766 | |
767 | QDirIterator it(QLatin1StringView(pathDiskByLabel), QDir::NoDotAndDotDot); |
768 | while (it.hasNext()) { |
769 | QFileInfo fileInfo = it.nextFileInfo(); |
770 | if (fileInfo.isSymLink() && fileInfo.symLinkTarget() == devicePath) |
771 | return decodeFsEncString(str: fileInfo.fileName()); |
772 | } |
773 | #elif defined Q_OS_HAIKU |
774 | fs_info fsInfo; |
775 | memset(&fsInfo, 0, sizeof(fsInfo)); |
776 | |
777 | int32 pos = 0; |
778 | dev_t dev; |
779 | while ((dev = next_dev(&pos)) >= 0) { |
780 | if (fs_stat_dev(dev, &fsInfo) != 0) |
781 | continue; |
782 | |
783 | if (qstrcmp(fsInfo.device_name, device.constData()) == 0) |
784 | return QString::fromLocal8Bit(fsInfo.volume_name); |
785 | } |
786 | #else |
787 | Q_UNUSED(device); |
788 | #endif |
789 | |
790 | return QString(); |
791 | } |
792 | |
793 | void QStorageInfoPrivate::doStat() |
794 | { |
795 | initRootPath(); |
796 | if (rootPath.isEmpty()) |
797 | return; |
798 | |
799 | retrieveVolumeInfo(); |
800 | name = retrieveLabel(device); |
801 | } |
802 | |
803 | void QStorageInfoPrivate::retrieveVolumeInfo() |
804 | { |
805 | QT_STATFSBUF statfs_buf; |
806 | int result; |
807 | EINTR_LOOP(result, QT_STATFS(QFile::encodeName(rootPath).constData(), &statfs_buf)); |
808 | if (result == 0) { |
809 | valid = true; |
810 | ready = true; |
811 | |
812 | #if defined(Q_OS_INTEGRITY) || (defined(Q_OS_BSD4) && !defined(Q_OS_NETBSD)) || defined(Q_OS_RTEMS) |
813 | bytesTotal = statfs_buf.f_blocks * statfs_buf.f_bsize; |
814 | bytesFree = statfs_buf.f_bfree * statfs_buf.f_bsize; |
815 | bytesAvailable = statfs_buf.f_bavail * statfs_buf.f_bsize; |
816 | #else |
817 | bytesTotal = statfs_buf.f_blocks * statfs_buf.f_frsize; |
818 | bytesFree = statfs_buf.f_bfree * statfs_buf.f_frsize; |
819 | bytesAvailable = statfs_buf.f_bavail * statfs_buf.f_frsize; |
820 | #endif |
821 | blockSize = statfs_buf.f_bsize; |
822 | #if defined(Q_OS_ANDROID) || defined(Q_OS_BSD4) || defined(Q_OS_INTEGRITY) || defined(Q_OS_RTEMS) |
823 | #if defined(_STATFS_F_FLAGS) |
824 | readOnly = (statfs_buf.f_flags & ST_RDONLY) != 0; |
825 | #endif |
826 | #else |
827 | readOnly = (statfs_buf.f_flag & ST_RDONLY) != 0; |
828 | #endif |
829 | } |
830 | } |
831 | |
832 | QList<QStorageInfo> QStorageInfoPrivate::mountedVolumes() |
833 | { |
834 | QStorageIterator it; |
835 | if (!it.isValid()) |
836 | return QList<QStorageInfo>() << root(); |
837 | |
838 | QList<QStorageInfo> volumes; |
839 | |
840 | while (it.next()) { |
841 | if (!shouldIncludeFs(it)) |
842 | continue; |
843 | |
844 | const QString mountDir = it.rootPath(); |
845 | QStorageInfo info(mountDir); |
846 | info.d->device = it.device(); |
847 | info.d->fileSystemType = it.fileSystemType(); |
848 | info.d->subvolume = it.subvolume(); |
849 | if (info.bytesTotal() == 0 && info != root()) |
850 | continue; |
851 | volumes.append(t: info); |
852 | } |
853 | |
854 | return volumes; |
855 | } |
856 | |
857 | QStorageInfo QStorageInfoPrivate::root() |
858 | { |
859 | return QStorageInfo(QStringLiteral("/" )); |
860 | } |
861 | |
862 | QT_END_NAMESPACE |
863 | |