From bd14553a0f6e60e35312bbddf0f1d317ec430916 Mon Sep 17 00:00:00 2001 From: Jonas 'Sortie' Termansen Date: Sun, 31 May 2015 20:39:04 +0200 Subject: [PATCH] Add df(1). --- doc/user-guide | 1 + utils/.gitignore | 1 + utils/Makefile | 1 + utils/df.cpp | 778 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 781 insertions(+) create mode 100644 utils/df.cpp diff --git a/doc/user-guide b/doc/user-guide index a420c8ae..c9ce311d 100644 --- a/doc/user-guide +++ b/doc/user-guide @@ -199,6 +199,7 @@ Sortix comes with a number of home-made programs. Here is an overview: * `column` - format lines nicely in columns * `cp` - copy file * `date` - display current time and date +* `df` - report file system disk space usage. * `dirname` - strip last component from file name * `dispd` - non-existent display server * `du` - report file and directory disk usage diff --git a/utils/.gitignore b/utils/.gitignore index c107a12e..78de33cb 100644 --- a/utils/.gitignore +++ b/utils/.gitignore @@ -11,6 +11,7 @@ column command-not-found cp date +df dirname du echo diff --git a/utils/Makefile b/utils/Makefile index 43bff6b3..ca559f29 100644 --- a/utils/Makefile +++ b/utils/Makefile @@ -23,6 +23,7 @@ column \ command-not-found \ cp \ date \ +df \ dirname \ du \ echo \ diff --git a/utils/df.cpp b/utils/df.cpp new file mode 100644 index 00000000..2c78c90e --- /dev/null +++ b/utils/df.cpp @@ -0,0 +1,778 @@ +/******************************************************************************* + + Copyright(C) Jonas 'Sortie' Termansen 2015, 2016. + + This program is free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + more details. + + You should have received a copy of the GNU General Public License along with + this program. If not, see . + + df.cpp + Report free disk space. + +*******************************************************************************/ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +__attribute__((format(printf, 1, 2))) +static char* print_string(const char* format, ...) +{ + char* ret = NULL; + va_list ap; + va_start(ap, format); + if ( vasprintf(&ret, format, ap) < 0 ) + ret = NULL; + va_end(ap); + return ret; +} + +static char* adirname(const char* path) +{ + char* clone = strdup(path); + if ( !clone ) + return NULL; + char* dirnamed = dirname(clone); + char* result = strdup(dirnamed); + free(clone); + return result; +} + +static char* atcgetblob(int fd, const char* name, size_t* size_ptr) +{ + ssize_t size = tcgetblob(fd, name, NULL, 0); + if ( size < 0 ) + return NULL; + char* result = (char*) malloc((size_t) size + 1); + if ( !result ) + return NULL; + ssize_t second_size = tcgetblob(fd, name, result, (size_t) size); + if ( second_size != size ) + return free(result), (char*) NULL; + result[(size_t) size] = '\0'; + if ( size_ptr ) + *size_ptr = (size_t) size; + return result; +} + +static size_t string_display_length(const char* str) +{ + size_t display_length = 0; + mbstate_t ps; + memset(&ps, 0, sizeof(ps)); + while ( true ) + { + wchar_t wc; + size_t amount = mbrtowc(&wc, str, SIZE_MAX, &ps); + if ( amount == 0 ) + break; + if ( amount == (size_t) -1 || amount == (size_t) -2 ) + { + display_length++; + str++; + memset(&ps, 0, sizeof(ps)); + continue; + } + int width = wcwidth(wc); + if ( width < 0 ) + width = 0; + if ( SIZE_MAX - display_length < (size_t) width ) + display_length = SIZE_MAX; + else + display_length += (size_t) width; + str += amount; + } + return display_length; +} + +static int open_canonical_directory(const char* path, char** out_path) +{ + char* canon = canonicalize_file_name(path); + if ( !canon ) + return -1; + int fd = open(path, O_RDONLY | O_DIRECTORY); + if ( fd < 0 && errno == ENOTDIR ) + { + char* parent_path = adirname(path); + if ( parent_path ) + { + if ( 0 <= (fd = open(parent_path, O_RDONLY | O_DIRECTORY)) ) + { + free(canon); + canon = parent_path; + parent_path = NULL; + } + free(parent_path); + } + } + if ( fd < 0 ) + { + free(canon); + return -1; + } + return *out_path = canon, fd; +} + +static int open_mount_directory(int fd, const char* path, char** out_path) +{ + char* result_path = strdup(path); + if ( !result_path ) + return -1; + int result_fd = openat(fd, ".", O_RDONLY | O_DIRECTORY); + if ( result_fd < 0 ) + return free(result_path), -1; + struct stat result_st; + if ( fstat(fd, &result_st) < 0 ) + return close(result_fd), free(result_path), -1; + while ( true ) + { + char* parent_path = adirname(result_path); + if ( !parent_path ) + return close(result_fd), free(result_path), -1; + if ( !strcmp(parent_path, result_path) ) + { + free(parent_path); + return *out_path = result_path, result_fd; + } + int parent_fd = openat(result_fd, "..", O_RDONLY | O_DIRECTORY); + if ( parent_fd < 0 ) + return free(parent_path), close(result_fd), free(result_path), -1; + struct stat parent_st; + if ( fstat(parent_fd, &parent_st) < 0 ) + return close(parent_fd), free(parent_path), close(result_fd), + free(result_path), -1; + if ( parent_st.st_dev != result_st.st_dev ) + { + close(parent_fd); + free(parent_path); + return *out_path = result_path, result_fd; + } + close(result_fd); + result_fd = parent_fd; + free(result_path); + result_path = parent_path; + result_st = parent_st; + } +} + +struct df +{ + char* filesystem; + char* type; + char* mount; + off_t blockdata_total; + off_t blockdata_free; + off_t blockdata_used; + fsfilcnt_t inodes_total; + fsfilcnt_t inodes_free; + fsfilcnt_t inodes_used; +}; + +static void dftotal(struct df* total, const struct df* dfs, size_t dfs_count) +{ + memset(total, 0, sizeof(*total)); + total->filesystem = strdup("total"); + total->type = strdup("-"); + total->mount = strdup("-"); + for ( size_t i = 0; i < dfs_count; i++ ) + { + total->blockdata_total += dfs[i].blockdata_total; + total->blockdata_free += dfs[i].blockdata_free; + total->blockdata_used += dfs[i].blockdata_used; + total->inodes_total += dfs[i].inodes_total; + total->inodes_free += dfs[i].inodes_free; + total->inodes_used += dfs[i].inodes_used; + } +} + +static bool df(const char* path, struct df* buf) +{ + memset(buf, 0, sizeof(*buf)); + char* canon_fd_path; + int canon_fd = open_canonical_directory(path, &canon_fd_path); + if ( canon_fd < 0 ) + { + error(0, errno, "%s", path); + return false; + } + char* mount_fd_path; + int mount_fd = + open_mount_directory(canon_fd, canon_fd_path, &mount_fd_path); + if ( mount_fd < 0 ) + error(0, errno, "finding mountpoint: %s", canon_fd_path); + free(canon_fd_path); + close(canon_fd); + if ( mount_fd < 0 ) + return false; + struct statvfs stvfs; + if ( fstatvfs(mount_fd, &stvfs) < 0 ) + { + error(0, errno, "statvfs: %s", mount_fd_path); + return free(mount_fd_path), close(mount_fd), false; + } + if ( !(buf->filesystem = atcgetblob(mount_fd, "device-path", NULL))) + buf->filesystem = strdup("none"); + if ( !(buf->type = atcgetblob(mount_fd, "filesystem-type", NULL)) ) + buf->filesystem = strdup("unknown"); + buf->mount = mount_fd_path; + buf->blockdata_total = stvfs.f_bsize * stvfs.f_blocks; + buf->blockdata_free = stvfs.f_bsize * stvfs.f_bfree; + buf->blockdata_used = buf->blockdata_total - buf->blockdata_free; + buf->inodes_total = stvfs.f_files; + buf->inodes_free = stvfs.f_ffree; + buf->inodes_used = buf->inodes_total - buf->inodes_free; + close(mount_fd); + return true; +} + +static void dffree(struct df* buf) +{ + free(buf->filesystem); + free(buf->type); + free(buf->mount); +} + +enum magnitude_format +{ + MAGFORMAT_DEFAULT, + MAGFORMAT_512B, + MAGFORMAT_1024B, + MAGFORMAT_HUMAN_READABLE, + MAGFORMAT_SI, +}; + +static char* format_bytes_amount(uintmax_t num_bytes, int base) +{ + uintmax_t value = num_bytes; + uintmax_t value_fraction = 0; + uintmax_t exponent = base; + char prefixes[] = { '\0', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y' }; + if ( base == 1000 ) + prefixes[1] = 'k'; + size_t num_prefixes = sizeof(prefixes) / sizeof(prefixes[0]); + size_t prefix_index = 0; + while ( exponent <= value && prefix_index + 1 < num_prefixes) + { + value_fraction = value % exponent; + value /= exponent; + prefix_index++; + } + char prefix_str[] = { prefixes[prefix_index], '\0' }; + int fix = base == 1024 ? 1 : 0; + char value_fraction_char = '0' + (value_fraction / (base / 10 + fix)) % 10; + char* result; + if ( prefix_index == 0 && + asprintf(&result, "%ju%s", value, prefix_str) < 0 ) + return NULL; + if ( prefix_index != 0 && + asprintf(&result, "%ju.%c%s", value, value_fraction_char, + prefix_str) < 0 ) + return NULL; + return result; +} + +static char* format_block_magnitude(off_t value, + enum magnitude_format magformat) +{ + switch ( magformat ) + { + case MAGFORMAT_DEFAULT: __builtin_unreachable(); + case MAGFORMAT_512B: return print_string("%ji", (intmax_t) (value / 512)); + case MAGFORMAT_1024B: return print_string("%ji", (intmax_t) (value / 1024)); + case MAGFORMAT_HUMAN_READABLE: return format_bytes_amount(value, 1024); + case MAGFORMAT_SI: return format_bytes_amount(value, 1000); + } + __builtin_unreachable(); +} + +static char* format_inodes_magnitude(fsfilcnt_t value, + enum magnitude_format magformat) +{ + switch ( magformat ) + { + case MAGFORMAT_DEFAULT: __builtin_unreachable(); + case MAGFORMAT_512B: return print_string("%ju", (uintmax_t) value); + case MAGFORMAT_1024B: return print_string("%ju", (uintmax_t) value); + case MAGFORMAT_HUMAN_READABLE: return format_bytes_amount(value, 1024); + case MAGFORMAT_SI: return format_bytes_amount(value, 1000); + } + __builtin_unreachable(); +} + +struct display_format +{ + enum magnitude_format magformat; + bool inodes; + bool portability; + bool print_type; +}; + +enum column +{ + COLUMN_FILESYSTEM, + COLUMN_TYPE, + COLUMN_BLOCKS_TOTAL, + COLUMN_BLOCKS_USED, + COLUMN_BLOCKS_AVAILABLE, + COLUMN_BLOCKS_USE_PERCENT, + COLUMN_INODES_TOTAL, + COLUMN_INODES_USED, + COLUMN_INODES_AVAILABLE, + COLUMN_INODES_USE_PERCENT, + COLUMN_MOUNT, + COLUMN_MAX_COLUMN, +}; + +static const char* label_column(enum column column, + struct display_format* format) +{ + switch ( column ) + { + case COLUMN_FILESYSTEM: return "Filesystem"; + case COLUMN_TYPE: return "Type"; + case COLUMN_BLOCKS_TOTAL: + switch ( format->magformat ) + { + case MAGFORMAT_512B: + return format->portability ? "512-blocks" : "512B-blocks"; + case MAGFORMAT_1024B: + return format->portability ? "1024-blocks" : "1K-blocks"; + default: + return "Size"; + } + case COLUMN_BLOCKS_USED: return "Used"; + case COLUMN_BLOCKS_AVAILABLE: + return format->magformat == MAGFORMAT_HUMAN_READABLE || + format->magformat == MAGFORMAT_SI ? "Avail" : "Available"; + case COLUMN_BLOCKS_USE_PERCENT: + return format->portability ? "Capacity" : "Use%"; + case COLUMN_INODES_TOTAL: return "Inodes"; + case COLUMN_INODES_USED: return "IUsed"; + case COLUMN_INODES_AVAILABLE: return "IFree"; + case COLUMN_INODES_USE_PERCENT: return "IUse%"; + case COLUMN_MOUNT: return "Mounted on"; + case COLUMN_MAX_COLUMN: __builtin_unreachable(); + } + __builtin_unreachable(); +} + +static char* format_percent(uintmax_t a, uintmax_t b) +{ + if ( !b ) + return print_string("-"); + int percent = 100 * ((double) a / (double) b); + if ( 100 < percent ) + percent = 100; + return print_string("%i%%", percent); +} + +static char* column_entry(struct df* buf, + enum column column, + struct display_format* format) +{ + switch ( column ) + { + case COLUMN_FILESYSTEM: + return strdup(buf->filesystem ? buf->filesystem : "none"); + case COLUMN_TYPE: + return strdup(buf->type ? buf->type : "unknown"); + case COLUMN_BLOCKS_TOTAL: + return format_block_magnitude(buf->blockdata_total, format->magformat); + case COLUMN_BLOCKS_USED: + return format_block_magnitude(buf->blockdata_used, format->magformat); + case COLUMN_BLOCKS_AVAILABLE: + return format_block_magnitude(buf->blockdata_free, format->magformat); + case COLUMN_BLOCKS_USE_PERCENT: + return format_percent(buf->blockdata_used, buf->blockdata_total); + case COLUMN_INODES_TOTAL: + return format_inodes_magnitude(buf->inodes_total, format->magformat); + case COLUMN_INODES_USED: + return format_inodes_magnitude(buf->inodes_used, format->magformat); + case COLUMN_INODES_AVAILABLE: + return format_inodes_magnitude(buf->inodes_free, format->magformat); + case COLUMN_INODES_USE_PERCENT: + return format_percent(buf->inodes_used, buf->inodes_total); + case COLUMN_MOUNT: + return strdup(buf->mount ? buf->mount : "unknown"); + case COLUMN_MAX_COLUMN: __builtin_unreachable(); + } + __builtin_unreachable(); +} + +static void display_string(const char* string, size_t* position) +{ + fputs(string, stdout); + *position += string_display_length(string); +} + +static void display_indent(size_t indention, size_t* position) +{ + while ( *position < indention ) + { + putchar(' '); + (*position)++; + } +} + +static void display(struct df* dfs, + size_t dfs_count, + struct display_format* format) +{ + size_t good_ones = 0; + for ( size_t i = 0; i < dfs_count; i++ ) + if ( dfs[i].mount ) + good_ones++; + if ( good_ones == 0 ) + return; + bool enable_column[COLUMN_MAX_COLUMN]; + enable_column[COLUMN_FILESYSTEM] = true; + enable_column[COLUMN_TYPE] = format->print_type; + enable_column[COLUMN_BLOCKS_TOTAL] = !format->inodes; + enable_column[COLUMN_BLOCKS_USED] = !format->inodes; + enable_column[COLUMN_BLOCKS_AVAILABLE] = !format->inodes; + enable_column[COLUMN_BLOCKS_USE_PERCENT] = !format->inodes; + enable_column[COLUMN_INODES_TOTAL] = format->inodes; + enable_column[COLUMN_INODES_USED] = format->inodes; + enable_column[COLUMN_INODES_AVAILABLE] = format->inodes; + enable_column[COLUMN_INODES_USE_PERCENT] = format->inodes; + enable_column[COLUMN_MOUNT] = true; + size_t column_width[COLUMN_MAX_COLUMN]; + for ( int c = 0; c < COLUMN_MAX_COLUMN; c++ ) + { + enum column column = (enum column) c; + if ( !enable_column[column] ) + continue; + size_t width = string_display_length(label_column(column, format)); + column_width[column] = width; + } + for ( size_t i = 0; i < dfs_count; i++ ) + { + if ( !dfs[i].mount ) + continue; + for ( int c = 0; c < COLUMN_MAX_COLUMN; c++ ) + { + enum column column = (enum column) c; + if ( !enable_column[column] ) + continue; + char* entry = column_entry(&dfs[i], column, format); + if ( !entry ) + error(1, errno, "malloc"); + size_t width = string_display_length(entry); + free(entry); + if ( column_width[column] < width ) + column_width[column] = width; + } + } + size_t position = 0; + size_t indention = 0; + for ( int c = 0; c < COLUMN_MAX_COLUMN; c++ ) + { + enum column column = (enum column) c; + if ( !enable_column[column] ) + continue; + display_indent(indention, &position); + display_string(label_column(column, format), &position); + indention += column_width[column] + 2; + } + printf("\n"); + for ( size_t i = 0; i < dfs_count; i++ ) + { + if ( !dfs[i].mount ) + continue; + position = 0; + indention = 0; + for ( int c = 0; c < COLUMN_MAX_COLUMN; c++ ) + { + enum column column = (enum column) c; + if ( !enable_column[column] ) + continue; + char* entry = column_entry(&dfs[i], column, format); + if ( !entry ) + error(1, errno, "malloc"); + display_indent(indention, &position); + display_string(entry, &position); + indention += column_width[column] + 2; + free(entry); + } + printf("\n"); + } +} + +static void compact_arguments(int* argc, char*** argv) +{ + for ( int i = 0; i < *argc; i++ ) + { + while ( i < *argc && !(*argv)[i] ) + { + for ( int n = i; n < *argc; n++ ) + (*argv)[n] = (*argv)[n+1]; + (*argc)--; + } + } +} + +static void add_mount(char*** mounts_ptr, + size_t* mounts_used_ptr, + size_t* mounts_length_ptr, + const char* path) +{ + if ( *mounts_used_ptr == *mounts_length_ptr ) + { + size_t new_length = 2 * *mounts_length_ptr; + if ( new_length == 0 ) + new_length = 8; + char** new_mounts = + (char**) reallocarray(*mounts_ptr, new_length, sizeof(char**)); + if ( !new_mounts ) + error(1, errno, "malloc"); + *mounts_ptr = new_mounts; + *mounts_length_ptr = new_length; + } + char* copy = strdup(path); + if ( !copy ) + error(1, errno, "malloc"); + (*mounts_ptr)[(*mounts_used_ptr)++] = copy; +} + +static bool is_mount(const char* path) +{ + int fd = open(path, O_RDONLY | O_DIRECTORY | O_NOFOLLOW); + if ( fd < 0 ) + { + if ( errno == ENOENT ) + return false; + error(0, errno, "%s", path); + return false; + } + struct stat fdst; + if ( fstat(fd, &fdst) < 0 ) + { + error(0, errno, "%s", path); + close(fd); + return false; + } + struct stat dotdotst; + if ( fstatat(fd, "..", &dotdotst, 0) < 0 ) + { + error(0, errno, "%s/..", path); + close(fd); + return false; + } + close(fd); + if ( fdst.st_dev == dotdotst.st_dev ) + return fdst.st_ino == dotdotst.st_ino; /* root directory */ + return true; +} + +static void get_mounts(char*** mounts, size_t* mounts_used) +{ + *mounts = NULL; + *mounts_used = 0; + size_t mounts_length = 0; + if ( !setfsent() ) + { + if ( errno != ENOENT ) + error(1, errno, "setfstab"); + add_mount(mounts, mounts_used, &mounts_length, "/"); + return; + } + struct fstab* fs; + while ( (errno = 0, fs = getfsent()) ) + { + if ( is_mount(fs->fs_file) ) + add_mount(mounts, mounts_used, &mounts_length, fs->fs_file); + } + if ( errno ) + error(1, errno, "getfsent"); + endfsent(); +} + +static int strcmp_indirect(const void* a_ptr, const void* b_ptr) +{ + const char* a = *(const char* const*) a_ptr; + const char* b = *(const char* const*) b_ptr; + return strcmp(a, b); +} + +static void help(FILE* fp, const char* argv0) +{ + fprintf(fp, "Usage: %s [OPTION]... [FILE]...\n", argv0); + fprintf(fp, "Write sorted concatenation of all FILE(s) to standard output.\n"); + fprintf(fp, "\n"); + fprintf(fp, "Mandatory arguments to long options are mandatory for short options too.\n"); + fprintf(fp, " -a, --all include dummy file systems\n"); +#if 0 + fprintf(fp, " -B, --block-size=SIZE scale sizes by SIZE before printing them. E.g.,\n"); + fprintf(fp, " '-BM' prints sizes in units of 1,048,576 bytes.\n"); + fprintf(fp, " See SIZE format below.\n"); +#endif + fprintf(fp, " --total produce a grand total\n"); + fprintf(fp, " -h, --human-readable print sizes in human readable format (e.g., 1K 234M 2G)\n"); + fprintf(fp, " -H, --si likewise, but use powers of 1000 not 1024\n"); + fprintf(fp, " -i, --inodes list inode information instead of block usage\n"); + fprintf(fp, " -k like --block-size=1K\n"); +#if 0 + fprintf(fp, " -l, --local limit listing to local file systems\n"); + fprintf(fp, " --no-sync do not invoke sync before getting usage info (default)\n"); + fprintf(fp, " --output[=FIELD_LIST] use the output format defined by FIELD_LIST,\n"); + fprintf(fp, " or print all fields if FIELD_LIST is omitted.\n"); +#endif + fprintf(fp, " -P, --portability use the POSIX output format\n"); +#if 0 + fprintf(fp, " --sync invoke sync before getting usage info\n"); + fprintf(fp, " -t, --type=TYPE limit listing to file systems of type TYPE\n"); +#endif + fprintf(fp, " -T, --print-type print file system type\n"); +#if 0 + fprintf(fp, " -x, --exclude-type=TYPE limit listing to file systems not of type TYPE\n"); +#endif + fprintf(fp, " --help display this help and exit\n"); + fprintf(fp, " --version output version information and exit\n"); +#if 0 + fprintf(fp, "\n"); + fprintf(fp, "Display values are in units of the first available SIZE from --block-size,\n"); + fprintf(fp, "and the DF_BLOCK_SIZE, BLOCK_SIZE and BLOCKSIZE environment variables.\n"); + fprintf(fp, "Otherwise, units default to 1024 bytes (or 512 if POSIXLY_CORRECT is set).\n"); + fprintf(fp, "\n"); + fprintf(fp, "SIZE is an integer and optional unit (example: 10M is 10*1024*1024). Units\n"); + fprintf(fp, "are K, M, G, T, P, E, Z, Y (powers of 1024) or KB, MB, ... (powers of 1000).\n"); + fprintf(fp, "\n"); + fprintf(fp, "FIELD_LIST is a comma-separated list of columns to be included. Valid\n"); + fprintf(fp, "field names are: 'source', 'fstype', 'itotal', 'iused', 'iavail', 'ipcent',\n"); + fprintf(fp, "'size', 'used', 'avail', 'pcent' and 'target' (see info page).\n"); +#endif +} + +static void version(FILE* fp, const char* argv0) +{ + fprintf(fp, "%s (Sortix) %s\n", argv0, VERSIONSTR); + fprintf(fp, "License GPLv3+: GNU GPL version 3 or later .\n"); + fprintf(fp, "This is free software: you are free to change and redistribute it.\n"); + fprintf(fp, "There is NO WARRANTY, to the extent permitted by law.\n"); +} + +int main(int argc, char* argv[]) +{ + setlocale(LC_ALL, ""); + + bool all = false; + struct display_format format; + memset(&format, 0, sizeof(format)); + format.magformat = MAGFORMAT_DEFAULT; + bool total = false; + + const char* argv0 = argv[0]; + for ( int i = 1; i < argc; i++ ) + { + const char* arg = argv[i]; + if ( arg[0] != '-' || !arg[1] ) + continue; + argv[i] = NULL; + if ( !strcmp(arg, "--") ) + break; + if ( arg[1] != '-' ) + { + while ( char c = *++arg ) switch ( c ) + { + case 'a': all = true; break; + case 'h': format.magformat = MAGFORMAT_HUMAN_READABLE; break; + case 'H': format.magformat = MAGFORMAT_SI; break; + case 'i': format.inodes = true; break; + case 'k': format.magformat = MAGFORMAT_1024B; break; + case 'P': format.portability = true; break; + case 'T': format.print_type = true; break; + default: + fprintf(stderr, "%s: unknown option -- '%c'\n", argv0, c); + help(stderr, argv0); + exit(1); + } + } + else if ( !strcmp(arg, "--help") ) + help(stdout, argv0), exit(0); + else if ( !strcmp(arg, "--version") ) + version(stdout, argv0), exit(0); + else if ( !strcmp(arg, "--all") ) + all = true; + else if ( !strcmp(arg, "--human-readable") ) + format.magformat = MAGFORMAT_HUMAN_READABLE; + else if ( !strcmp(arg, "--inodes") ) + format.inodes = true; + else if ( !strcmp(arg, "--portability") ) + format.portability = true; + else if ( !strcmp(arg, "--print-type") ) + format.print_type = true; + else if ( !strcmp(arg, "--si") ) + format.magformat = MAGFORMAT_SI; + else if ( !strcmp(arg, "--total") ) + total = true; + else + { + fprintf(stderr, "%s: unknown option: %s\n", argv0, arg); + help(stderr, argv0); + exit(1); + } + } + + compact_arguments(&argc, &argv); + + if ( format.magformat == MAGFORMAT_DEFAULT ) + format.magformat = format.portability || format.inodes ? + MAGFORMAT_512B : MAGFORMAT_HUMAN_READABLE; + + bool success = true; + + char** paths = argv + 1; + size_t paths_count = argc - 1; + + if ( argc == 1 ) + { + (void) all; + get_mounts(&paths, &paths_count); + qsort(paths, paths_count, sizeof(char*), strcmp_indirect); + } + + struct df* dfs = (struct df*) reallocarray(NULL, paths_count + 1, sizeof(struct df)); + if ( !dfs ) + error(1, errno, "malloc"); + for ( size_t i = 0; i < paths_count; i++ ) + { + if ( !df(paths[i], &dfs[i]) ) + success = false; + } + if ( total ) + dftotal(&dfs[paths_count], dfs, paths_count); + display(dfs, paths_count + (total ? 1 : 0), &format); + for ( size_t i = 0; i < paths_count; i++ ) + dffree(&dfs[i]); + if ( total ) + dffree(&dfs[paths_count]); + free(dfs); + + if ( ferror(stdout) || fflush(stdout) == EOF ) + success = false; + + return success ? 0 : 1; +}