logo

utils-std

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

df.c (9349B)


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