From 76424950e373d3b04ac3dd13019151bfba3e8423 Mon Sep 17 00:00:00 2001 From: sumuel Date: Mon, 17 Aug 2026 20:44:55 +0000 Subject: Add the files --- src/detection/disk/disk.c | 55 ++++++ src/detection/disk/disk.h | 40 +++++ src/detection/disk/disk_bsd.c | 214 +++++++++++++++++++++++ src/detection/disk/disk_haiku.cpp | 74 ++++++++ src/detection/disk/disk_linux.c | 337 ++++++++++++++++++++++++++++++++++++ src/detection/disk/disk_nosupport.c | 5 + src/detection/disk/disk_sunos.c | 145 ++++++++++++++++ src/detection/disk/disk_windows.c | 128 ++++++++++++++ 8 files changed, 998 insertions(+) create mode 100644 src/detection/disk/disk.c create mode 100644 src/detection/disk/disk.h create mode 100644 src/detection/disk/disk_bsd.c create mode 100644 src/detection/disk/disk_haiku.cpp create mode 100644 src/detection/disk/disk_linux.c create mode 100644 src/detection/disk/disk_nosupport.c create mode 100644 src/detection/disk/disk_sunos.c create mode 100644 src/detection/disk/disk_windows.c (limited to 'src/detection/disk') 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 + +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 +#include +#include + +#ifdef __NetBSD__ + #include + #include + #define statfs statvfs + #define f_flags f_flag + #define f_bsize f_frsize +#endif + +#ifdef __FreeBSD__ + #if __has_include() + #include + +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 + #include + + #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 +#include +#include + +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 +#include +#include +#include +#include +#include + +#if defined(STATX_BTIME) && !defined(__ANDROID__) + #include +#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(¤tDisk->mountpoint, "/") || ffStrbufEqualS(¤tDisk->mountpoint, "/storage/emulated")) { + currentDisk->type = FF_DISK_VOLUME_TYPE_REGULAR_BIT; + } else if (ffStrbufStartsWithS(¤tDisk->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(¤tDisk->mountFrom, "drvfs")) { // WSL Windows drives + return false; + } + + if (ffStrbufEqualS(¤tDisk->filesystem, "zfs")) { + // ZFS subvolumes + uint32_t index = ffStrbufFirstIndexC(¤tDisk->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(¤tDisk->mountFrom, &otherDevice->mountFrom)) { + return true; + } + } + } + + return false; +} + +static bool isRemovable(FFDisk* currentDisk) { + if (!ffStrbufStartsWithS(¤tDisk->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 +#include +#include +#include +#include + +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(¤tDisk->filesystem, "zfs")) { + // ZFS subvolumes + uint32_t index = ffStrbufFirstIndexC(¤tDisk->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(¤tDisk->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 +#include +#include +#include + +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; +} -- cgit v1.2.3