master ohmu/fastfetch-openbsd / src / detection / disk / disk_linux.c
ohmu  ·  2026-10-15
  1#include "disk.h"
  2
  3#include "common/debug.h"
  4#include "common/io.h"
  5#include "common/strutil.h"
  6
  7#include <limits.h>
  8#include <ctype.h>
  9#include <dirent.h>
 10#include <mntent.h>
 11#include <sys/stat.h>
 12#include <sys/statvfs.h>
 13
 14#if defined(STATX_BTIME) && !defined(__ANDROID__)
 15    #include <sys/syscall.h>
 16#endif
 17
 18#ifdef __USE_LARGEFILE64
 19    #define stat stat64
 20    #define statvfs statvfs64
 21    #define dirent dirent64
 22    #define readdir readdir64
 23#endif
 24
 25static bool isPhysicalDevice(const struct mntent* device) {
 26#ifndef __ANDROID__ // On Android, `/dev` is not accessible, so that the following checks always fail
 27
 28    // Always show the root path
 29    if (ffStrEquals(device->mnt_dir, "/")) {
 30        return true;
 31    }
 32
 33    if (ffStrEquals(device->mnt_fsname, "none")) {
 34        return false;
 35    }
 36
 37    // DrvFs is a filesystem plugin to WSL that was designed to support interop between WSL and the Windows filesystem.
 38    if (ffStrEquals(device->mnt_type, "9p")) {
 39        return ffStrContains(device->mnt_opts, "aname=drvfs");
 40    }
 41
 42    // ZFS pool
 43    if (ffStrEquals(device->mnt_type, "zfs")) {
 44        return true;
 45    }
 46
 47    // sshfs
 48    if (ffStrEquals(device->mnt_type, "fuse.sshfs")) {
 49        return true;
 50    }
 51
 52    // Pseudo filesystems don't have a device in /dev
 53    if (!ffStrStartsWith(device->mnt_fsname, "/dev/")) {
 54        return false;
 55    }
 56
 57    // #731
 58    if (ffStrEquals(device->mnt_type, "bcachefs")) {
 59        return true;
 60    }
 61
 62    if (
 63        ffStrStartsWith(device->mnt_fsname + 5, "loop") || // Ignore loop devices
 64        ffStrStartsWith(device->mnt_fsname + 5, "ram") ||  // Ignore ram devices
 65        ffStrStartsWith(device->mnt_fsname + 5, "fd")      // Ignore fd devices
 66    ) {
 67        return false;
 68    }
 69
 70    if (ffStrStartsWith(device->mnt_dir, "/bedrock/")) { // Ignore Bedrock Linux subvolumes
 71        return false;
 72    }
 73
 74    struct stat deviceStat;
 75    if (stat(device->mnt_fsname, &deviceStat) != 0) {
 76        return false;
 77    }
 78
 79    // Ignore all devices that are not block devices
 80    if (!S_ISBLK(deviceStat.st_mode)) {
 81        return false;
 82    }
 83
 84#else
 85
 86    // Pseudo filesystems don't have a device in /dev
 87    if (!ffStrStartsWith(device->mnt_fsname, "/dev/")) {
 88        return false;
 89    }
 90
 91    if (
 92        ffStrStartsWith(device->mnt_fsname + 5, "loop") || // Ignore loop devices
 93        ffStrStartsWith(device->mnt_fsname + 5, "ram") ||  // Ignore ram devices
 94        ffStrStartsWith(device->mnt_fsname + 5, "fd")      // Ignore fd devices
 95    ) {
 96        return false;
 97    }
 98
 99    // https://source.android.com/docs/core/ota/apex?hl=zh-cn
100    if (ffStrStartsWith(device->mnt_dir, "/apex/")) {
101        return false;
102    }
103
104#endif // __ANDROID__
105
106    return true;
107}
108
109static void detectNameFromPath(FFDisk* disk, const struct stat* deviceStat, FFstrbuf* basePath) {
110    FF_AUTO_CLOSE_DIR DIR* dir = opendir(basePath->chars);
111    if (dir == nullptr) {
112        return;
113    }
114
115    uint32_t basePathLength = basePath->length;
116
117    struct dirent* entry;
118    while ((entry = readdir(dir)) != nullptr) {
119        if (entry->d_name[0] == '.') {
120            continue;
121        }
122
123        ffStrbufAppendS(basePath, entry->d_name);
124
125        struct stat entryStat;
126        bool ret = stat(basePath->chars, &entryStat) == 0;
127
128        ffStrbufSubstrBefore(basePath, basePathLength);
129
130        if (!ret || deviceStat->st_ino != entryStat.st_ino) {
131            continue;
132        }
133
134        ffStrbufAppendS(&disk->name, entry->d_name);
135        break;
136    }
137}
138
139static void detectName(FFDisk* disk) {
140    struct stat deviceStat;
141    if (stat(disk->mountFrom.chars, &deviceStat) != 0) {
142        return;
143    }
144
145    FF_STRBUF_AUTO_DESTROY basePath = ffStrbufCreate();
146
147    // Try label first
148    ffStrbufSetS(&basePath, "/dev/disk/by-label/");
149    detectNameFromPath(disk, &deviceStat, &basePath);
150
151    if (disk->name.length == 0) {
152        // Try partlabel second
153        ffStrbufSetS(&basePath, "/dev/disk/by-partlabel/");
154        detectNameFromPath(disk, &deviceStat, &basePath);
155    }
156
157    if (disk->name.length == 0) {
158        return;
159    }
160
161    ffStrbufDecodeHexEscapeSequences(&disk->name);
162}
163
164#ifdef __ANDROID__
165
166static void detectType([[maybe_unused]] const FFlist* disks, FFDisk* currentDisk, [[maybe_unused]] struct mntent* device) {
167    if (ffStrbufEqualS(&currentDisk->mountpoint, "/") || ffStrbufEqualS(&currentDisk->mountpoint, "/storage/emulated")) {
168        currentDisk->type = FF_DISK_VOLUME_TYPE_REGULAR_BIT;
169    } else if (ffStrbufStartsWithS(&currentDisk->mountpoint, "/mnt/media_rw/")) {
170        currentDisk->type = FF_DISK_VOLUME_TYPE_EXTERNAL_BIT;
171    } else {
172        currentDisk->type = FF_DISK_VOLUME_TYPE_HIDDEN_BIT;
173    }
174}
175
176#else
177
178static bool isSubvolume(const FFlist* disks, FFDisk* currentDisk) {
179    if (ffStrbufEqualS(&currentDisk->mountFrom, "drvfs")) { // WSL Windows drives
180        return false;
181    }
182
183    if (ffStrbufEqualS(&currentDisk->filesystem, "zfs")) {
184        // ZFS subvolumes
185        uint32_t index = ffStrbufFirstIndexC(&currentDisk->mountFrom, '/');
186        if (index == currentDisk->mountFrom.length) {
187            return false;
188        }
189
190        FF_STRBUF_AUTO_DESTROY zpoolName = ffStrbufCreateNS(index, currentDisk->mountFrom.chars);
191        for (uint32_t i = 0; i < disks->length - 1; i++) {
192            const FFDisk* otherDevice = FF_LIST_GET(FFDisk, *disks, i);
193            if (ffStrbufEqualS(&otherDevice->filesystem, "zfs") && ffStrbufStartsWith(&otherDevice->mountFrom, &zpoolName)) {
194                return true;
195            }
196        }
197
198        return false;
199    } else {
200        // Filter all disks which device was already found. This catches BTRFS subvolumes.
201        for (uint32_t i = 0; i < disks->length - 1; i++) {
202            const FFDisk* otherDevice = FF_LIST_GET(FFDisk, *disks, i);
203
204            if (ffStrbufEqual(&currentDisk->mountFrom, &otherDevice->mountFrom)) {
205                return true;
206            }
207        }
208    }
209
210    return false;
211}
212
213static bool isRemovable(FFDisk* currentDisk) {
214    if (!ffStrbufStartsWithS(&currentDisk->mountFrom, "/dev/")) {
215        return false;
216    }
217
218    char sysBlockPartition[64];
219    snprintf(sysBlockPartition, ARRAY_SIZE(sysBlockPartition), "/sys/class/block/%s", currentDisk->mountFrom.chars + strlen("/dev/"));
220
221    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
222    if (realpath(sysBlockPartition, sysBlockVolume) == nullptr) {
223        return false;
224    }
225    char* lastSlash = strrchr(sysBlockVolume, '/');
226    if (lastSlash == nullptr) {
227        return false;
228    }
229    strcpy(lastSlash + 1, "removable");
230
231    char removableChar = '0';
232    return ffReadFileData(sysBlockVolume, 1, &removableChar) > 0 && removableChar == '1';
233}
234
235static void detectType(const FFlist* disks, FFDisk* currentDisk, struct mntent* device) {
236    if (hasmntopt(device, "x-gvfs-hide") || hasmntopt(device, "hidden")) {
237        currentDisk->type = FF_DISK_VOLUME_TYPE_HIDDEN_BIT;
238    } else if (isSubvolume(disks, currentDisk)) {
239        currentDisk->type = FF_DISK_VOLUME_TYPE_SUBVOLUME_BIT;
240    } else if (isRemovable(currentDisk)) {
241        currentDisk->type = FF_DISK_VOLUME_TYPE_EXTERNAL_BIT;
242    } else {
243        currentDisk->type = FF_DISK_VOLUME_TYPE_REGULAR_BIT;
244    }
245    if (hasmntopt(device, MNTOPT_RO)) {
246        currentDisk->type |= FF_DISK_VOLUME_TYPE_READONLY_BIT;
247    }
248}
249
250#endif
251
252static void detectStats(FFDiskOptions* options, FFDisk* disk) {
253    struct statvfs fs;
254    if (statvfs(disk->mountpoint.chars, &fs) != 0) {
255        memset(&fs, 0, sizeof(fs)); // Set all values to 0, so our values get initialized to 0 too
256    }
257
258    disk->bytesTotal = fs.f_blocks * (uint64_t) fs.f_frsize;
259    disk->bytesFree = (options->calcType == FF_DISK_CALC_TYPE_FREE ? fs.f_bfree : fs.f_bavail) * (uint64_t) fs.f_frsize;
260
261    if (fs.f_files >= fs.f_ffree) {
262        disk->filesTotal = (uint32_t) fs.f_files;
263        disk->filesUsed = (uint32_t) (disk->filesTotal - fs.f_ffree);
264    } else {
265        // Windows filesystem in WSL
266        disk->filesTotal = disk->filesUsed = 0;
267    }
268
269    disk->createTime = 0;
270#ifdef SYS_statx
271    struct statx stx;
272    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*/) {
273        disk->createTime = (uint64_t) ((stx.stx_btime.tv_sec * 1000) + (stx.stx_btime.tv_nsec / 1000000));
274    }
275#endif
276
277#ifdef __ANDROID__ // hasmntopt requires a higher Android API level
278    if (fs.f_flag & ST_RDONLY) {
279        disk->type |= FF_DISK_VOLUME_TYPE_READONLY_BIT;
280    }
281#endif
282}
283
284const char* ffDetectDisksImpl(FFDiskOptions* options, FFlist* disks) {
285    FILE* mountsFile = setmntent("/proc/mounts", "r");
286    if (mountsFile == nullptr) {
287        FF_DEBUG("setmntent(\"/proc/mounts\", \"r\") failed: %s", strerror(errno));
288        return "setmntent(\"/proc/mounts\", \"r\") == nullptr";
289    }
290
291    struct mntent* device;
292
293    while ((device = getmntent(mountsFile))) {
294        if (__builtin_expect(options->folders.length > 0, false)) {
295            if (!ffStrbufSeparatedContainS(&options->folders, device->mnt_dir, FF_DISK_FOLDER_SEPARATOR)) {
296                continue;
297            }
298        } else if (!isPhysicalDevice(device)) {
299            continue;
300        }
301
302        if (options->hideFolders.length && ffDiskMatchesFolderPatterns(&options->hideFolders, device->mnt_dir, FF_DISK_FOLDER_SEPARATOR)) {
303            continue;
304        }
305
306        if (options->hideFS.length && ffStrbufSeparatedContainS(&options->hideFS, device->mnt_type, ':')) {
307            continue;
308        }
309
310        // We have a valid device, add it to the list
311        FFDisk* disk = FF_LIST_ADD(FFDisk, *disks);
312        disk->type = FF_DISK_VOLUME_TYPE_NONE;
313
314        // detect mountFrom
315        ffStrbufInitS(&disk->mountFrom, device->mnt_fsname);
316
317        // detect mountpoint
318        ffStrbufInitS(&disk->mountpoint, device->mnt_dir);
319
320        // detect filesystem
321        ffStrbufInitS(&disk->filesystem, device->mnt_type);
322
323        // detect name
324        ffStrbufInit(&disk->name);
325        detectName(disk); // Also detects external devices
326
327        // detect type
328        detectType(disks, disk, device);
329
330        // Detects stats
331        detectStats(options, disk);
332    }
333
334    endmntent(mountsFile);
335
336    return nullptr;
337}