9#include <private/qcore_unix_p.h>
10#include <private/qlocale_tools_p.h>
11#include <private/qtools_p.h>
13#include <QtCore/qdirlisting.h>
14#include <QtCore/qsystemdetection.h>
20#include <sys/statfs.h>
24# define FSLABEL_MAX 256
26#ifndef FS_IOC_GETFSLABEL
32# define ST_RDONLY 0x0001
35#if defined(Q_OS_ANDROID)
39# undef STATX_BASIC_STATS
40#include <private/qjnihelpers_p.h>
45using namespace Qt::StringLiterals;
52 auto it = devno.cbegin();
53 auto r = qstrntoll(it, devno.size(), 10);
56 int rdevmajor =
int(r.result);
62 r = qstrntoll(++it, devno.size() - r.used + 1, 10);
66 return makedev(rdevmajor, r.result);
79 QByteArray ret(path.size(),
'\0');
80 char *dst = ret.data();
81 const char *src = path.data();
82 const char *srcEnd = path.data() + path.size();
83 while (src != srcEnd) {
91 char c = (*src++ -
'0') << 6;
92 c |= (*src++ -
'0') << 3;
105 ret.resize(dst - ret.data());
125static void tokenizeLine(std::array<QByteArrayView, FieldCount> &fields, QByteArrayView line)
127 size_t fieldIndex = 0;
129 const char *begin = line.data();
130 const qsizetype len = line.size();
131 qsizetype spaceIndex = -1;
132 while ((spaceIndex = line.indexOf(
' ', from)) != -1 && fieldIndex <
FieldCount) {
133 fields[fieldIndex] = QByteArrayView{begin + from, begin + spaceIndex};
138 static constexpr char separatorField[] =
" - ";
139 const qsizetype sepIndex = line.indexOf(separatorField, from);
140 if (sepIndex == -1) {
141 qCWarning(lcStorageInfo,
142 "Malformed line (missing '-' separator field) while parsing '%s':\n%s",
143 MountInfoPath, line.constData());
148 from = sepIndex + strlen(separatorField);
162 qCInfo(lcStorageInfo,
163 "Expected %d fields while parsing line from '%s', but found %zu instead:\n%.*s",
164 FieldCount, MountInfoPath, fieldIndex,
int(line.size()), line.data());
175 auto it = mountinfo.cbegin();
176 const auto end = mountinfo.cend();
177 auto nextLine = [&it, &end]() -> QByteArrayView {
178 auto nIt = std::find(it, end,
'\n');
180 QByteArrayView ba(it, nIt);
187 std::vector<MountInfo> infos;
188 std::array<QByteArrayView, FieldCount> fields;
191 auto checkField = [&line](QByteArrayView field) {
192 if (field.isEmpty()) {
193 qDebug(
"Failed to parse line from %s:\n%.*s",
MountInfoPath,
int(line.size()),
201 while (!(line = nextLine()).isEmpty()) {
203 tokenizeLine(fields, line);
206 if (
auto r = qstrntoll(fields[
MountId].data(), fields[
MountId].size(), 10); r.ok()) {
207 info.mntid = r.result;
213 QByteArray mountP = parseMangledPath(fields[MountPoint]);
214 if (!checkField(mountP))
216 info.mountPoint = QFile::decodeName(mountP);
218 if (!checkField(fields[
FsType]))
220 info.fsType = fields[
FsType].toByteArray();
222 if (filter == FilterMountInfo::Filtered
223 && !QStorageInfoPrivate::shouldIncludeFs(info.mountPoint, info.fsType))
226 std::optional<dev_t> devno = deviceNumber(fields[DevNo]);
233 QByteArrayView fsRootView = fields[FsRoot];
234 if (!checkField(fsRootView))
239 if (fsRootView !=
"/") {
240 info.fsRoot = parseMangledPath(fsRootView);
241 if (!checkField(info.fsRoot))
245 info.device = parseMangledPath(fields[MountSource]);
246 if (!checkField(info.device))
249 infos.push_back(std::move(info));
255struct AutoFileDescriptor
258 AutoFileDescriptor(
const QString &path,
int mode = QT_OPEN_RDONLY)
259 : fd(qt_safe_open(QFile::encodeName(path), mode))
261 ~AutoFileDescriptor() {
if (fd >= 0) qt_safe_close(fd); }
262 operator
int()
const noexcept {
return fd; }
271 using namespace QtMiscUtils;
272 qsizetype start = str.indexOf(u'\\');
274 return std::move(str);
277 QString decoded = std::move(str);
278 auto ptr =
reinterpret_cast<
char16_t *>(decoded.begin());
279 qsizetype in = start;
280 qsizetype out = start;
281 qsizetype size = decoded.size();
284 Q_ASSERT(ptr[in] == u'\\');
285 if (size - in >= 4 && ptr[in + 1] == u'x') {
286 int c = fromHex(ptr[in + 2]) << 4;
287 c |= fromHex(ptr[in + 3]);
288 if (Q_UNLIKELY(c < 0))
289 c = QChar::ReplacementCharacter;
294 for ( ; in < size; ++in) {
295 char16_t c = ptr[in];
308 if (QT_STAT(QFile::encodeName(device), &st) < 0)
317#if defined(STATX_BASIC_STATS
) && defined(STATX_MNT_ID
)
320 int r = statx(fd,
"", AT_EMPTY_PATH | AT_NO_AUTOMOUNT, STATX_MNT_ID, &st);
321 if (r == 0 && (st.stx_mask & STATX_MNT_ID))
322 return st.stx_mnt_id;
336 if (major(deviceId) != 0)
340 if (device.size() < 2 || !device.startsWith(
'/'))
344 if (QT_STAT(device, &st) < 0)
346 if (!S_ISBLK(st.st_mode))
353 static const char pathDiskByLabel[] =
"/dev/disk/by-label";
354 static constexpr auto LabelFileFilter = QDirListing::IteratorFlag::IncludeHidden;
355 return QDirListing(QLatin1StringView(pathDiskByLabel), LabelFileFilter);
366 for (
const auto &dirEntry : devicesByLabel()) {
367 quint64 deviceId = retrieveDeviceId(QFile::encodeName(dirEntry.filePath()));
370 result.emplaceBack(Entry{ decodeFsEncString(dirEntry.fileName()), deviceId });
387 return QString::fromUtf8(label);
392 if (
auto label = retrieveLabelViaIoctl(fd))
395 deviceId = retrieveDeviceId(d.device, deviceId);
399 for (
const auto &dirEntry : devicesByLabel()) {
400 if (retrieveDeviceId(QFile::encodeName(dirEntry.filePath())) == deviceId)
401 return decodeFsEncString(dirEntry.fileName());
406void QStorageInfoPrivate::retrieveVolumeInfo()
408 struct statfs64 statfs_buf;
410 QT_EINTR_LOOP(result, statfs64(QFile::encodeName(rootPath).constData(), &statfs_buf));
411 valid = ready = (result == 0);
413 bytesTotal = statfs_buf.f_blocks * statfs_buf.f_frsize;
414 bytesFree = statfs_buf.f_bfree * statfs_buf.f_frsize;
415 bytesAvailable = statfs_buf.f_bavail * statfs_buf.f_frsize;
416 blockSize =
int(statfs_buf.f_bsize);
417 readOnly = (statfs_buf.f_flags &
ST_RDONLY) != 0;
423 QFile file(u"/proc/self/mountinfo"_s);
424 if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
427 QByteArray mountinfo = file.readAll();
430 return doParseMountInfo(mountinfo, filter);
433void QStorageInfoPrivate::doStat()
436 if (QtAndroidPrivate::isUncompressedNativeLibs()) {
438 QString possibleApk = QtAndroidPrivate::resolveApkPath(rootPath);
439 if (!possibleApk.isEmpty())
440 rootPath = possibleApk;
444 retrieveVolumeInfo();
448 rootPath = QFileInfo(rootPath).canonicalFilePath();
449 if (rootPath.isEmpty())
452 std::vector<MountInfo> infos = parseMountInfo();
458 MountInfo *best =
nullptr;
459 AutoFileDescriptor fd(rootPath);
460 if (quint64 mntid = mountIdForPath(fd)) {
462 auto it = std::find_if(infos.begin(), infos.end(),
463 [mntid](
const MountInfo &info) {
return info.mntid == mntid; });
464 if (it != infos.end())
465 best = q20::to_address(it);
482 const QString oldRootPath = std::exchange(rootPath, QString());
483 const dev_t rootPathDevId = deviceIdForPath(oldRootPath);
484 for (
auto it = infos.rbegin(); it != infos.rend(); ++it) {
485 if (!isParentOf(it->mountPoint, oldRootPath))
487 if (rootPathDevId == it->stDev) {
489 best = q20::to_address(it);
495 best = q20::to_address(it);
500 auto stDev = best->stDev;
501 setFromMountInfo(std::move(*best));
502 name = retrieveLabel(*
this, fd, stDev);
506QList<QStorageInfo> QStorageInfoPrivate::mountedVolumes()
508 std::vector<MountInfo> infos = parseMountInfo(FilterMountInfo::Filtered);
510 return QList{root()};
512 std::optional<
decltype(retrieveLabels())> labelMap;
513 auto labelForDevice = [&labelMap](
const QStorageInfoPrivate &d,
int fd, quint64 devid) {
514 if (d.fileSystemType ==
"tmpfs")
517 if (
auto label = retrieveLabelViaIoctl(fd))
520 devid = retrieveDeviceId(d.device, devid);
525 labelMap = retrieveLabels();
526 for (
auto &[deviceLabel, deviceId] : std::as_const(*labelMap)) {
527 if (devid == deviceId)
533 QList<QStorageInfo> volumes;
534 volumes.reserve(infos.size());
535 for (
auto it = infos.begin(); it != infos.end(); ++it) {
536 MountInfo &info = *it;
537 AutoFileDescriptor fd(info.mountPoint);
540 quint64 mntid = mountIdForPath(fd);
544 auto isParent = [&info](
const MountInfo &maybeParent) {
545 return isParentOf(maybeParent.mountPoint, info.mountPoint);
547 if (std::find_if(it + 1, infos.end(), isParent) != infos.end())
549 }
else if (mntid != info.mntid) {
553 const auto infoStDev = info.stDev;
554 QStorageInfoPrivate d(std::move(info));
555 d.retrieveVolumeInfo();
556 if (d.bytesTotal <= 0 && d.rootPath != u'/')
558 d.name = labelForDevice(d, fd, infoStDev);
559 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)