df.c (9458B)
- // utils-std: Collection of commonly available Unix tools
- // SPDX-FileCopyrightText: 2017 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
- // SPDX-License-Identifier: MPL-2.0
- #define _POSIX_C_SOURCE 200809L
- #define _DEFAULT_SOURCE // mntent in glibc 2.19+
- #include "../lib/humanize.h"
- #include <assert.h>
- #include <ctype.h> // iscntrl, isspace
- #include <errno.h> // errno
- #include <mntent.h>
- #include <stdbool.h>
- #include <stdio.h> // printf
- #include <stdlib.h> // abort, exit
- #include <string.h> // strerror
- #include <sys/stat.h> // stat, dev_t
- #include <sys/statvfs.h>
- #include <unistd.h> // getopt
- // Grabbed from OpenRC rc-functions.sh
- // clang-format off
- static const char *net_fs_list[] = {
- "afs", "ceph", "cifs", "coda", "davfs", "fuse", "fuse.glusterfs",
- "fuse.sshfs", "gfs", "glusterfs", "lustre", "ncpfs", "nfs", "nfs4",
- "ocfs2", "shfs", "smbfs"
- };
- // clang-format on
- blksize_t forced_bsize = 0;
- const char *argv0 = "df";
- // Replaces control characters and whitespaces with '?' in-place (no allocation)
- static void
- static_escape(char *str)
- {
- for(size_t i = 0; i < strlen(str); i++)
- if(iscntrl(str[i]) || isspace(str[i])) str[i] = '?';
- }
- int
- main(int argc, char *argv[])
- {
- bool opt_P = false, opt_h = false, opt_a = false, opt_T = false, opt_l = false, opt_i = false;
- int fs_col_width = 20;
- size_t excluded_count = 0;
- char *excluded[4096];
- size_t only_count = 0;
- char *only[4096];
- int c = -1;
- while((c = getopt(argc, argv, ":ahilPkTt:x:")) != -1)
- {
- switch(c)
- {
- case 'a':
- opt_a = true;
- break;
- case 'P':
- if(forced_bsize == 0) forced_bsize = 512;
- opt_P = true;
- break;
- case 'k':
- forced_bsize = 1024;
- break;
- case 'h':
- opt_h = true;
- fs_col_width = 30;
- break;
- case 'i':
- opt_i = true;
- break;
- case 'l':
- opt_l = true;
- break;
- case 'T':
- opt_T = true;
- break;
- case 't':
- only[only_count++] = optarg;
- break;
- case 'x':
- excluded[excluded_count++] = optarg;
- break;
- case ':':
- fprintf(stderr, "%s: error: Missing operand for option: '-%c'\n", argv0, optopt);
- return 1;
- case '?':
- fprintf(stderr, "%s: error: Unrecognised option: '-%c'\n", argv0, optopt);
- return 1;
- }
- }
- if(opt_P && opt_T)
- {
- fprintf(stderr, "%s: error: Options -P and -T are incompatible\n", argv0);
- return 1;
- }
- if(opt_P) opt_h = false;
- argc -= optind;
- argv += optind;
- int args_left = argc != 0 ? argc : 1;
- bool tty_out = isatty(1);
- int col_width = tty_out ? 10 : 0;
- if(!tty_out)
- {
- errno = 0;
- fs_col_width = 0;
- }
- dev_t *arg_devs = NULL;
- if(argc > 0)
- {
- arg_devs = calloc(argc, sizeof(dev_t));
- if(arg_devs == NULL)
- {
- fprintf(stderr, "%s: error: Failed to allocate arg_devs array: %s\n", argv0, strerror(errno));
- return 1;
- }
- }
- assert(errno == 0);
- for(int i = 0; i < argc; i++)
- {
- struct stat file_stats;
- if(stat(argv[i], &file_stats) != 0)
- {
- fprintf(stderr,
- "%s: error: Failed getting status for file '%s': %s\n",
- argv0,
- argv[i],
- strerror(errno));
- goto error;
- }
- arg_devs[i] = file_stats.st_dev;
- }
- // Begin: Print header
- printf("%-*s ", fs_col_width, "Filesystem");
- if(opt_T) printf("%*s ", col_width, "Type");
- if(opt_i)
- {
- printf("%*s %*s %*s ", col_width, "Inodes", col_width, "Iused", col_width, "IFree");
- if(opt_P)
- printf("IUse%% Mounted on\n");
- else
- printf("IUse%% Mountpoint\n");
- }
- else
- {
- if(forced_bsize != 0)
- printf("%zd-blocks ", forced_bsize);
- else
- printf("%*s ", col_width, "Total");
- printf("%*s %*s ", col_width, "Used", col_width, "Available");
- if(opt_P)
- printf("Capacity Mounted on\n");
- else
- printf("Use%% Mountpoint\n");
- }
- // End: Print header
- assert(errno == 0);
- FILE *mounted = setmntent(MOUNTED, "r");
- if(mounted == NULL)
- {
- fprintf(stderr,
- "%s: error: Failed opening setmntent(\"" MOUNTED "\", \"r\"): %s\n",
- argv0,
- strerror(errno));
- goto error;
- }
- // FIXME: Fix maximum number of mount entries / devices
- dev_t devices[4096];
- size_t devices_found = 0;
- // Even with argc>0 we still need to go over mntent for the filesystem mountpoint and type
- while(args_left > 0)
- {
- struct mntent *mntent = getmntent(mounted);
- if(mntent == NULL) break;
- if(excluded_count > 0)
- {
- bool exclude = false;
- for(size_t i = 0; i < excluded_count; i++)
- if(strcmp(excluded[i], mntent->mnt_type) == 0)
- {
- exclude = true;
- break;
- }
- if(exclude) continue;
- }
- if(only_count > 0)
- {
- bool include = false;
- for(size_t i = 0; i < only_count; i++)
- if(strcmp(only[i], mntent->mnt_type) == 0)
- {
- include = true;
- break;
- }
- if(!include) continue;
- }
- if(opt_l)
- {
- bool remote = false;
- for(size_t i = 0; i < sizeof(net_fs_list) / sizeof(char *); i++)
- if(strcmp(net_fs_list[i], mntent->mnt_type) == 0)
- {
- remote = true;
- break;
- }
- if(remote) continue;
- }
- assert(errno == 0);
- struct stat file_stats;
- if(!opt_a || argc > 0)
- {
- if(stat(mntent->mnt_dir, &file_stats) != 0)
- {
- fprintf(stderr,
- "%s: warning: Failed getting status for file '%s': %s\n",
- argv0,
- mntent->mnt_dir,
- strerror(errno));
- errno = 0;
- }
- }
- if(argc > 0)
- {
- bool found = false;
- for(int i = 0; i < argc; i++)
- if(arg_devs[i] == file_stats.st_dev)
- {
- found = true;
- break;
- }
- if(!found) continue;
- args_left--;
- }
- if(!opt_a)
- {
- bool dupe = false;
- for(size_t i = 0; i < devices_found; i++)
- if(devices[i] == file_stats.st_dev)
- {
- dupe = true;
- break;
- }
- if(dupe) continue;
- if(devices_found >= 4096)
- fprintf(stderr,
- "%s: warning: Reached maximum amount of devices which can be deduplicated\n",
- argv0);
- devices[devices_found++] = file_stats.st_dev;
- }
- // Note: musl prior to 1.2.5 has broken getmntent when octal sequences and carriage return is used
- // https://git.musl-libc.org/cgit/musl/commit/src/misc/mntent.c?id=f314e133929b6379eccc632bef32eaebb66a7335
- // https://git.musl-libc.org/cgit/musl/commit/src/misc/mntent.c?id=ee1d39bc1573c1ae49ee6b658938b56bbef95a6c
- assert(errno == 0);
- struct statvfs stats;
- if(statvfs(mntent->mnt_dir, &stats) != 0)
- {
- fprintf(stderr,
- "%s: warning: Failed getting statistics for filesystem '%s': %s\n",
- argv0,
- mntent->mnt_dir,
- strerror(errno));
- errno = 0;
- static_escape(mntent->mnt_fsname);
- static_escape(mntent->mnt_dir);
- if(opt_a)
- {
- printf("%-*s ", fs_col_width, mntent->mnt_fsname);
- if(opt_T) printf("%*s ", col_width, mntent->mnt_type);
- printf("%*s ", col_width, "-"); // total
- printf("%*s ", col_width, "-"); // used
- printf("%*s ", col_width, "-"); // free
- printf("%*s ", tty_out ? 3 : 0, "-"); // percent
- printf("%s\n", mntent->mnt_dir);
- }
- continue;
- }
- // Skip null filesystems
- if(!opt_a && stats.f_blocks == 0) continue;
- // Needs to be done after calling statvfs(3) and stat(3)
- static_escape(mntent->mnt_fsname);
- static_escape(mntent->mnt_dir);
- printf("%-*s ", fs_col_width, mntent->mnt_fsname);
- if(opt_T) printf("%*s ", col_width, mntent->mnt_type);
- if(opt_i)
- {
- fsfilcnt_t used = stats.f_files - stats.f_ffree;
- fsfilcnt_t percent = (used / stats.f_files) * 100;
- if(opt_h && !opt_P)
- {
- struct si_scale total_scl = dtosi(stats.f_files, false);
- struct si_scale used_scl = dtosi(used, false);
- struct si_scale free_scl = dtosi(stats.f_ffree, false);
- int width_num = tty_out ? col_width - 1 : 0;
- int width_pre = tty_out ? 1 : 0;
- printf("%*.2f%-*s ", width_num, total_scl.number, width_pre, total_scl.prefix);
- printf("%*.2f%-*s ", width_num, used_scl.number, width_pre, used_scl.prefix);
- printf("%*.2f%-*s ", width_num, free_scl.number, width_pre, free_scl.prefix);
- }
- else
- {
- printf("%*zd ", col_width, stats.f_files);
- printf("%*zd ", col_width, used);
- printf("%*zd ", col_width, stats.f_ffree);
- }
- printf("%*zd%% ", tty_out ? 4 : 0, percent);
- printf("%s\n", mntent->mnt_dir);
- continue;
- }
- off_t percent = 0;
- off_t total = stats.f_frsize * (stats.f_blocks != 0 ? stats.f_blocks : 1);
- off_t free = stats.f_bfree * (stats.f_bsize != 0 ? stats.f_bsize : 1);
- off_t used = total - free;
- if(used + free)
- {
- percent = (used * 100) / (used + free);
- if(used * 100 != percent * (used + free)) percent++;
- }
- if(forced_bsize != 0)
- {
- total /= forced_bsize;
- free /= forced_bsize;
- used /= forced_bsize;
- }
- if(opt_h && !opt_P)
- {
- struct si_scale total_scl = dtosi(total, true);
- struct si_scale used_scl = dtosi(used, true);
- struct si_scale free_scl = dtosi(free, true);
- int width_num = tty_out ? col_width - 3 : 0;
- int width_pre = tty_out ? 3 : 0;
- printf("%*.2f%-*s ", width_num, total_scl.number, width_pre, total_scl.prefix);
- printf("%*.2f%-*s ", width_num, used_scl.number, width_pre, used_scl.prefix);
- printf("%*.2f%-*s ", width_num, free_scl.number, width_pre, free_scl.prefix);
- }
- else
- {
- printf("%*zd ", col_width, total);
- printf("%*zd ", col_width, used);
- printf("%*zd ", col_width, free);
- }
- printf("%*zd%% ", tty_out ? 3 : 0, percent);
- printf("%s\n", mntent->mnt_dir);
- }
- endmntent(mounted);
- if(argc > 0) free(arg_devs);
- return 0;
- error:
- if(argc > 0) free(arg_devs);
- return 1;
- }