diff options
| author | sumuel <samuel@yakubos.org> | 2026-08-17 20:44:55 +0000 |
|---|---|---|
| committer | sumuel <samuel@yakubos.org> | 2026-08-17 20:44:55 +0000 |
| commit | 76424950e373d3b04ac3dd13019151bfba3e8423 (patch) | |
| tree | 4c3cfdbda039e592b9186be3e28f8d7cfd439e8a /src/detection/disk/disk_linux.c | |
Add the files
Diffstat (limited to 'src/detection/disk/disk_linux.c')
| -rw-r--r-- | src/detection/disk/disk_linux.c | 337 |
1 files changed, 337 insertions, 0 deletions
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(¤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; +} |