#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; }