logo

utils-std

Collection of commonly available Unix tools git clone https://anongit.hacktivis.me/git/utils-std.git/

df.c (10951B)


  1. // utils-std: Collection of commonly available Unix tools
  2. // SPDX-FileCopyrightText: 2017 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
  3. // SPDX-License-Identifier: MPL-2.0
  4. #define _POSIX_C_SOURCE 202405L
  5. #define _DEFAULT_SOURCE // mntent in glibc 2.19+
  6. #include "../lib/reallocarray.h"
  7. #include "../libutils/getopt_nolong.h"
  8. #include "../libutils/humanize.h"
  9. #include <ctype.h> // iscntrl, isspace
  10. #include <errno.h> // errno
  11. #include <mntent.h>
  12. #include <stdbool.h>
  13. #include <stdio.h> // printf
  14. #include <stdlib.h> // abort, exit
  15. #include <string.h> // strerror
  16. #include <sys/stat.h> // stat, dev_t
  17. #include <sys/statvfs.h>
  18. #include <unistd.h> // getopt
  19. #define STR(s) #s
  20. /*
  21. * Rough idea of builtin filesystems in Linux 6.12.31:
  22. * $ grep -RI '\bregister_filesystem(' /usr/src/linux/fs/ | wc -l
  23. * 79
  24. */
  25. #define DF_MNT_TYPE_MAX 128
  26. // Grabbed from OpenRC rc-functions.sh
  27. // clang-format off
  28. static const char *net_fs_list[] = {
  29. "afs", "ceph", "cifs", "coda", "davfs", "fuse", "fuse.glusterfs",
  30. "fuse.sshfs", "gfs", "glusterfs", "lustre", "ncpfs", "nfs", "nfs4",
  31. "ocfs2", "shfs", "smbfs"
  32. };
  33. // clang-format on
  34. blksize_t forced_bsize = 0;
  35. const char *argv0 = "df";
  36. // Replaces control characters and whitespaces with '?' in-place (no allocation)
  37. static void
  38. static_escape(char *str)
  39. {
  40. for(size_t i = 0; i < strlen(str); i++)
  41. if(iscntrl(str[i]) || isspace(str[i])) str[i] = '?';
  42. }
  43. int
  44. main(int argc, char *argv[])
  45. {
  46. bool opt_P = false, opt_h = false, opt_a = false, opt_T = false, opt_l = false, opt_i = false;
  47. int fs_col_width = 20;
  48. dev_t *devices = NULL;
  49. size_t excluded_count = 0;
  50. static char *excluded[DF_MNT_TYPE_MAX];
  51. size_t only_count = 0;
  52. static char *only[DF_MNT_TYPE_MAX];
  53. for(int c = -1; (c = getopt_nolong(argc, argv, ":ahilPkTt:x:")) != -1;)
  54. {
  55. switch(c)
  56. {
  57. case 'a':
  58. opt_a = true;
  59. break;
  60. case 'P':
  61. if(forced_bsize == 0) forced_bsize = 512;
  62. opt_P = true;
  63. break;
  64. case 'k':
  65. forced_bsize = 1024;
  66. break;
  67. case 'h':
  68. opt_h = true;
  69. fs_col_width = 30;
  70. break;
  71. case 'i':
  72. opt_i = true;
  73. break;
  74. case 'l':
  75. opt_l = true;
  76. break;
  77. case 'T':
  78. opt_T = true;
  79. break;
  80. case 't':
  81. if(only_count >= (DF_MNT_TYPE_MAX - 1))
  82. {
  83. fprintf(
  84. stderr,
  85. "%s: error: Can only select up to " STR(DF_MNT_TYPE_MAX) " mnt_types via option `-t`\n",
  86. argv0);
  87. return 1;
  88. }
  89. only[only_count++] = optarg;
  90. break;
  91. case 'x':
  92. if(excluded_count >= (DF_MNT_TYPE_MAX - 1))
  93. {
  94. fprintf(
  95. stderr,
  96. "%s: error: Can only select up to " STR(DF_MNT_TYPE_MAX) " mnt_types via option `-x`\n",
  97. argv0);
  98. return 1;
  99. }
  100. excluded[excluded_count++] = optarg;
  101. break;
  102. case ':':
  103. fprintf(stderr, "%s: error: Missing operand for option: '-%c'\n", argv0, optopt);
  104. return 1;
  105. case '?':
  106. GETOPT_UNKNOWN_OPT
  107. return 1;
  108. }
  109. }
  110. if(opt_P && opt_T)
  111. {
  112. fprintf(stderr, "%s: error: Options -P and -T are incompatible\n", argv0);
  113. return 1;
  114. }
  115. if(opt_P) opt_h = false;
  116. argc -= optind;
  117. argv += optind;
  118. int args_left = argc != 0 ? argc : 1;
  119. bool tty_out = isatty(1);
  120. int col_width = tty_out ? 10 : 0;
  121. if(!tty_out)
  122. {
  123. errno = 0;
  124. fs_col_width = 0;
  125. }
  126. dev_t *arg_devs = NULL;
  127. if(argc > 0)
  128. {
  129. arg_devs = calloc(argc, sizeof(dev_t));
  130. if(arg_devs == NULL)
  131. {
  132. fprintf(stderr, "%s: error: Failed to allocate arg_devs array: %s\n", argv0, strerror(errno));
  133. return 1;
  134. }
  135. }
  136. for(int i = 0; i < argc; i++)
  137. {
  138. struct stat file_stats;
  139. if(stat(argv[i], &file_stats) != 0)
  140. {
  141. fprintf(stderr,
  142. "%s: error: Failed getting status for file '%s': %s\n",
  143. argv0,
  144. argv[i],
  145. strerror(errno));
  146. goto error;
  147. }
  148. arg_devs[i] = file_stats.st_dev;
  149. }
  150. // Begin: Print header
  151. printf("%-*s ", fs_col_width, "Filesystem");
  152. if(opt_T) printf("%*s ", col_width, "Type");
  153. if(opt_i)
  154. {
  155. printf("%*s %*s %*s ", col_width, "Inodes", col_width, "Iused", col_width, "IFree");
  156. if(opt_P)
  157. printf("IUse%% Mounted on\n");
  158. else
  159. printf("IUse%% Mountpoint\n");
  160. }
  161. else
  162. {
  163. if(forced_bsize != 0)
  164. printf("%zd-blocks ", forced_bsize);
  165. else
  166. printf("%*s ", col_width, "Total");
  167. printf("%*s %*s ", col_width, "Used", col_width, "Available");
  168. if(opt_P)
  169. printf("Capacity Mounted on\n");
  170. else
  171. printf("Use%% Mountpoint\n");
  172. }
  173. // End: Print header
  174. FILE *mounted = setmntent(MOUNTED, "r");
  175. if(mounted == NULL)
  176. {
  177. fprintf(stderr,
  178. "%s: error: Failed opening setmntent(\"" MOUNTED "\", \"r\"): %s\n",
  179. argv0,
  180. strerror(errno));
  181. goto error;
  182. }
  183. // Linux has a configurable limit of maximum mounts which defaults to 100_000
  184. // See /proc/sys/fs/mount-max introduced in Linux 4.9 for CVE-2016-6213
  185. // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=d29216842a85c7970c536108e093963f02714498
  186. //
  187. // Which is way too much to be either stack-allocated or statically allocated:
  188. // 100000*sizeof(dev_t) = 100000*8 bytes ≈ 781.25 KiB
  189. // So reallocarray to the rescue
  190. size_t devices_found = 0;
  191. size_t devices_len = 100;
  192. devices = reallocarray(NULL, devices_len, sizeof(dev_t));
  193. if(!devices)
  194. {
  195. fprintf(stderr,
  196. "%s: error: Failed to allocate memory for storing (%zd) devices: %s\n",
  197. argv0,
  198. devices_len,
  199. strerror(errno));
  200. goto error;
  201. }
  202. // Even with argc>0 we still need to go over mntent for the filesystem mountpoint and type
  203. while(args_left > 0)
  204. {
  205. struct mntent *mntent = getmntent(mounted);
  206. if(mntent == NULL) break;
  207. if(excluded_count > 0)
  208. {
  209. bool exclude = false;
  210. for(size_t i = 0; i < excluded_count; i++)
  211. if(strcmp(excluded[i], mntent->mnt_type) == 0)
  212. {
  213. exclude = true;
  214. break;
  215. }
  216. if(exclude) continue;
  217. }
  218. if(only_count > 0)
  219. {
  220. bool include = false;
  221. for(size_t i = 0; i < only_count; i++)
  222. if(strcmp(only[i], mntent->mnt_type) == 0)
  223. {
  224. include = true;
  225. break;
  226. }
  227. if(!include) continue;
  228. }
  229. if(opt_l)
  230. {
  231. bool remote = false;
  232. for(size_t i = 0; i < sizeof(net_fs_list) / sizeof(char *); i++)
  233. if(strcmp(net_fs_list[i], mntent->mnt_type) == 0)
  234. {
  235. remote = true;
  236. break;
  237. }
  238. if(remote) continue;
  239. }
  240. struct stat file_stats;
  241. if(!opt_a || argc > 0)
  242. {
  243. if(stat(mntent->mnt_dir, &file_stats) != 0)
  244. {
  245. fprintf(stderr,
  246. "%s: warning: Failed getting status for file '%s': %s\n",
  247. argv0,
  248. mntent->mnt_dir,
  249. strerror(errno));
  250. errno = 0;
  251. }
  252. }
  253. if(argc > 0)
  254. {
  255. bool found = false;
  256. for(int i = 0; i < argc; i++)
  257. if(arg_devs[i] == file_stats.st_dev)
  258. {
  259. found = true;
  260. break;
  261. }
  262. if(!found) continue;
  263. args_left--;
  264. }
  265. if(!opt_a)
  266. {
  267. bool dupe = false;
  268. for(size_t i = 0; i < devices_found; i++)
  269. if(devices[i] == file_stats.st_dev)
  270. {
  271. dupe = true;
  272. break;
  273. }
  274. if(dupe) continue;
  275. if(devices_found + 1 >= devices_len)
  276. {
  277. devices_len *= 2;
  278. if(devices_len <= 0) abort();
  279. devices = reallocarray(devices, devices_len, sizeof(dev_t));
  280. if(!devices)
  281. {
  282. fprintf(stderr,
  283. "%s: error: Failed to allocate memory for storing (%zd) devices: %s\n",
  284. argv0,
  285. devices_len,
  286. strerror(errno));
  287. goto error;
  288. }
  289. }
  290. devices[devices_found++] = file_stats.st_dev;
  291. }
  292. // Note: musl prior to 1.2.5 has broken getmntent when octal sequences and carriage return is used
  293. // https://git.musl-libc.org/cgit/musl/commit/src/misc/mntent.c?id=f314e133929b6379eccc632bef32eaebb66a7335
  294. // https://git.musl-libc.org/cgit/musl/commit/src/misc/mntent.c?id=ee1d39bc1573c1ae49ee6b658938b56bbef95a6c
  295. struct statvfs stats;
  296. if(statvfs(mntent->mnt_dir, &stats) != 0)
  297. {
  298. fprintf(stderr,
  299. "%s: warning: Failed getting statistics for filesystem '%s': %s\n",
  300. argv0,
  301. mntent->mnt_dir,
  302. strerror(errno));
  303. errno = 0;
  304. static_escape(mntent->mnt_fsname);
  305. static_escape(mntent->mnt_dir);
  306. if(opt_a)
  307. {
  308. printf("%-*s ", fs_col_width, mntent->mnt_fsname);
  309. if(opt_T) printf("%*s ", col_width, mntent->mnt_type);
  310. printf("%*s ", col_width, "-"); // total
  311. printf("%*s ", col_width, "-"); // used
  312. printf("%*s ", col_width, "-"); // free
  313. printf("%*s ", tty_out ? 3 : 0, "-"); // percent
  314. printf("%s\n", mntent->mnt_dir);
  315. }
  316. continue;
  317. }
  318. // Skip null filesystems
  319. if(!opt_a && stats.f_blocks == 0) continue;
  320. // Needs to be done after calling statvfs(3) and stat(3)
  321. static_escape(mntent->mnt_fsname);
  322. static_escape(mntent->mnt_dir);
  323. printf("%-*s ", fs_col_width, mntent->mnt_fsname);
  324. if(opt_T) printf("%*s ", col_width, mntent->mnt_type);
  325. if(opt_i)
  326. {
  327. fsfilcnt_t used = stats.f_files - stats.f_ffree;
  328. fsfilcnt_t percent = (used / stats.f_files) * 100;
  329. if(opt_h && !opt_P)
  330. {
  331. struct si_scale total_scl = dtosi(stats.f_files, false);
  332. struct si_scale used_scl = dtosi(used, false);
  333. struct si_scale free_scl = dtosi(stats.f_ffree, false);
  334. int width_num = tty_out ? col_width - 1 : 0;
  335. int width_pre = tty_out ? 1 : 0;
  336. printf("%*.2f%-*s ", width_num, total_scl.number, width_pre, total_scl.prefix);
  337. printf("%*.2f%-*s ", width_num, used_scl.number, width_pre, used_scl.prefix);
  338. printf("%*.2f%-*s ", width_num, free_scl.number, width_pre, free_scl.prefix);
  339. }
  340. else
  341. {
  342. printf("%*zd ", col_width, stats.f_files);
  343. printf("%*zd ", col_width, used);
  344. printf("%*zd ", col_width, stats.f_ffree);
  345. }
  346. printf("%*zd%% ", tty_out ? 4 : 0, percent);
  347. printf("%s\n", mntent->mnt_dir);
  348. continue;
  349. }
  350. off_t percent = 0;
  351. off_t total = stats.f_frsize * (stats.f_blocks != 0 ? stats.f_blocks : 1);
  352. off_t free = stats.f_bfree * (stats.f_bsize != 0 ? stats.f_bsize : 1);
  353. off_t used = total - free;
  354. if(used + free)
  355. {
  356. percent = (used * 100) / (used + free);
  357. if(used * 100 != percent * (used + free)) percent++;
  358. }
  359. if(forced_bsize != 0)
  360. {
  361. total /= forced_bsize;
  362. free /= forced_bsize;
  363. used /= forced_bsize;
  364. }
  365. if(opt_h && !opt_P)
  366. {
  367. struct si_scale total_scl = dtosi(total, true);
  368. struct si_scale used_scl = dtosi(used, true);
  369. struct si_scale free_scl = dtosi(free, true);
  370. int width_num = tty_out ? col_width - 3 : 0;
  371. int width_pre = tty_out ? 3 : 0;
  372. printf("%*.2f%-*s ", width_num, total_scl.number, width_pre, total_scl.prefix);
  373. printf("%*.2f%-*s ", width_num, used_scl.number, width_pre, used_scl.prefix);
  374. printf("%*.2f%-*s ", width_num, free_scl.number, width_pre, free_scl.prefix);
  375. }
  376. else
  377. {
  378. printf("%*zd ", col_width, total);
  379. printf("%*zd ", col_width, used);
  380. printf("%*zd ", col_width, free);
  381. }
  382. printf("%*zd%% ", tty_out ? 3 : 0, percent);
  383. printf("%s\n", mntent->mnt_dir);
  384. }
  385. endmntent(mounted);
  386. free(devices);
  387. free(arg_devs);
  388. return 0;
  389. error:
  390. free(devices);
  391. free(arg_devs);
  392. return 1;
  393. }