logo

utils-std

Collection of commonly available Unix tools

df.c (7915B)


  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. // 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;
  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. int c = EOF;
  44. while((c = getopt(argc, argv, ":ahlPkTt:x:")) != EOF)
  45. {
  46. switch(c)
  47. {
  48. case 'a':
  49. opt_a = true;
  50. break;
  51. case 'P':
  52. if(forced_bsize == 0) forced_bsize = 512;
  53. opt_P = true;
  54. break;
  55. case 'k':
  56. forced_bsize = 1024;
  57. break;
  58. case 'h':
  59. opt_h = true;
  60. fs_col_width = 30;
  61. break;
  62. case 'l':
  63. opt_l = true;
  64. break;
  65. case 'T':
  66. opt_T = true;
  67. break;
  68. case 't':
  69. only[only_count++] = optarg;
  70. break;
  71. case 'x':
  72. excluded[excluded_count++] = optarg;
  73. break;
  74. case ':':
  75. fprintf(stderr, "df: Error: Missing operand for option: '-%c'\n", optopt);
  76. return 1;
  77. case '?':
  78. fprintf(stderr, "df: Error: Unrecognised option: '-%c'\n", optopt);
  79. return 1;
  80. }
  81. }
  82. if(opt_P && opt_T)
  83. {
  84. fprintf(stderr, "df: Options -P and -T are incompatible\n");
  85. return 1;
  86. }
  87. if(opt_P) opt_h = false;
  88. argc -= optind;
  89. argv += optind;
  90. int args_left = argc != 0 ? argc : 1;
  91. bool tty_out = isatty(1);
  92. int col_width = tty_out ? 10 : 0;
  93. if(!tty_out)
  94. {
  95. errno = 0;
  96. fs_col_width = 0;
  97. }
  98. dev_t *arg_devs = NULL;
  99. if(argc > 0)
  100. {
  101. arg_devs = calloc(argc, sizeof(dev_t));
  102. if(arg_devs == NULL)
  103. {
  104. fprintf(stderr, "df: Failed to allocate arg_devs array: %s\n", strerror(errno));
  105. return 1;
  106. }
  107. }
  108. assert(errno == 0);
  109. for(int i = 0; i < argc; i++)
  110. {
  111. struct stat file_stats;
  112. if(stat(argv[i], &file_stats) != 0)
  113. {
  114. fprintf(stderr, "df: Error stat(\"%s\", _): %s\n", argv[i], strerror(errno));
  115. goto error;
  116. }
  117. arg_devs[i] = file_stats.st_dev;
  118. }
  119. // Begin: Print header
  120. printf("%-*s ", fs_col_width, "Filesystem");
  121. if(opt_T) printf("%*s ", col_width, "Type");
  122. if(forced_bsize != 0)
  123. printf("%zd-blocks ", forced_bsize);
  124. else
  125. printf("%*s ", col_width, "Total");
  126. printf("%*s %*s ", col_width, "Used", col_width, "Available");
  127. if(opt_P)
  128. printf("Capacity Mounted on\n");
  129. else
  130. printf("Use%% Mountpoint\n");
  131. // End: Print header
  132. assert(errno == 0);
  133. FILE *mounted = setmntent(MOUNTED, "r");
  134. if(mounted == NULL)
  135. {
  136. fprintf(stderr, "df: Error opening setmntent(\"" MOUNTED "\", \"r\"): %s", strerror(errno));
  137. goto error;
  138. }
  139. // FIXME: Fix maximum number of mount entries / devices
  140. dev_t devices[4096];
  141. size_t devices_found = 0;
  142. // Even with argc>0 we still need to go over mntent for the filesystem mountpoint and type
  143. while(args_left > 0)
  144. {
  145. struct mntent *mntent = getmntent(mounted);
  146. if(mntent == NULL) break;
  147. if(excluded_count > 0)
  148. {
  149. bool exclude = false;
  150. for(size_t i = 0; i < excluded_count; i++)
  151. if(strcmp(excluded[i], mntent->mnt_type) == 0)
  152. {
  153. exclude = true;
  154. break;
  155. }
  156. if(exclude) continue;
  157. }
  158. if(only_count > 0)
  159. {
  160. bool include = false;
  161. for(size_t i = 0; i < only_count; i++)
  162. if(strcmp(only[i], mntent->mnt_type) == 0)
  163. {
  164. include = true;
  165. break;
  166. }
  167. if(!include) continue;
  168. }
  169. if(opt_l)
  170. {
  171. bool remote = false;
  172. for(size_t i = 0; i < sizeof(net_fs_list) / sizeof(char *); i++)
  173. if(strcmp(net_fs_list[i], mntent->mnt_type) == 0)
  174. {
  175. remote = true;
  176. break;
  177. }
  178. if(remote) continue;
  179. }
  180. assert(errno == 0);
  181. struct stat file_stats;
  182. if(!opt_a || argc > 0)
  183. {
  184. if(stat(mntent->mnt_dir, &file_stats) != 0)
  185. {
  186. fprintf(stderr, "df: Warning stat(\"%s\", _): %s\n", mntent->mnt_dir, strerror(errno));
  187. errno = 0;
  188. }
  189. }
  190. if(argc > 0)
  191. {
  192. bool found = false;
  193. for(int i = 0; i < argc; i++)
  194. if(arg_devs[i] == file_stats.st_dev)
  195. {
  196. found = true;
  197. break;
  198. }
  199. if(!found) continue;
  200. args_left--;
  201. }
  202. if(!opt_a)
  203. {
  204. bool dupe = false;
  205. for(size_t i = 0; i < devices_found; i++)
  206. if(devices[i] == file_stats.st_dev)
  207. {
  208. dupe = true;
  209. break;
  210. }
  211. if(dupe) continue;
  212. if(devices_found >= 4096)
  213. fprintf(stderr,
  214. "df: Warning: Reached maximum amount of devices which can be deduplicated\n");
  215. devices[devices_found++] = file_stats.st_dev;
  216. }
  217. // Note: musl prior to 1.2.5 has broken getmntent when octal sequences and carriage return is used
  218. // https://git.musl-libc.org/cgit/musl/commit/src/misc/mntent.c?id=f314e133929b6379eccc632bef32eaebb66a7335
  219. // https://git.musl-libc.org/cgit/musl/commit/src/misc/mntent.c?id=ee1d39bc1573c1ae49ee6b658938b56bbef95a6c
  220. assert(errno == 0);
  221. struct statvfs stats;
  222. if(statvfs(mntent->mnt_dir, &stats) != 0)
  223. {
  224. fprintf(stderr, "df: Warning: statvfs(\"%s\", _): %s\n", mntent->mnt_dir, strerror(errno));
  225. errno = 0;
  226. static_escape(mntent->mnt_fsname);
  227. static_escape(mntent->mnt_dir);
  228. if(opt_a)
  229. {
  230. printf("%-*s ", fs_col_width, mntent->mnt_fsname);
  231. if(opt_T) printf("%*s ", col_width, mntent->mnt_type);
  232. printf("%*s ", col_width, "-"); // total
  233. printf("%*s ", col_width, "-"); // used
  234. printf("%*s ", col_width, "-"); // free
  235. printf("%*s ", tty_out ? 3 : 0, "-"); // percent
  236. printf("%s\n", mntent->mnt_dir);
  237. }
  238. continue;
  239. }
  240. // Skip null filesystems
  241. if(!opt_a && stats.f_blocks == 0) continue;
  242. // Needs to be done after calling statvfs(3) and stat(3)
  243. static_escape(mntent->mnt_fsname);
  244. static_escape(mntent->mnt_dir);
  245. off_t percent = 0;
  246. off_t total = stats.f_frsize * (stats.f_blocks != 0 ? stats.f_blocks : 1);
  247. off_t free = stats.f_bfree * (stats.f_bsize != 0 ? stats.f_bsize : 1);
  248. off_t used = total - free;
  249. if(used + free)
  250. {
  251. percent = (used * 100) / (used + free);
  252. if(used * 100 != percent * (used + free)) percent++;
  253. }
  254. if(forced_bsize != 0)
  255. {
  256. total /= forced_bsize;
  257. free /= forced_bsize;
  258. used /= forced_bsize;
  259. }
  260. printf("%-*s ", fs_col_width, mntent->mnt_fsname);
  261. if(opt_T) printf("%*s ", col_width, mntent->mnt_type);
  262. if(opt_h && !opt_P)
  263. {
  264. struct si_scale total_scl = dtosi(total, true);
  265. struct si_scale used_scl = dtosi(used, true);
  266. struct si_scale free_scl = dtosi(free, true);
  267. int width_num = tty_out ? col_width - 3 : 0;
  268. int width_pre = tty_out ? 3 : 0;
  269. printf("%*.2f%-*s ", width_num, total_scl.number, width_pre, total_scl.prefix);
  270. printf("%*.2f%-*s ", width_num, used_scl.number, width_pre, used_scl.prefix);
  271. printf("%*.2f%-*s ", width_num, free_scl.number, width_pre, free_scl.prefix);
  272. }
  273. else
  274. {
  275. printf("%*zd ", col_width, total);
  276. printf("%*zd ", col_width, used);
  277. printf("%*zd ", col_width, free);
  278. }
  279. printf("%*zd%% ", tty_out ? 3 : 0, percent);
  280. printf("%s\n", mntent->mnt_dir);
  281. }
  282. endmntent(mounted);
  283. if(argc > 0) free(arg_devs);
  284. return 0;
  285. error:
  286. if(argc > 0) free(arg_devs);
  287. return 1;
  288. }