10#include <private/qcore_unix_p.h>
11#include <private/qlocale_tools_p.h>
12#include <private/qtools_p.h>
14#include <QtCore/qdirlisting.h>
15#include <QtCore/qsystemdetection.h>
21#include <sys/statfs.h>
25# define FSLABEL_MAX 256
27#ifndef FS_IOC_GETFSLABEL
33# define ST_RDONLY 0x0001
36#if defined(Q_OS_ANDROID)
40# undef STATX_BASIC_STATS
41#include <private/qjnihelpers_p.h>
46using namespace Qt::StringLiterals;
53 auto it = devno.cbegin();
54 auto r = qstrntoll(it, devno.size(), 10);
57 int rdevmajor =
int(r.result);
63 r = qstrntoll(++it, devno.size() - r.used + 1, 10);
67 return makedev(rdevmajor, r.result);
80 QByteArray ret(path.size(),
'\0');
81 char *dst = ret.data();
82 const char *src = path.data();
83 const char *srcEnd = path.data() + path.size();
84 while (src != srcEnd) {
92 char c = (*src++ -
'0') << 6;
93 c |= (*src++ -
'0') << 3;
106 ret.resize(dst - ret.data());
126static void tokenizeLine(std::array<QByteArrayView, FieldCount> &fields, QByteArrayView line)
128 size_t fieldIndex = 0;
130 const char *begin = line.data();
131 const qsizetype len = line.size();
132 qsizetype spaceIndex = -1;
133 while ((spaceIndex = line.indexOf(
' ', from)) != -1 && fieldIndex <
FieldCount) {
134 fields[fieldIndex] = QByteArrayView{begin + from, begin + spaceIndex};
139 static constexpr char separatorField[] =
" - ";
140 const qsizetype sepIndex = line.indexOf(separatorField, from);
141 if (sepIndex == -1) {
142 qCWarning(lcStorageInfo,
143 "Malformed line (missing '-' separator field) while parsing '%s':\n%s",
144 MountInfoPath, line.constData());
149 from = sepIndex + strlen(separatorField);
163 qCInfo(lcStorageInfo,
164 "Expected %d fields while parsing line from '%s', but found %zu instead:\n%.*s",
165 FieldCount, MountInfoPath, fieldIndex,
int(line.size()), line.data());
176 auto it = mountinfo.cbegin();
177 const auto end = mountinfo.cend();
178 auto nextLine = [&it, &end]() -> QByteArrayView {
179 auto nIt = std::find(it, end,
'\n');
181 QByteArrayView ba(it, nIt);
188 std::vector<MountInfo> infos;
189 std::array<QByteArrayView, FieldCount> fields;
192 auto checkField = [&line](QByteArrayView field) {
193 if (field.isEmpty()) {
194 qDebug(
"Failed to parse line from %s:\n%.*s",
MountInfoPath,
int(line.size()),
202 while (!(line = nextLine()).isEmpty()) {
204 tokenizeLine(fields, line);
207 if (
auto r = qstrntoll(fields[
MountId].data(), fields[
MountId].size(), 10); r.ok()) {
208 info.mntid = r.result;
214 QByteArray mountP = parseMangledPath(fields[MountPoint]);
215 if (!checkField(mountP))
217 info.mountPoint = QFile::decodeName(mountP);
219 if (!checkField(fields[
FsType]))
221 info.fsType = fields[
FsType].toByteArray();
223 if (filter == FilterMountInfo::Filtered
224 && !QStorageInfoPrivate::shouldIncludeFs(info.mountPoint, info.fsType))
227 std::optional<dev_t> devno = deviceNumber(fields[DevNo]);
234 QByteArrayView fsRootView = fields[
FsRoot];
235 if (!checkField(fsRootView))
240 if (fsRootView !=
"/") {
241 info.fsRoot = parseMangledPath(fsRootView);
242 if (!checkField(info.fsRoot))
246 info.device = parseMangledPath(fields[MountSource]);
247 if (!checkField(info.device))
250 infos.push_back(std::move(info));
256struct AutoFileDescriptor
259 AutoFileDescriptor(
const QString &path,
int mode = QT_OPEN_RDONLY)
260 : fd(qt_safe_open(QFile::encodeName(path), mode))
262 ~AutoFileDescriptor() {
if (fd >= 0) qt_safe_close(fd); }
263 operator
int()
const noexcept {
return fd; }
272 using namespace QtMiscUtils;
273 qsizetype start = str.indexOf(u'\\');
275 return std::move(str);
278 QString decoded =
std::move(str);
279 auto ptr =
reinterpret_cast<
char16_t *>(decoded.begin());
280 qsizetype in = start;
281 qsizetype out = start;
282 qsizetype size = decoded.size();
285 Q_ASSERT(ptr[in] == u'\\');
286 if (size - in >= 4 && ptr[in + 1] == u'x') {
287 int c = fromHex(ptr[in + 2]) << 4;
288 c |= fromHex(ptr[in + 3]);
289 if (Q_UNLIKELY(c < 0))
290 c = QChar::ReplacementCharacter;
295 for ( ; in < size; ++in) {
296 char16_t c = ptr[in];
309 if (QT_STAT(QFile::encodeName(device), &st) < 0)
318#if defined(STATX_BASIC_STATS
) && defined(STATX_MNT_ID
)
321 int r = statx(fd,
"", AT_EMPTY_PATH | AT_NO_AUTOMOUNT, STATX_MNT_ID, &st);
322 if (r == 0 && (st.stx_mask & STATX_MNT_ID))
323 return st.stx_mnt_id;
337 if (major(deviceId) != 0)
341 if (device.size() < 2 || !device.startsWith(
'/'))
345 if (QT_STAT(device, &st) < 0)
347 if (!S_ISBLK(st.st_mode))
354 static const char pathDiskByLabel[] =
"/dev/disk/by-label";
355 static constexpr auto LabelFileFilter = QDirListing::IteratorFlag::IncludeHidden;
356 return QDirListing(QLatin1StringView(pathDiskByLabel), LabelFileFilter);
367 for (
const auto &dirEntry : devicesByLabel()) {
368 quint64 deviceId = retrieveDeviceId(QFile::encodeName(dirEntry.filePath()));
371 result.emplaceBack(Entry{ decodeFsEncString(dirEntry.fileName()), deviceId });
388 return QString::fromUtf8(label);
393 if (
auto label = retrieveLabelViaIoctl(fd))
396 deviceId = retrieveDeviceId(d.device, deviceId);
400 for (
const auto &dirEntry : devicesByLabel()) {
401 if (retrieveDeviceId(QFile::encodeName(dirEntry.filePath())) == deviceId)
402 return decodeFsEncString(dirEntry.fileName());
407void QStorageInfoPrivate::retrieveVolumeInfo()
409 struct statfs64 statfs_buf;
411 QT_EINTR_LOOP(result, statfs64(QFile::encodeName(rootPath).constData(), &statfs_buf));
412 valid = ready = (result == 0);
414 bytesTotal = statfs_buf.f_blocks * statfs_buf.f_frsize;
415 bytesFree = statfs_buf.f_bfree * statfs_buf.f_frsize;
416 bytesAvailable = statfs_buf.f_bavail * statfs_buf.f_frsize;
417 blockSize =
int(statfs_buf.f_bsize);
418 readOnly = (statfs_buf.f_flags &
ST_RDONLY) != 0;
424 QFile file(u"/proc/self/mountinfo"_s);
425 if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
428 QByteArray mountinfo = file.readAll();
431 return doParseMountInfo(mountinfo, filter);
434void QStorageInfoPrivate::doStat()
437 if (QtAndroidPrivate::isUncompressedNativeLibs()) {
439 QString possibleApk = QtAndroidPrivate::resolveApkPath(rootPath);
440 if (!possibleApk.isEmpty())
441 rootPath = possibleApk;
445 retrieveVolumeInfo();
449 rootPath = QFileInfo(rootPath).canonicalFilePath();
450 if (rootPath.isEmpty())
453 std::vector<MountInfo> infos = parseMountInfo();
459 MountInfo *best =
nullptr;
460 AutoFileDescriptor fd(rootPath);
461 if (quint64 mntid = mountIdForPath(fd)) {
463 auto it = std::find_if(infos.begin(), infos.end(),
464 [mntid](
const MountInfo &info) {
return info.mntid == mntid; });
465 if (it != infos.end())
466 best = q20::to_address(it);
483 const QString oldRootPath = std::exchange(rootPath, QString());
484 const dev_t rootPathDevId = deviceIdForPath(oldRootPath);
485 for (
auto it = infos.rbegin(); it != infos.rend(); ++it) {
486 if (!isParentOf(it->mountPoint, oldRootPath))
488 if (rootPathDevId == it->stDev) {
490 best = q20::to_address(it);
496 best = q20::to_address(it);
501 auto stDev = best->stDev;
502 setFromMountInfo(std::move(*best));
503 name = retrieveLabel(*
this, fd, stDev);
507QList<QStorageInfo> QStorageInfoPrivate::mountedVolumes()
509 std::vector<MountInfo> infos = parseMountInfo(FilterMountInfo::Filtered);
511 return QList{root()};
513 std::optional<
decltype(retrieveLabels())> labelMap;
514 auto labelForDevice = [&labelMap](
const QStorageInfoPrivate &d,
int fd, quint64 devid) {
515 if (d.fileSystemType ==
"tmpfs")
518 if (
auto label = retrieveLabelViaIoctl(fd))
521 devid = retrieveDeviceId(d.device, devid);
526 labelMap = retrieveLabels();
527 for (
auto &[deviceLabel, deviceId] : std::as_const(*labelMap)) {
528 if (devid == deviceId)
534 QList<QStorageInfo> volumes;
535 volumes.reserve(infos.size());
536 for (
auto it = infos.begin(); it != infos.end(); ++it) {
537 MountInfo &info = *it;
538 AutoFileDescriptor fd(info.mountPoint);
541 quint64 mntid = mountIdForPath(fd);
545 auto isParent = [&info](
const MountInfo &maybeParent) {
546 return isParentOf(maybeParent.mountPoint, info.mountPoint);
548 if (std::find_if(it + 1, infos.end(), isParent) != infos.end())
550 }
else if (mntid != info.mntid) {
554 const auto infoStDev = info.stDev;
555 QStorageInfoPrivate d(std::move(info));
556 d.retrieveVolumeInfo();
557 if (d.bytesTotal <= 0 && d.rootPath != u'/')
559 d.name = labelForDevice(d, fd, infoStDev);
560 volumes.emplace_back(QStorageInfo(*
new QStorageInfoPrivate(std::move(d))));
static QString retrieveLabel(const QStorageInfoPrivate &d, int fd, quint64 deviceId)
static constexpr short MountId
static quint64 retrieveDeviceId(const QByteArray &device, quint64 deviceId=0)
static const char MountInfoPath[]
static constexpr short MountPoint
static constexpr short MountSource
static constexpr short FieldCount
static quint64 mountIdForPath(int fd)
static constexpr short MountOptions
std::vector< MountInfo > doParseMountInfo(const QByteArray &mountinfo, FilterMountInfo filter)
static constexpr short FsType
static std::optional< dev_t > deviceNumber(QByteArrayView devno)
static std::optional< QString > retrieveLabelViaIoctl(int fd)
#define FS_IOC_GETFSLABEL
static constexpr short DevNo
static QByteArray parseMangledPath(QByteArrayView path)
static constexpr short SuperOptions
static QDirListing devicesByLabel()
static constexpr short FsRoot
static QString decodeFsEncString(QString &&str)
static std::vector< MountInfo > parseMountInfo(FilterMountInfo filter=FilterMountInfo::All)
static auto retrieveLabels()
static void tokenizeLine(std::array< QByteArrayView, FieldCount > &fields, QByteArrayView line)
static dev_t deviceIdForPath(const QString &device)