logo

utils-std

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

df.c (9458B)


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