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(¤tDisk->mountpoint, "/") || ffStrbufEqualS(¤tDisk->mountpoint, "/storage/emulated")) {
168 currentDisk->type = FF_DISK_VOLUME_TYPE_REGULAR_BIT;
169 } else if (ffStrbufStartsWithS(¤tDisk->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(¤tDisk->mountFrom, "drvfs")) { // WSL Windows drives
180 return false;
181 }
182
183 if (ffStrbufEqualS(¤tDisk->filesystem, "zfs")) {
184 // ZFS subvolumes
185 uint32_t index = ffStrbufFirstIndexC(¤tDisk->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(¤tDisk->mountFrom, &otherDevice->mountFrom)) {
205 return true;
206 }
207 }
208 }
209
210 return false;
211}
212
213static bool isRemovable(FFDisk* currentDisk) {
214 if (!ffStrbufStartsWithS(¤tDisk->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}