summaryrefslogtreecommitdiffstats
path: root/src/detection/disk
diff options
context:
space:
mode:
Diffstat (limited to 'src/detection/disk')
-rw-r--r--src/detection/disk/disk.c55
-rw-r--r--src/detection/disk/disk.h40
-rw-r--r--src/detection/disk/disk_bsd.c214
-rw-r--r--src/detection/disk/disk_haiku.cpp74
-rw-r--r--src/detection/disk/disk_linux.c337
-rw-r--r--src/detection/disk/disk_nosupport.c5
-rw-r--r--src/detection/disk/disk_sunos.c145
-rw-r--r--src/detection/disk/disk_windows.c128
8 files changed, 998 insertions, 0 deletions
diff --git a/src/detection/disk/disk.c b/src/detection/disk/disk.c
new file mode 100644
index 0000000..c86282d
--- /dev/null
+++ b/src/detection/disk/disk.c
@@ -0,0 +1,55 @@
+#include "disk.h"
+
+static int compareDisks(const FFDisk* disk1, const FFDisk* disk2) {
+ return ffStrbufComp(&disk1->mountpoint, &disk2->mountpoint);
+}
+
+const char* ffDetectDisks(FFDiskOptions* options, FFlist* disks) {
+ const char* error = ffDetectDisksImpl(options, disks);
+
+ if (error) {
+ return error;
+ }
+ if (disks->length == 0) {
+ return NULL;
+ }
+
+ // We need to sort the disks, so that we can detect, which disk a path resides on
+ // For example for /boot/efi/bootmgr we need to check /boot/efi before /boot
+ // Note that we sort alphabetically here for a better ordering when printing the list,
+ // so the check must be done in reverse order
+ ffListSort(disks, sizeof(FFDisk), (void*) compareDisks);
+ FF_LIST_FOR_EACH (FFDisk, disk, *disks) {
+ if (disk->bytesTotal == 0) {
+ disk->type |= FF_DISK_VOLUME_TYPE_UNKNOWN_BIT;
+ } else {
+ disk->bytesUsed = disk->bytesTotal - (options->calcType == FF_DISK_CALC_TYPE_FREE ? disk->bytesFree : disk->bytesAvailable);
+ }
+ }
+
+ return NULL;
+}
+
+#ifndef _WIN32
+ #include <fnmatch.h>
+
+bool ffDiskMatchesFolderPatterns(FFstrbuf* folders, const char* path, char separator) {
+ uint32_t startIndex = 0;
+ while (startIndex < folders->length) {
+ uint32_t sepIndex = ffStrbufNextIndexC(folders, startIndex, separator);
+
+ char savedSep = folders->chars[sepIndex]; // Can be '\0' if at end
+ folders->chars[sepIndex] = '\0';
+
+ bool matched = fnmatch(&folders->chars[startIndex], path, 0) == 0;
+ folders->chars[sepIndex] = savedSep;
+
+ if (matched) {
+ return true;
+ }
+
+ startIndex = sepIndex + 1;
+ }
+ return false;
+}
+#endif
diff --git a/src/detection/disk/disk.h b/src/detection/disk/disk.h
new file mode 100644
index 0000000..57aaf2f
--- /dev/null
+++ b/src/detection/disk/disk.h
@@ -0,0 +1,40 @@
+#pragma once
+
+#include "fastfetch.h"
+#include "modules/disk/option.h"
+
+#ifdef _WIN32
+ #define FF_DISK_FOLDER_SEPARATOR ';'
+#else
+ #define FF_DISK_FOLDER_SEPARATOR ':'
+#endif
+
+typedef struct FFDisk {
+ FFstrbuf mountFrom;
+ FFstrbuf mountpoint;
+ FFstrbuf filesystem;
+ FFstrbuf name;
+ FFDiskVolumeType type;
+
+ uint64_t bytesUsed;
+ uint64_t bytesFree;
+ uint64_t bytesAvailable;
+ uint64_t bytesTotal;
+
+ uint32_t filesUsed;
+ uint32_t filesTotal;
+
+ uint64_t createTime;
+} FFDisk;
+
+/**
+ * Returns a List of FFDisk, sorted alphabetically by mountpoint.
+ * If error is not set, disks contains at least one disk.
+ */
+const char* ffDetectDisks(FFDiskOptions* options, FFlist* disks /* list of FFDisk */);
+
+const char* ffDetectDisksImpl(FFDiskOptions* options, FFlist* disks);
+
+#ifndef _WIN32
+bool ffDiskMatchesFolderPatterns(FFstrbuf* folders, const char* path, char separator);
+#endif
diff --git a/src/detection/disk/disk_bsd.c b/src/detection/disk/disk_bsd.c
new file mode 100644
index 0000000..9156d79
--- /dev/null
+++ b/src/detection/disk/disk_bsd.c
@@ -0,0 +1,214 @@
+#include "disk.h"
+#include "common/mallocHelper.h"
+#include "common/strutil.h"
+
+#include <stdalign.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+
+#ifdef __NetBSD__
+ #include <sys/types.h>
+ #include <sys/statvfs.h>
+ #define statfs statvfs
+ #define f_flags f_flag
+ #define f_bsize f_frsize
+#endif
+
+#ifdef __FreeBSD__
+ #if __has_include(<libgeom.h>)
+ #include <libgeom.h>
+
+static const char* detectFsLabel(struct statfs* fs, FFDisk* disk) {
+ if (!ffStrStartsWith(fs->f_mntfromname, "/dev/")) {
+ return "Only block devices are supported";
+ }
+
+ // Detect volume label in geom tree
+ static struct gmesh geomTree;
+ static struct gclass* cLabels;
+ if (!cLabels) {
+ if (geomTree.lg_ident) {
+ return "Previous geom_gettree() failed";
+ }
+
+ if (geom_gettree(&geomTree) < 0) {
+ geomTree.lg_ident = (void*) (intptr_t) -1;
+ return "geom_gettree() failed";
+ }
+
+ for (cLabels = geomTree.lg_class.lh_first; cLabels && !ffStrEquals(cLabels->lg_name, "LABEL"); cLabels = cLabels->lg_class.le_next);
+ if (!cLabels) {
+ return "Class LABEL is not found";
+ }
+ }
+
+ for (struct ggeom* label = cLabels->lg_geom.lh_first; label; label = label->lg_geom.le_next) {
+ struct gprovider* provider = label->lg_provider.lh_first;
+ if (!provider || !ffStrEquals(label->lg_name, fs->f_mntfromname + strlen("/dev/"))) {
+ continue;
+ }
+ const char* str = strchr(provider->lg_name, '/');
+ ffStrbufSetS(&disk->name, str ? str + 1 : provider->lg_name);
+ }
+
+ return NULL;
+}
+ #else
+static const char* detectFsLabel(FF_A_UNUSED struct statfs* fs, FF_A_UNUSED FFDisk* disk) {
+ return "Fastfetch was compiled without libgeom support";
+}
+ #endif
+
+static void detectFsInfo(struct statfs* fs, FFDisk* disk) {
+ if (ffStrbufEqualS(&disk->filesystem, "zfs")) {
+ disk->type = !ffStrbufStartsWithS(&disk->mountFrom, "zroot/") || ffStrbufStartsWithS(&disk->mountFrom, "zroot/ROOT/")
+ ? FF_DISK_VOLUME_TYPE_REGULAR_BIT
+ : FF_DISK_VOLUME_TYPE_SUBVOLUME_BIT;
+ } else if (fs->f_flags & MNT_IGNORE) {
+ disk->type = FF_DISK_VOLUME_TYPE_HIDDEN_BIT;
+ } else if (!(fs->f_flags & MNT_LOCAL)) {
+ disk->type = FF_DISK_VOLUME_TYPE_EXTERNAL_BIT;
+ } else {
+ disk->type = FF_DISK_VOLUME_TYPE_REGULAR_BIT;
+ }
+
+ detectFsLabel(fs, disk);
+}
+#elif __APPLE__
+ #include "common/apple/cf_helpers.h"
+
+ #include <sys/attr.h>
+ #include <unistd.h>
+
+ #ifndef MAC_OS_X_VERSION_10_15
+ #define MNT_REMOVABLE 0x00000200
+ #endif
+
+struct CmnAttrBuf {
+ uint32_t length;
+ attrreference_t nameRef;
+ char nameSpace[NAME_MAX * 3 + 1];
+} FF_A_PACKED;
+
+void detectFsInfo(struct statfs* fs, FFDisk* disk) {
+ if (fs->f_flags & MNT_DONTBROWSE) {
+ disk->type = FF_DISK_VOLUME_TYPE_HIDDEN_BIT;
+ } else if (fs->f_flags & MNT_REMOVABLE || !(fs->f_flags & MNT_LOCAL)) {
+ disk->type = FF_DISK_VOLUME_TYPE_EXTERNAL_BIT;
+ } else {
+ disk->type = FF_DISK_VOLUME_TYPE_REGULAR_BIT;
+ }
+
+ alignas(4) struct CmnAttrBuf attrBuf;
+ if (getattrlist(disk->mountpoint.chars, &(struct attrlist) {
+ .bitmapcount = ATTR_BIT_MAP_COUNT,
+ .commonattr = ATTR_CMN_NAME,
+ },
+ &attrBuf,
+ sizeof(attrBuf),
+ 0) == 0) {
+ ffStrbufInitNS(&disk->name, attrBuf.nameRef.attr_length - 1 /* excluding '\0' */, attrBuf.nameSpace);
+ }
+}
+#else
+static void detectFsInfo(struct statfs* fs, FFDisk* disk) {
+ #ifdef MNT_IGNORE
+ if (fs->f_flags & MNT_IGNORE) {
+ disk->type = FF_DISK_VOLUME_TYPE_HIDDEN_BIT;
+ } else
+ #endif
+ if (!(fs->f_flags & MNT_LOCAL)) {
+ disk->type = FF_DISK_VOLUME_TYPE_EXTERNAL_BIT;
+ } else {
+ disk->type = FF_DISK_VOLUME_TYPE_REGULAR_BIT;
+ }
+}
+#endif
+
+const char* ffDetectDisksImpl(FFDiskOptions* options, FFlist* disks) {
+#ifndef __NetBSD__
+ int size = getfsstat(NULL, 0, MNT_WAIT);
+ if (size <= 0) {
+ return "getfsstat(NULL, 0, MNT_WAIT) failed";
+ }
+#else
+ int size = getvfsstat(NULL, 0, ST_WAIT);
+ if (size <= 0) {
+ return "getvfsstat(NULL, 0, ST_WAIT) failed";
+ }
+#endif
+
+ FF_AUTO_FREE struct statfs* buf = malloc(sizeof(*buf) * (unsigned) size);
+#ifndef __NetBSD__
+ if (getfsstat(buf, (int) (sizeof(*buf) * (unsigned) size), MNT_NOWAIT) <= 0) {
+ return "getfsstat(buf, size, MNT_NOWAIT) failed";
+ }
+#else
+ if (getvfsstat(buf, sizeof(*buf) * (unsigned) size, ST_NOWAIT) <= 0) {
+ return "getvfsstat(buf, size, ST_NOWAIT) failed";
+ }
+#endif
+
+ for (struct statfs* fs = buf; fs < buf + size; ++fs) {
+ if (__builtin_expect(options->folders.length > 0, 0)) {
+ if (!ffStrbufSeparatedContainS(&options->folders, fs->f_mntonname, FF_DISK_FOLDER_SEPARATOR)) {
+ continue;
+ }
+ } else if (!ffStrEquals(fs->f_mntonname, "/") && !ffStrStartsWith(fs->f_mntfromname, "/dev/") && !ffStrEquals(fs->f_fstypename, "zfs") && !ffStrEquals(fs->f_fstypename, "fusefs.sshfs")) {
+ continue;
+ }
+
+ if (options->hideFolders.length && ffDiskMatchesFolderPatterns(&options->hideFolders, fs->f_mntonname, FF_DISK_FOLDER_SEPARATOR)) {
+ continue;
+ }
+
+ if (options->hideFS.length && ffStrbufSeparatedContainS(&options->hideFS, fs->f_fstypename, ':')) {
+ continue;
+ }
+
+#ifdef __FreeBSD__
+ // f_bavail and f_ffree are signed on FreeBSD...
+ if (fs->f_bavail < 0) {
+ fs->f_bavail = 0;
+ }
+ if (fs->f_ffree < 0) {
+ fs->f_ffree = 0;
+ }
+#endif
+
+ FFDisk* disk = FF_LIST_ADD(FFDisk, *disks);
+
+ disk->bytesTotal = (uint64_t) fs->f_blocks * (uint64_t) fs->f_bsize;
+ disk->bytesFree = (uint64_t) fs->f_bfree * (uint64_t) fs->f_bsize;
+ disk->bytesAvailable = (uint64_t) fs->f_bavail * (uint64_t) fs->f_bsize;
+ disk->bytesUsed = 0; // To be filled in ./disk.c
+
+ disk->filesTotal = (uint32_t) fs->f_files;
+ disk->filesUsed = (uint32_t) fs->f_files - (uint32_t) fs->f_ffree;
+
+ ffStrbufInitS(&disk->mountFrom, fs->f_mntfromname);
+ ffStrbufInitS(&disk->mountpoint, fs->f_mntonname);
+ ffStrbufInitS(&disk->filesystem, fs->f_fstypename);
+ ffStrbufInit(&disk->name);
+ disk->type = 0;
+ disk->createTime = 0;
+
+ detectFsInfo(fs, disk);
+
+ if (fs->f_flags & MNT_RDONLY) {
+ disk->type |= FF_DISK_VOLUME_TYPE_READONLY_BIT;
+ }
+
+#ifdef __OpenBSD__
+ #define st_birthtimespec __st_birthtim
+#endif
+#ifndef __DragonFly__
+ struct stat st;
+ if (stat(fs->f_mntonname, &st) == 0 && st.st_birthtimespec.tv_sec > 0) {
+ disk->createTime = (uint64_t) (((uint64_t) st.st_birthtimespec.tv_sec * 1000) + ((uint64_t) st.st_birthtimespec.tv_nsec / 1000000));
+ }
+#endif
+ }
+
+ return NULL;
+}
diff --git a/src/detection/disk/disk_haiku.cpp b/src/detection/disk/disk_haiku.cpp
new file mode 100644
index 0000000..0987897
--- /dev/null
+++ b/src/detection/disk/disk_haiku.cpp
@@ -0,0 +1,74 @@
+extern "C" {
+#include "disk.h"
+#include "common/strutil.h"
+}
+#include <fs_info.h>
+#include <Directory.h>
+#include <Path.h>
+
+const char* ffDetectDisksImpl(FFDiskOptions* options, FFlist* disks) {
+ int32 pos = 0;
+
+ for (dev_t dev; (dev = next_dev(&pos)) >= B_OK;) {
+ fs_info fs;
+ if (fs_stat_dev(dev, &fs) < 0) {
+ continue;
+ }
+
+ node_ref node(fs.dev, fs.root);
+ BDirectory dir(&node);
+ BPath path(&dir);
+ if (path.InitCheck() != B_OK) {
+ continue;
+ }
+
+ if (__builtin_expect(options->folders.length, 0)) {
+ if (!ffStrbufSeparatedContainS(&options->folders, path.Path(), FF_DISK_FOLDER_SEPARATOR)) {
+ continue;
+ }
+ }
+
+ if (options->hideFolders.length && ffDiskMatchesFolderPatterns(&options->hideFolders, path.Path(), FF_DISK_FOLDER_SEPARATOR)) {
+ continue;
+ }
+
+ if (options->hideFS.length && ffStrbufSeparatedContainS(&options->hideFS, fs.fsh_name, ':')) {
+ continue;
+ }
+
+ FFDisk* disk = FF_LIST_ADD(FFDisk, *disks);
+
+ disk->bytesTotal = (uint64_t) fs.total_blocks * (uint64_t) fs.block_size;
+ disk->bytesFree = (uint64_t) fs.free_blocks * (uint64_t) fs.block_size;
+ disk->bytesAvailable = disk->bytesFree;
+ disk->bytesUsed = 0; // To be filled in ./disk.c
+
+ disk->filesTotal = (uint32_t) fs.total_nodes;
+ disk->filesUsed = (uint32_t) (fs.total_nodes - fs.free_nodes);
+
+ ffStrbufInitS(&disk->mountFrom, fs.device_name);
+ ffStrbufInitS(&disk->mountpoint, path.Path());
+ ffStrbufInitS(&disk->filesystem, fs.fsh_name);
+ ffStrbufInitS(&disk->name, fs.volume_name);
+ disk->type = FF_DISK_VOLUME_TYPE_NONE;
+ if (!(fs.flags & B_FS_IS_PERSISTENT)) {
+ disk->type = (FFDiskVolumeType) (disk->type | FF_DISK_VOLUME_TYPE_HIDDEN_BIT);
+ }
+ if (fs.flags & B_FS_IS_READONLY) {
+ disk->type = (FFDiskVolumeType) (disk->type | FF_DISK_VOLUME_TYPE_READONLY_BIT);
+ }
+ if (fs.flags & B_FS_IS_REMOVABLE) {
+ disk->type = (FFDiskVolumeType) (disk->type | FF_DISK_VOLUME_TYPE_EXTERNAL_BIT);
+ }
+ if (disk->type == FF_DISK_VOLUME_TYPE_NONE) {
+ disk->type = FF_DISK_VOLUME_TYPE_REGULAR_BIT;
+ }
+ disk->createTime = 0;
+
+ time_t crTime;
+ if (dir.GetCreationTime(&crTime) == B_OK) {
+ disk->createTime = (uint64_t) crTime * 1000;
+ }
+ }
+ return NULL;
+}
diff --git a/src/detection/disk/disk_linux.c b/src/detection/disk/disk_linux.c
new file mode 100644
index 0000000..7205317
--- /dev/null
+++ b/src/detection/disk/disk_linux.c
@@ -0,0 +1,337 @@
+#include "disk.h"
+
+#include "common/io.h"
+#include "common/strutil.h"
+
+#include <limits.h>
+#include <ctype.h>
+#include <dirent.h>
+#include <mntent.h>
+#include <sys/stat.h>
+#include <sys/statvfs.h>
+
+#if defined(STATX_BTIME) && !defined(__ANDROID__)
+ #include <sys/syscall.h>
+#endif
+
+#ifdef __USE_LARGEFILE64
+ #define stat stat64
+ #define statvfs statvfs64
+ #define dirent dirent64
+ #define readdir readdir64
+#endif
+
+static bool isPhysicalDevice(const struct mntent* device) {
+#ifndef __ANDROID__ // On Android, `/dev` is not accessible, so that the following checks always fail
+
+ // Always show the root path
+ if (ffStrEquals(device->mnt_dir, "/")) {
+ return true;
+ }
+
+ if (ffStrEquals(device->mnt_fsname, "none")) {
+ return false;
+ }
+
+ // DrvFs is a filesystem plugin to WSL that was designed to support interop between WSL and the Windows filesystem.
+ if (ffStrEquals(device->mnt_type, "9p")) {
+ return ffStrContains(device->mnt_opts, "aname=drvfs");
+ }
+
+ // ZFS pool
+ if (ffStrEquals(device->mnt_type, "zfs")) {
+ return true;
+ }
+
+ // sshfs
+ if (ffStrEquals(device->mnt_type, "fuse.sshfs")) {
+ return true;
+ }
+
+ // Pseudo filesystems don't have a device in /dev
+ if (!ffStrStartsWith(device->mnt_fsname, "/dev/")) {
+ return false;
+ }
+
+ // #731
+ if (ffStrEquals(device->mnt_type, "bcachefs")) {
+ return true;
+ }
+
+ if (
+ ffStrStartsWith(device->mnt_fsname + 5, "loop") || // Ignore loop devices
+ ffStrStartsWith(device->mnt_fsname + 5, "ram") || // Ignore ram devices
+ ffStrStartsWith(device->mnt_fsname + 5, "fd") // Ignore fd devices
+ ) {
+ return false;
+ }
+
+ if (ffStrStartsWith(device->mnt_dir, "/bedrock/")) { // Ignore Bedrock Linux subvolumes
+ return false;
+ }
+
+ struct stat deviceStat;
+ if (stat(device->mnt_fsname, &deviceStat) != 0) {
+ return false;
+ }
+
+ // Ignore all devices that are not block devices
+ if (!S_ISBLK(deviceStat.st_mode)) {
+ return false;
+ }
+
+#else
+
+ // Pseudo filesystems don't have a device in /dev
+ if (!ffStrStartsWith(device->mnt_fsname, "/dev/")) {
+ return false;
+ }
+
+ if (
+ ffStrStartsWith(device->mnt_fsname + 5, "loop") || // Ignore loop devices
+ ffStrStartsWith(device->mnt_fsname + 5, "ram") || // Ignore ram devices
+ ffStrStartsWith(device->mnt_fsname + 5, "fd") // Ignore fd devices
+ ) {
+ return false;
+ }
+
+ // https://source.android.com/docs/core/ota/apex?hl=zh-cn
+ if (ffStrStartsWith(device->mnt_dir, "/apex/")) {
+ return false;
+ }
+
+#endif // __ANDROID__
+
+ return true;
+}
+
+static void detectNameFromPath(FFDisk* disk, const struct stat* deviceStat, FFstrbuf* basePath) {
+ FF_AUTO_CLOSE_DIR DIR* dir = opendir(basePath->chars);
+ if (dir == NULL) {
+ return;
+ }
+
+ uint32_t basePathLength = basePath->length;
+
+ struct dirent* entry;
+ while ((entry = readdir(dir)) != NULL) {
+ if (entry->d_name[0] == '.') {
+ continue;
+ }
+
+ ffStrbufAppendS(basePath, entry->d_name);
+
+ struct stat entryStat;
+ bool ret = stat(basePath->chars, &entryStat) == 0;
+
+ ffStrbufSubstrBefore(basePath, basePathLength);
+
+ if (!ret || deviceStat->st_ino != entryStat.st_ino) {
+ continue;
+ }
+
+ ffStrbufAppendS(&disk->name, entry->d_name);
+ break;
+ }
+}
+
+static void detectName(FFDisk* disk) {
+ struct stat deviceStat;
+ if (stat(disk->mountFrom.chars, &deviceStat) != 0) {
+ return;
+ }
+
+ FF_STRBUF_AUTO_DESTROY basePath = ffStrbufCreate();
+
+ // Try label first
+ ffStrbufSetS(&basePath, "/dev/disk/by-label/");
+ detectNameFromPath(disk, &deviceStat, &basePath);
+
+ if (disk->name.length == 0) {
+ // Try partlabel second
+ ffStrbufSetS(&basePath, "/dev/disk/by-partlabel/");
+ detectNameFromPath(disk, &deviceStat, &basePath);
+ }
+
+ if (disk->name.length == 0) {
+ return;
+ }
+
+ ffStrbufDecodeHexEscapeSequences(&disk->name);
+}
+
+#ifdef __ANDROID__
+
+static void detectType(FF_A_UNUSED const FFlist* disks, FFDisk* currentDisk, FF_A_UNUSED struct mntent* device) {
+ if (ffStrbufEqualS(&currentDisk->mountpoint, "/") || ffStrbufEqualS(&currentDisk->mountpoint, "/storage/emulated")) {
+ currentDisk->type = FF_DISK_VOLUME_TYPE_REGULAR_BIT;
+ } else if (ffStrbufStartsWithS(&currentDisk->mountpoint, "/mnt/media_rw/")) {
+ currentDisk->type = FF_DISK_VOLUME_TYPE_EXTERNAL_BIT;
+ } else {
+ currentDisk->type = FF_DISK_VOLUME_TYPE_HIDDEN_BIT;
+ }
+}
+
+#else
+
+static bool isSubvolume(const FFlist* disks, FFDisk* currentDisk) {
+ if (ffStrbufEqualS(&currentDisk->mountFrom, "drvfs")) { // WSL Windows drives
+ return false;
+ }
+
+ if (ffStrbufEqualS(&currentDisk->filesystem, "zfs")) {
+ // ZFS subvolumes
+ uint32_t index = ffStrbufFirstIndexC(&currentDisk->mountFrom, '/');
+ if (index == currentDisk->mountFrom.length) {
+ return false;
+ }
+
+ FF_STRBUF_AUTO_DESTROY zpoolName = ffStrbufCreateNS(index, currentDisk->mountFrom.chars);
+ for (uint32_t i = 0; i < disks->length - 1; i++) {
+ const FFDisk* otherDevice = FF_LIST_GET(FFDisk, *disks, i);
+ if (ffStrbufEqualS(&otherDevice->filesystem, "zfs") && ffStrbufStartsWith(&otherDevice->mountFrom, &zpoolName)) {
+ return true;
+ }
+ }
+
+ return false;
+ } else {
+ // Filter all disks which device was already found. This catches BTRFS subvolumes.
+ for (uint32_t i = 0; i < disks->length - 1; i++) {
+ const FFDisk* otherDevice = FF_LIST_GET(FFDisk, *disks, i);
+
+ if (ffStrbufEqual(&currentDisk->mountFrom, &otherDevice->mountFrom)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+static bool isRemovable(FFDisk* currentDisk) {
+ if (!ffStrbufStartsWithS(&currentDisk->mountFrom, "/dev/")) {
+ return false;
+ }
+
+ char sysBlockPartition[64];
+ snprintf(sysBlockPartition, ARRAY_SIZE(sysBlockPartition), "/sys/class/block/%s", currentDisk->mountFrom.chars + strlen("/dev/"));
+
+ char sysBlockVolume[PATH_MAX]; // /sys/devices/pci0000:00/0000:00:14.0/usb4/4-3/4-3:1.0/host0/target0:0:0/0:0:0:0/block/sda/sda1
+ if (realpath(sysBlockPartition, sysBlockVolume) == NULL) {
+ return false;
+ }
+ char* lastSlash = strrchr(sysBlockVolume, '/');
+ if (lastSlash == NULL) {
+ return false;
+ }
+ strcpy(lastSlash + 1, "removable");
+
+ char removableChar = '0';
+ return ffReadFileData(sysBlockVolume, 1, &removableChar) > 0 && removableChar == '1';
+}
+
+static void detectType(const FFlist* disks, FFDisk* currentDisk, struct mntent* device) {
+ if (hasmntopt(device, "x-gvfs-hide") || hasmntopt(device, "hidden")) {
+ currentDisk->type = FF_DISK_VOLUME_TYPE_HIDDEN_BIT;
+ } else if (isSubvolume(disks, currentDisk)) {
+ currentDisk->type = FF_DISK_VOLUME_TYPE_SUBVOLUME_BIT;
+ } else if (isRemovable(currentDisk)) {
+ currentDisk->type = FF_DISK_VOLUME_TYPE_EXTERNAL_BIT;
+ } else {
+ currentDisk->type = FF_DISK_VOLUME_TYPE_REGULAR_BIT;
+ }
+ if (hasmntopt(device, MNTOPT_RO)) {
+ currentDisk->type |= FF_DISK_VOLUME_TYPE_READONLY_BIT;
+ }
+}
+
+#endif
+
+static void detectStats(FFDisk* disk) {
+ struct statvfs fs;
+ if (statvfs(disk->mountpoint.chars, &fs) != 0) {
+ memset(&fs, 0, sizeof(fs)); // Set all values to 0, so our values get initialized to 0 too
+ }
+
+ disk->bytesTotal = fs.f_blocks * (uint64_t) fs.f_frsize;
+ disk->bytesFree = fs.f_bfree * (uint64_t) fs.f_frsize;
+ disk->bytesAvailable = fs.f_bavail * (uint64_t) fs.f_frsize;
+ disk->bytesUsed = 0; // To be filled in ./disk.c
+
+ if (fs.f_files >= fs.f_ffree) {
+ disk->filesTotal = (uint32_t) fs.f_files;
+ disk->filesUsed = (uint32_t) (disk->filesTotal - fs.f_ffree);
+ } else {
+ // Windows filesystem in WSL
+ disk->filesTotal = disk->filesUsed = 0;
+ }
+
+ disk->createTime = 0;
+#ifdef SYS_statx
+ struct statx stx;
+ if (syscall(SYS_statx, 0, disk->mountpoint.chars, 0, STATX_BTIME, &stx) == 0 && (stx.stx_mask & STATX_BTIME) && stx.stx_btime.tv_sec > 685065600 /*birth of Linux*/) {
+ disk->createTime = (uint64_t) ((stx.stx_btime.tv_sec * 1000) + (stx.stx_btime.tv_nsec / 1000000));
+ }
+#endif
+
+#ifdef __ANDROID__ // hasmntopt requires a higher Android API level
+ if (fs.f_flag & ST_RDONLY) {
+ disk->type |= FF_DISK_VOLUME_TYPE_READONLY_BIT;
+ }
+#endif
+}
+
+const char* ffDetectDisksImpl(FFDiskOptions* options, FFlist* disks) {
+ FILE* mountsFile = setmntent("/proc/mounts", "r");
+ if (mountsFile == NULL) {
+ return "setmntent(\"/proc/mounts\", \"r\") == NULL";
+ }
+
+ struct mntent* device;
+
+ while ((device = getmntent(mountsFile))) {
+ if (__builtin_expect(options->folders.length > 0, false)) {
+ if (!ffStrbufSeparatedContainS(&options->folders, device->mnt_dir, FF_DISK_FOLDER_SEPARATOR)) {
+ continue;
+ }
+ } else if (!isPhysicalDevice(device)) {
+ continue;
+ }
+
+ if (options->hideFolders.length && ffDiskMatchesFolderPatterns(&options->hideFolders, device->mnt_dir, FF_DISK_FOLDER_SEPARATOR)) {
+ continue;
+ }
+
+ if (options->hideFS.length && ffStrbufSeparatedContainS(&options->hideFS, device->mnt_type, ':')) {
+ continue;
+ }
+
+ // We have a valid device, add it to the list
+ FFDisk* disk = FF_LIST_ADD(FFDisk, *disks);
+ disk->type = FF_DISK_VOLUME_TYPE_NONE;
+
+ // detect mountFrom
+ ffStrbufInitS(&disk->mountFrom, device->mnt_fsname);
+
+ // detect mountpoint
+ ffStrbufInitS(&disk->mountpoint, device->mnt_dir);
+
+ // detect filesystem
+ ffStrbufInitS(&disk->filesystem, device->mnt_type);
+
+ // detect name
+ ffStrbufInit(&disk->name);
+ detectName(disk); // Also detects external devices
+
+ // detect type
+ detectType(disks, disk, device);
+
+ // Detects stats
+ detectStats(disk);
+ }
+
+ endmntent(mountsFile);
+
+ return NULL;
+}
diff --git a/src/detection/disk/disk_nosupport.c b/src/detection/disk/disk_nosupport.c
new file mode 100644
index 0000000..eabddbc
--- /dev/null
+++ b/src/detection/disk/disk_nosupport.c
@@ -0,0 +1,5 @@
+#include "disk.h"
+
+const char* ffDetectDisksImpl(FF_A_UNUSED FFDiskOptions* options, FF_A_UNUSED FFlist* disks) {
+ return "Not supported on this platform";
+}
diff --git a/src/detection/disk/disk_sunos.c b/src/detection/disk/disk_sunos.c
new file mode 100644
index 0000000..3d811e0
--- /dev/null
+++ b/src/detection/disk/disk_sunos.c
@@ -0,0 +1,145 @@
+#include "disk.h"
+#include "common/io.h"
+#include "common/strutil.h"
+
+#include <sys/mntent.h>
+#include <sys/stat.h>
+#include <sys/statvfs.h>
+#include <sys/mount.h>
+#include <sys/mnttab.h>
+
+static bool isPhysicalDevice(const struct mnttab* device) {
+ // Always show the root path
+ if (ffStrEquals(device->mnt_mountp, "/")) {
+ return true;
+ }
+
+ if (ffStrEquals(device->mnt_special, "none")) {
+ return false;
+ }
+
+ // ZFS pool
+ if (ffStrEquals(device->mnt_fstype, "zfs")) {
+ return true;
+ }
+
+ // Pseudo filesystems don't have a device in /dev
+ if (!ffStrStartsWith(device->mnt_special, "/dev/")) {
+ return false;
+ }
+
+ struct stat deviceStat;
+ if (stat(device->mnt_special, &deviceStat) != 0) {
+ return false;
+ }
+
+ // Ignore all devices that are not block devices
+ if (!S_ISBLK(deviceStat.st_mode)) {
+ return false;
+ }
+
+ return true;
+}
+
+static bool isSubvolume(const FFlist* disks, FFDisk* currentDisk) {
+ if (ffStrbufEqualS(&currentDisk->filesystem, "zfs")) {
+ // ZFS subvolumes
+ uint32_t index = ffStrbufFirstIndexC(&currentDisk->mountFrom, '/');
+ if (index == currentDisk->mountFrom.length) {
+ return false;
+ }
+
+ FF_STRBUF_AUTO_DESTROY zpoolName = ffStrbufCreateNS(index, currentDisk->mountFrom.chars);
+ for (uint32_t i = 0; i < disks->length - 1; i++) {
+ const FFDisk* otherDevice = FF_LIST_GET(FFDisk, *disks, i);
+ if (ffStrbufEqualS(&otherDevice->filesystem, "zfs") && ffStrbufStartsWith(&otherDevice->mountFrom, &zpoolName)) {
+ return true;
+ }
+ }
+
+ return false;
+ } else {
+ // Filter all disks which device was already found. This catches BTRFS subvolumes.
+ for (uint32_t i = 0; i < disks->length - 1; i++) {
+ const FFDisk* otherDevice = FF_LIST_GET(FFDisk, *disks, i);
+
+ if (ffStrbufEqual(&currentDisk->mountFrom, &otherDevice->mountFrom)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+static void detectType(const FFlist* disks, FFDisk* currentDisk, struct mnttab* device) {
+ if (hasmntopt(device, MNTOPT_NOBROWSE)) {
+ currentDisk->type = FF_DISK_VOLUME_TYPE_HIDDEN_BIT;
+ } else if (isSubvolume(disks, currentDisk)) {
+ currentDisk->type = FF_DISK_VOLUME_TYPE_SUBVOLUME_BIT;
+ } else {
+ currentDisk->type = FF_DISK_VOLUME_TYPE_REGULAR_BIT;
+ }
+ if (hasmntopt(device, MNTOPT_RO)) {
+ currentDisk->type |= FF_DISK_VOLUME_TYPE_READONLY_BIT;
+ }
+}
+
+static void detectStats(FFDisk* disk) {
+ struct statvfs fs;
+ if (statvfs(disk->mountpoint.chars, &fs) != 0) {
+ memset(&fs, 0, sizeof(fs));
+ }
+
+ disk->bytesTotal = fs.f_blocks * fs.f_frsize;
+ disk->bytesFree = fs.f_bfree * fs.f_frsize;
+ disk->bytesAvailable = fs.f_bavail * fs.f_frsize;
+ disk->bytesUsed = 0; // To be filled in ./disk.c
+
+ disk->filesTotal = (uint32_t) fs.f_files;
+ disk->filesUsed = (uint32_t) (disk->filesTotal - fs.f_ffree);
+
+ ffStrbufSetS(&disk->name, fs.f_fstr);
+ ffStrbufTrimRightSpace(&disk->name);
+
+ disk->createTime = 0;
+}
+
+const char* ffDetectDisksImpl(FFDiskOptions* options, FFlist* disks) {
+ FF_AUTO_CLOSE_FILE FILE* mountsFile = fopen(MNTTAB, "r");
+ if (mountsFile == NULL) {
+ return "fopen(\"" MNTTAB "\", \"r\") == NULL";
+ }
+
+ struct mnttab device;
+
+ while (getmntent(mountsFile, &device) == 0) {
+ if (__builtin_expect(options->folders.length, 0)) {
+ if (!ffStrbufSeparatedContainS(&options->folders, device.mnt_mountp, FF_DISK_FOLDER_SEPARATOR)) {
+ continue;
+ }
+ } else if (!isPhysicalDevice(&device)) {
+ continue;
+ }
+
+ if (options->hideFolders.length && ffDiskMatchesFolderPatterns(&options->hideFolders, device.mnt_mountp, FF_DISK_FOLDER_SEPARATOR)) {
+ continue;
+ }
+
+ if (options->hideFS.length && ffStrbufSeparatedContainS(&options->hideFS, device.mnt_fstype, ':')) {
+ continue;
+ }
+
+ // We have a valid device, add it to the list
+ FFDisk* disk = FF_LIST_ADD(FFDisk, *disks);
+ disk->type = FF_DISK_VOLUME_TYPE_NONE;
+ ffStrbufInitS(&disk->mountFrom, device.mnt_special);
+ ffStrbufInitS(&disk->mountpoint, device.mnt_mountp);
+ ffStrbufInitS(&disk->filesystem, device.mnt_fstype);
+ ffStrbufInit(&disk->name);
+ detectType(disks, disk, &device);
+ detectStats(disk);
+ }
+
+ return NULL;
+}
diff --git a/src/detection/disk/disk_windows.c b/src/detection/disk/disk_windows.c
new file mode 100644
index 0000000..0b3c93d
--- /dev/null
+++ b/src/detection/disk/disk_windows.c
@@ -0,0 +1,128 @@
+#include "disk.h"
+#include "common/io.h"
+#include "common/time.h"
+#include "common/windows/unicode.h"
+#include "common/windows/nt.h"
+
+#include <windows.h>
+#include <winioctl.h>
+#include <ntstatus.h>
+#include <stdalign.h>
+
+const char* ffDetectDisksImpl(FFDiskOptions* options, FFlist* disks) {
+ PROCESS_DEVICEMAP_INFORMATION_EX info = {};
+ ULONG size = 0;
+ if (!NT_SUCCESS(NtQueryInformationProcess(NtCurrentProcess(), ProcessDeviceMap, &info, sizeof(info), &size))) {
+ return "NtQueryInformationProcess(ProcessDeviceMap) failed";
+ }
+
+ // For cross-platform portability; used by `presets/examples/13.jsonc`
+ if (options->folders.length == 1 && options->folders.chars[0] == '/') {
+ options->folders.chars[0] = (char) SharedUserData->NtSystemRoot[0];
+ ffStrbufAppendS(&options->folders, ":\\");
+ }
+
+ wchar_t mountpointW[] = L"X:\\";
+ char mountpointA[] = "X:\\";
+
+ for (wchar_t i = L'A'; i <= L'Z'; i++) {
+ if (!(info.Query.DriveMap & (1 << (i - L'A')))) {
+ continue;
+ }
+ mountpointW[0] = i;
+ mountpointA[0] = (char) i;
+
+ UINT driveType = info.Query.DriveType[i - L'A'];
+
+ if (__builtin_expect((long) options->folders.length, 0)) {
+ if (!ffStrbufSeparatedContainNS(&options->folders, 3, mountpointA, FF_DISK_FOLDER_SEPARATOR)) {
+ continue;
+ }
+ } else if (driveType == DRIVE_NO_ROOT_DIR) {
+ continue;
+ }
+
+ if (options->hideFolders.length && ffStrbufSeparatedContainNS(&options->hideFolders, 3, mountpointA, FF_DISK_FOLDER_SEPARATOR)) {
+ continue;
+ }
+
+ FF_AUTO_CLOSE_FD HANDLE handle = CreateFileW(mountpointW, FILE_READ_ATTRIBUTES, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
+ if (handle == INVALID_HANDLE_VALUE) {
+ continue;
+ }
+
+ IO_STATUS_BLOCK iosb;
+
+ alignas(FILE_FS_ATTRIBUTE_INFORMATION) uint8_t bufFsAttr[1024];
+ FILE_FS_ATTRIBUTE_INFORMATION* fsAttr = NT_SUCCESS(NtQueryVolumeInformationFile(handle, &iosb, bufFsAttr, sizeof(bufFsAttr), FileFsAttributeInformation))
+ ? (FILE_FS_ATTRIBUTE_INFORMATION*) bufFsAttr
+ : NULL;
+
+ FF_STRBUF_AUTO_DESTROY diskFileSystemBuf = ffStrbufCreate();
+ if (fsAttr) {
+ ffStrbufSetNWS(&diskFileSystemBuf, fsAttr->FileSystemNameLength / sizeof(WCHAR), fsAttr->FileSystemName);
+ if (options->hideFS.length && ffStrbufSeparatedContain(&options->hideFS, &diskFileSystemBuf, ':')) {
+ continue;
+ }
+ }
+
+ FFDisk* disk = FF_LIST_ADD(FFDisk, *disks);
+
+ disk->filesUsed = 0;
+ disk->filesTotal = 0;
+ disk->bytesTotal = 0;
+ disk->bytesFree = 0;
+ disk->bytesUsed = 0; // To be filled in ./disk.c
+ disk->bytesAvailable = 0;
+ disk->createTime = 0;
+ ffStrbufInit(&disk->filesystem);
+ ffStrbufInit(&disk->name);
+ ffStrbufInitNS(&disk->mountpoint, 3, mountpointA);
+ ffStrbufInit(&disk->mountFrom);
+ disk->type = driveType == DRIVE_REMOVABLE || driveType == DRIVE_REMOTE || driveType == DRIVE_CDROM
+ ? FF_DISK_VOLUME_TYPE_EXTERNAL_BIT
+ : driveType == DRIVE_FIXED
+ ? FF_DISK_VOLUME_TYPE_REGULAR_BIT
+ : FF_DISK_VOLUME_TYPE_HIDDEN_BIT;
+
+ {
+ wchar_t volumeName[MAX_PATH + 1];
+ mountpointW[2] = L'\0';
+ if (QueryDosDeviceW(mountpointW, volumeName, ARRAY_SIZE(volumeName))) {
+ ffStrbufSetWS(&disk->mountFrom, volumeName);
+ }
+ mountpointW[2] = L'\\';
+ }
+
+ alignas(FILE_FS_VOLUME_INFORMATION) uint8_t bufFsVolume[1024];
+ FILE_FS_VOLUME_INFORMATION* fsVolume = NT_SUCCESS(NtQueryVolumeInformationFile(handle, &iosb, bufFsVolume, sizeof(bufFsVolume), FileFsVolumeInformation))
+ ? (FILE_FS_VOLUME_INFORMATION*) bufFsVolume
+ : NULL;
+
+ if (fsVolume) {
+ if (fsVolume->VolumeLabelLength > 0) {
+ ffStrbufSetNWS(&disk->name, fsVolume->VolumeLabelLength / sizeof(WCHAR), fsVolume->VolumeLabel);
+ }
+ if (fsVolume->VolumeCreationTime.QuadPart) {
+ disk->createTime = ffFileTimeToUnixMs((uint64_t) fsVolume->VolumeCreationTime.QuadPart);
+ }
+ }
+
+ if (fsAttr) {
+ ffStrbufInitMove(&disk->filesystem, &diskFileSystemBuf);
+ if (fsAttr->FileSystemAttributes & FILE_READ_ONLY_VOLUME) {
+ disk->type |= FF_DISK_VOLUME_TYPE_READONLY_BIT;
+ }
+ }
+
+ FILE_FS_FULL_SIZE_INFORMATION fsFullSize;
+ if (NT_SUCCESS(NtQueryVolumeInformationFile(handle, &iosb, &fsFullSize, sizeof(fsFullSize), FileFsFullSizeInformation))) {
+ uint64_t units = fsFullSize.BytesPerSector * fsFullSize.SectorsPerAllocationUnit;
+ disk->bytesTotal = (uint64_t) fsFullSize.TotalAllocationUnits.QuadPart * units;
+ disk->bytesFree = (uint64_t) fsFullSize.ActualAvailableAllocationUnits.QuadPart * units;
+ disk->bytesAvailable = (uint64_t) fsFullSize.CallerAvailableAllocationUnits.QuadPart * units;
+ }
+ }
+
+ return NULL;
+}