From dae8d223b69bdea7c8913d33640a7b36ef0ff755 Mon Sep 17 00:00:00 2001 From: Bernhard Voelker Date: Thu, 30 Aug 2012 14:58:22 +0200 Subject: df: add --output to select which fields to display This supports changing the order of the fields displayed, and also allows the simultaneous display of inode and block fields. src/df.c (get_dev): Factor out calling get_header to ... (main): ... here. Call print_table only if file_systems_processed. src/df.c (Displayable fields): Rename DEV_FIELD to SOURCE_FIELD. Rename TYPE_FIELD to FSTYPE_FIELD. Rename FREE_FIELD to AVAIL_FIELD. Rename MNT_FIELD to TARGET_FIELD. * src/df.c (display_field_t): Turn loose enum definition of the displayable fields into a typedef. Add the inode fields ITOTAL_FIELD, IUSED_FIELD, IAVAIL_FIELD, IPCENT_FIELD. (field_data_t): Define structure to hold the display field, the caption, the width and the alignment for each field of the above type. (field_data): Add array the values of field data for each display field. (headers, alignments, widths): Remove arrays. (columns): Add a pointer to the storage for the array of the actual output columns, i.e., fields. (ncolumns): Add counter for the current output columns. (alloc_table_row): Allocate the dynamic ncolumns value of strings. (print_table): Loop over ncolumns instead of constant NFIELDS. Rename loop variable 'field' to 'col' to avoid ambiguity with the 'field' element in the columns structure. Adjust the condition for printing the last column by comparing with the column number instead of the field name (TARGET_FIELD). Use the width and the alignment stored in the columns data. (alloc_field): Add new function to allocate a field in the columns array. (get_field_list): Add new function to fill the array of output columns for each mode. (get_header): Loop over ncolumns instead of constant NFIELDS. Rename the loop variable 'field' to 'col' to avoid ambiguity with the 'field' element in the columns structure. Remove the code for continuing the loop if the current column is the file system type and print_type is not active (which is now impossible). Store the cell in the columns store along with the new width. (get_dev): Loop over ncolumns instead of the constant NFIELDS. Rename the loop variable 'field' to 'col' to avoid ambiguity with the 'field' element in the columns structure; move the definition down to where it is used first. Add cases for the inode fields ITOTAL_FIELD, IUSED_FIELD, IAVAIL_FIELD and IPCENT_FIELD. Store the cell in the columns store along with the new width. (main): Use new get_field_list function to fill the list of output columns. * src/df.c (print_table): Instead of fputs()ing directly, apply ambsalign on the last field, too. Use the new MBA_NO_RIGHT_PAD flag for this. * src/df.c (TOTAL_OPTION): Add new enum value. (long_options): Use it for the "total" option instead of 'c'. (main): Likewise. * src/df.c (get_dev): Remove condition to copy the fstype into the FSTYPE_FIELD - based on whether print_type is non-Null. Since the introduction of get_field_list(), there are only fields added to the columns array which have to be added. * src/df.c (get_dev): Guard the summing up of the values for the grand total: only do it if we have to print the total and if the current invocation is not for processing it. * src/df.c (main): Pass a hyphen "-" for the mount point name to get_dev. (get_dev): As the mount_point is now always there, remove the condition and the else case for the TARGET_FIELD. Instead, simply copy the mount_point. All cells are now always present. Therefore, add an assertion statement if one was not. Furthermore, hide the problematic characters unconditionally. (print_table): Remove the skipping of empty cells. * tests/df/total-verify.sh: Accommodate to the new "-" in the target field of the summary line. * NEWS: Mention the change in behavior. * src/df.c (field_type_t): Add new typedef of 3 enums to distinguish between block, inode and other fields. (field_data_t): Add field_type member of the above new type. (field_data): Add default values for the above field_type, indicating whether a field contains block values, inode values or other, generic values. (field_values_t): Add this struct to store the field values, used by and factored out from get_dev to be able to define such a struct for both the inode and the block values. (get_field_values): Add this function to obtain the block values and the inode values from the file system usage, used by and factored out from get_dev. (add_to_grand_total): Add this function to sum the values of the current mount point up for the grand total, used by and factored out from get_dev. (get_dev): Move the definition of the variables fsu, buf, pct and cell down to where they are used first to give them a better scope. Factor out input_units, output_units, total, available, negate_available, available_to_root, used and negate_used into the above struct field_values_t. Factor out the mapping of the fsu values to the above variables into above function get_field_values. Factor out the summing up of the grand total values into the above function add_to_grand_total. Define block_values and inode_values of the new type and call the new get_field_values to fill them from the fsu values. Call the above function add_to_grand_total for summing up the values for the grand total. Inside the loop over all fields, define a variable 'v' to point to either the block_values or the inode_values, depending on the current field's field_type. Change the code in the cases TOTAL_FIELD/ITOTAL_FIELD, USED_FIELD/IUSED_FIELD, AVAIL_FIELD/ IAVAIL_FIELD and PCENT_FIELD/IPCENT_FIELD to use the field values where 'v' is pointing to, i.e., either the block_values or the inode_values. * src/df.c (main): Remove setting of grand_fsu.fsu_blocks in the inode_format case as this is no longer needed and would lead to wrong results once when mixed block/inode fields will be used. * src/df.c (main): Cleanup the code at the end regarding file_systems_processed to make the code clearer. * src/df.c (inode_format): Remove variable. (main): Remove initialization of the above variable. In getopts loop, directly set the header_mode to INODES_MODE instead of using the above variable. Afterwards, remove the mapping to INODES_MODE as it is already set. * src/df.c (posix_format): Move variable ... (main): ... to here. * src/df.c (print_table): Enhance the comment about 2-line format in cases where the SOURCE_FIELD exceeds 20 chars, as such behavior has been removed long ago by commit v8.10-40-g99679ff. * src/df.c (Display modes): Add OUTPUT_MODE, remove unused NMODES. (display_field_t): Remove unnecessary NFIELDS. (field_data_t): Add member 'arg' for the field name in the --output argument. Add member 'used' to remember if a field is already used in the columns array. (field_data): Add values for the above new members arg and used. (all_args_string): Add variable which represents the argument for the --output option which includes all fields. (OUTPUT_OPTION): Add enum to identify the long --output option. (long_options): Add optional-argument --output option. (alloc_field): Assert that the field is not already used. Mark the field as used. (decode_output_arg): Add function to parse the comma-separated field list passed to the --output option in order to add the appropriate fields to the columns array. (get_field_list): Add case for the new OUTPUT_MODE to add all available fields to columns in the case the --output option has been passed without any values. Use the comma-separated field list form to pass to decode_output_arg to keep the field header mapping for the OUTPUT_MODE only on one place. (main): Define format string msg_mut_excl to be used in the following checks whether the use of --output and the other option is mutually exclusive. In the getopt_long loop, add a check to the case for the -i option to issue an error message when it is used together with --output; Likewise for -T and -P. Add a new case for OUTPUT_OPTION, together with similar checks as above and eventually passing the optarg to decode_output_arg. After the getopt_long loop, consider the OUTPUT_MODE case in order not to run into -h or -P mode. * src/df.c (get_dev): Also xstrdup the dev_name, and free it afterwards to silence a valgrind warning about definitely lost memory. (main): Free the columns store to silence valgrind, guarded by the IF_LINT macro. * src/df.c (main): Pass "total" as the mount point to get_dev if the SOURCE_FIELD is not among the columns to output. * tests/df/df-output.sh: Change the test to ensure the content of the target field of the grand total line: if the source field is present, then the target should be "-", else the target field should be "target". * NEWS (Changes in behavior): Enhance the exiting NEWS entry. * doc/coreutils.texi (df invocation): Document the content of the source and target field in the grand total line. * src/df.c (main): Add another condition to the need_fs_type parameter of read_file_system_list whether the FSTYPE_FIELD is used or not. * src/df.c (get_header): Indicate the block size used, in the "size" header, when using --output without -h. * tests/df/df-output.sh: Adjust for, and add an extra test for, the new behavior. --- src/df.c | 771 ++++++++++++++++++++++++++++++++--------------- tests/df/total-verify.sh | 6 +- 2 files changed, 539 insertions(+), 238 deletions(-) diff --git a/src/df.c b/src/df.c index 83fef7719..2f4208d57 100644 --- a/src/df.c +++ b/src/df.c @@ -43,11 +43,8 @@ proper_name ("David MacKenzie"), \ proper_name ("Paul Eggert") -/* If true, show inode information. */ -static bool inode_format; - /* If true, show even file systems with zero size or - uninteresting types. */ + uninteresting types. */ static bool show_all_fs; /* If true, show only local file systems. */ @@ -63,9 +60,6 @@ static int human_output_opts; /* The units to use when printing sizes. */ static uintmax_t output_block_size; -/* If true, use the POSIX output format. */ -static bool posix_format; - /* True if a file system has been processed for output. */ static bool file_systems_processed; @@ -78,7 +72,7 @@ static bool require_sync; /* Desired exit status. */ static int exit_status; -/* A file system type to display. */ +/* A file system type to display. */ struct fs_type_list { @@ -104,7 +98,7 @@ static struct fs_type_list *fs_select_list; static struct fs_type_list *fs_exclude_list; -/* Linked list of mounted file systems. */ +/* Linked list of mounted file systems. */ static struct mount_entry *mount_list; /* If true, print file system type as well. */ @@ -113,48 +107,113 @@ static bool print_type; /* If true, print a grand total at the end. */ static bool print_grand_total; -/* Grand total data. */ +/* Grand total data. */ static struct fs_usage grand_fsu; /* Display modes. */ -enum { DEFAULT_MODE, INODES_MODE, HUMAN_MODE, POSIX_MODE, NMODES }; +enum +{ + DEFAULT_MODE, + INODES_MODE, + HUMAN_MODE, + POSIX_MODE, + OUTPUT_MODE +}; static int header_mode = DEFAULT_MODE; /* Displayable fields. */ -enum +typedef enum { - DEV_FIELD, /* file system */ - TYPE_FIELD, /* FS type */ - TOTAL_FIELD, /* blocks or inodes */ - USED_FIELD, /* ditto */ - FREE_FIELD, /* ditto */ - PCENT_FIELD, /* percent used */ - MNT_FIELD, /* mount point */ - NFIELDS -}; + SOURCE_FIELD, /* file system */ + FSTYPE_FIELD, /* FS type */ + SIZE_FIELD, /* FS size */ + USED_FIELD, /* FS size used */ + AVAIL_FIELD, /* FS size available */ + PCENT_FIELD, /* percent used */ + ITOTAL_FIELD, /* inode total */ + IUSED_FIELD, /* inodes used */ + IAVAIL_FIELD, /* inodes available */ + IPCENT_FIELD, /* inodes used in percent */ + TARGET_FIELD /* mount point */ +} display_field_t; + +/* Flag if a field contains a block, an inode or another value. */ +typedef enum +{ + BLOCK_FLD, /* Block values field */ + INODE_FLD, /* Inode values field */ + OTHER_FLD /* Neutral field, e.g. target */ +} field_type_t; -/* Header strings for the above fields in each mode. - NULL means to use the header for the default mode. */ -static const char *headers[NFIELDS][NMODES] = { -/* DEFAULT_MODE INODES_MODE HUMAN_MODE POSIX_MODE */ - { N_("Filesystem"), NULL, NULL, NULL }, - { N_("Type"), NULL, NULL, NULL }, - { N_("blocks"), N_("Inodes"), N_("Size"), NULL }, - { N_("Used"), N_("IUsed"), NULL, NULL }, - { N_("Available"), N_("IFree"), N_("Avail"), NULL }, - { N_("Use%"), N_("IUse%"), NULL, N_("Capacity") }, - { N_("Mounted on"), NULL, NULL, NULL } +/* Attributes of a display field. */ +struct field_data_t +{ + display_field_t field; + char const *arg; + field_type_t field_type; + const char *caption;/* NULL means to use the default header of this field. */ + size_t width; /* Auto adjusted (up) widths used to align columns. */ + mbs_align_t align; /* Alignment for this field. */ + bool used; }; -/* Alignments for the 3 textual and 4 numeric fields. */ -static mbs_align_t alignments[NFIELDS] = { - MBS_ALIGN_LEFT, MBS_ALIGN_LEFT, - MBS_ALIGN_RIGHT, MBS_ALIGN_RIGHT, MBS_ALIGN_RIGHT, MBS_ALIGN_RIGHT, - MBS_ALIGN_LEFT +/* Header strings, minimum width and alignment for the above fields. */ +static struct field_data_t field_data[] = { + [SOURCE_FIELD] = { SOURCE_FIELD, + "source", OTHER_FLD, N_("Filesystem"), 14, MBS_ALIGN_LEFT, false }, + + [FSTYPE_FIELD] = { FSTYPE_FIELD, + "fstype", OTHER_FLD, N_("Type"), 4, MBS_ALIGN_LEFT, false }, + + [SIZE_FIELD] = { SIZE_FIELD, + "size", BLOCK_FLD, N_("blocks"), 5, MBS_ALIGN_RIGHT, false }, + + [USED_FIELD] = { USED_FIELD, + "used", BLOCK_FLD, N_("Used"), 5, MBS_ALIGN_RIGHT, false }, + + [AVAIL_FIELD] = { AVAIL_FIELD, + "avail", BLOCK_FLD, N_("Available"), 5, MBS_ALIGN_RIGHT, false }, + + [PCENT_FIELD] = { PCENT_FIELD, + "pcent", BLOCK_FLD, N_("Use%"), 4, MBS_ALIGN_RIGHT, false }, + + [ITOTAL_FIELD] = { ITOTAL_FIELD, + "itotal", INODE_FLD, N_("Inodes"), 5, MBS_ALIGN_RIGHT, false }, + + [IUSED_FIELD] = { IUSED_FIELD, + "iused", INODE_FLD, N_("IUsed"), 5, MBS_ALIGN_RIGHT, false }, + + [IAVAIL_FIELD] = { IAVAIL_FIELD, + "iavail", INODE_FLD, N_("IFree"), 5, MBS_ALIGN_RIGHT, false }, + + [IPCENT_FIELD] = { IPCENT_FIELD, + "ipcent", INODE_FLD, N_("IUse%"), 4, MBS_ALIGN_RIGHT, false }, + + [TARGET_FIELD] = { TARGET_FIELD, + "target", OTHER_FLD, N_("Mounted on"), 0, MBS_ALIGN_LEFT, false } }; -/* Auto adjusted (up) widths used to align columns. */ -static size_t widths[NFIELDS] = { 14, 4, 5, 5, 5, 4, 0 }; +static char const *all_args_string = "source,fstype,size,used,avail,pcent," + "itotal,iused,iavail,ipcent,target"; + +/* Storage for the definition of output columns. */ +static struct field_data_t **columns; + +/* The current number of output columns. */ +static size_t ncolumns; + +/* Field values. */ +struct field_values_t +{ + uintmax_t input_units; + uintmax_t output_units; + uintmax_t total; + uintmax_t available; + bool negate_available; + uintmax_t available_to_root; + uintmax_t used; + bool negate_used; +}; /* Storage for pointers for each string (cell of table). */ static char ***table; @@ -168,6 +227,8 @@ enum { NO_SYNC_OPTION = CHAR_MAX + 1, SYNC_OPTION, + TOTAL_OPTION, + OUTPUT_OPTION, MEGABYTES_OPTION /* FIXME: remove long opt in Aug 2013 */ }; @@ -180,11 +241,12 @@ static struct option const long_options[] = {"si", no_argument, NULL, 'H'}, {"local", no_argument, NULL, 'l'}, {"megabytes", no_argument, NULL, MEGABYTES_OPTION}, /* obsolescent, */ + {"output", optional_argument, NULL, OUTPUT_OPTION}, {"portability", no_argument, NULL, 'P'}, {"print-type", no_argument, NULL, 'T'}, {"sync", no_argument, NULL, SYNC_OPTION}, {"no-sync", no_argument, NULL, NO_SYNC_OPTION}, - {"total", no_argument, NULL, 'c'}, + {"total", no_argument, NULL, TOTAL_OPTION}, {"type", required_argument, NULL, 't'}, {"exclude-type", required_argument, NULL, 'x'}, {GETOPT_HELP_OPTION_DECL}, @@ -217,7 +279,7 @@ alloc_table_row (void) { nrows++; table = xnrealloc (table, nrows, sizeof (char *)); - table[nrows-1] = xnmalloc (NFIELDS, sizeof (char *)); + table[nrows - 1] = xnmalloc (ncolumns, sizeof (char *)); } /* Output each cell in the table, accounting for the @@ -226,32 +288,33 @@ alloc_table_row (void) static void print_table (void) { - size_t field, row; + size_t row; - for (row = 0; row < nrows; row ++) + for (row = 0; row < nrows; row++) { - for (field = 0; field < NFIELDS; field++) + size_t col; + for (col = 0; col < ncolumns; col++) { - size_t width = widths[field]; - char *cell = table[row][field]; - if (!cell) /* Missing type column, or mount point etc. */ - continue; + char *cell = table[row][col]; - /* Note the DEV_FIELD used to be displayed on it's own line + /* Note the SOURCE_FIELD used to be displayed on it's own line if (!posix_format && mbswidth (cell) > 20), but that - functionality is probably more problematic than helpful. */ - if (field != 0) + functionality was probably more problematic than helpful, + hence changed in commit v8.10-40-g99679ff. */ + if (col != 0) putchar (' '); - if (field == MNT_FIELD) /* The last one. */ - fputs (cell, stdout); - else - { - cell = ambsalign (cell, &width, alignments[field], 0); - /* When ambsalign fails, output unaligned data. */ - fputs (cell ? cell : table[row][field], stdout); - free (cell); - } - IF_LINT (free (table[row][field])); + + int flags = 0; + if (col == ncolumns - 1) /* The last one. */ + flags = MBA_NO_RIGHT_PAD; + + size_t width = columns[col]->width; + cell = ambsalign (cell, &width, columns[col]->align, flags); + /* When ambsalign fails, output unaligned data. */ + fputs (cell ? cell : table[row][col], stdout); + free (cell); + + IF_LINT (free (table[row][col])); } putchar ('\n'); IF_LINT (free (table[row])); @@ -260,29 +323,180 @@ print_table (void) IF_LINT (free (table)); } -/* Obtain the appropriate header entries. */ +/* Dynamically allocate a struct field_t in COLUMNS, which + can then be accessed with standard array notation. */ static void -get_header (void) +alloc_field (int f, const char *c) { - size_t field; + ncolumns++; + columns = xnrealloc (columns, ncolumns, sizeof (struct field_data_t *)); + columns[ncolumns - 1] = &field_data[f]; + if (c != NULL) + columns[ncolumns - 1]->caption = c; - alloc_table_row (); + if (field_data[f].used) + assert (!"field used"); + + /* Mark field as used. */ + field_data[f].used = true; +} - for (field = 0; field < NFIELDS; field++) + +/* Given a string, ARG, containing a comma-separated list of arguments + to the --output option, add the appropriate fields to columns. */ +static void +decode_output_arg (char const *arg) +{ + char *arg_writable = xstrdup (arg); + char *s = arg_writable; + do { - if (field == TYPE_FIELD && !print_type) + /* find next comma */ + char *comma = strchr (s, ','); + + /* If we found a comma, put a NUL in its place and advance. */ + if (comma) + *comma++ = 0; + + /* process S. */ + display_field_t field = -1; + for (unsigned int i = 0; i < ARRAY_CARDINALITY (field_data); i++) { - table[nrows-1][field] = NULL; - continue; + if (STREQ (field_data[i].arg, s)) + { + field = i; + break; + } + } + if (field == -1) + { + error (0, 0, _("option --output: field '%s' unknown"), s); + usage (EXIT_FAILURE); + } + + if (field_data[field].used) + { + /* Prevent the fields from being used more than once. */ + error (0, 0, _("option --output: field '%s' used more than once"), + field_data[field].arg); + usage (EXIT_FAILURE); + } + + switch (field) + { + case SOURCE_FIELD: + case FSTYPE_FIELD: + case USED_FIELD: + case PCENT_FIELD: + case ITOTAL_FIELD: + case IUSED_FIELD: + case IAVAIL_FIELD: + case IPCENT_FIELD: + case TARGET_FIELD: + alloc_field (field, NULL); + break; + + case SIZE_FIELD: + alloc_field (field, N_("Size")); + break; + + case AVAIL_FIELD: + alloc_field (field, N_("Avail")); + break; + + default: + assert (!"invalid field"); + } + s = comma; + } + while (s); + + free (arg_writable); +} + +/* Get the appropriate columns for the mode. */ +static void +get_field_list (void) +{ + switch (header_mode) + { + case DEFAULT_MODE: + alloc_field (SOURCE_FIELD, NULL); + if (print_type) + alloc_field (FSTYPE_FIELD, NULL); + alloc_field (SIZE_FIELD, NULL); + alloc_field (USED_FIELD, NULL); + alloc_field (AVAIL_FIELD, NULL); + alloc_field (PCENT_FIELD, NULL); + alloc_field (TARGET_FIELD, NULL); + break; + + case HUMAN_MODE: + alloc_field (SOURCE_FIELD, NULL); + if (print_type) + alloc_field (FSTYPE_FIELD, NULL); + + alloc_field (SIZE_FIELD, N_("Size")); + alloc_field (USED_FIELD, NULL); + alloc_field (AVAIL_FIELD, N_("Avail")); + alloc_field (PCENT_FIELD, NULL); + alloc_field (TARGET_FIELD, NULL); + break; + + case INODES_MODE: + alloc_field (SOURCE_FIELD, NULL); + if (print_type) + alloc_field (FSTYPE_FIELD, NULL); + alloc_field (ITOTAL_FIELD, NULL); + alloc_field (IUSED_FIELD, NULL); + alloc_field (IAVAIL_FIELD, NULL); + alloc_field (IPCENT_FIELD, NULL); + alloc_field (TARGET_FIELD, NULL); + break; + + case POSIX_MODE: + alloc_field (SOURCE_FIELD, NULL); + if (print_type) + alloc_field (FSTYPE_FIELD, NULL); + alloc_field (SIZE_FIELD, NULL); + alloc_field (USED_FIELD, NULL); + alloc_field (AVAIL_FIELD, NULL); + alloc_field (PCENT_FIELD, N_("Capacity")); + alloc_field (TARGET_FIELD, NULL); + break; + + case OUTPUT_MODE: + if (!ncolumns) + { + /* Add all fields if --output was given without a field list. */ + decode_output_arg (all_args_string); } + break; + + default: + assert (!"invalid header_mode"); + } +} +/* Obtain the appropriate header entries. */ + +static void +get_header (void) +{ + size_t col; + + alloc_table_row (); + + for (col = 0; col < ncolumns; col++) + { char *cell = NULL; - char const *header = _(headers[field][header_mode]); - if (!header) - header = _(headers[field][DEFAULT_MODE]); + char const *header = _(columns[col]->caption); - if (header_mode == DEFAULT_MODE && field == TOTAL_FIELD) + if (columns[col]->field == SIZE_FIELD + && (header_mode == DEFAULT_MODE + || (header_mode == OUTPUT_MODE + && !(human_output_opts & human_autoscale)))) { char buf[LONGEST_HUMAN_READABLE + 1]; @@ -315,11 +529,14 @@ get_header (void) char *num = human_readable (output_block_size, buf, opts, 1, 1); + /* Reset the header back to the default in OUTPUT_MODE. */ + header = N_("blocks"); + /* TRANSLATORS: this is the "1K-blocks" header in "df" output. */ if (asprintf (&cell, _("%s-%s"), num, header) == -1) cell = NULL; } - else if (header_mode == POSIX_MODE && field == TOTAL_FIELD) + else if (header_mode == POSIX_MODE && columns[col]->field == SIZE_FIELD) { char buf[INT_BUFSIZE_BOUND (uintmax_t)]; char *num = umaxtostr (output_block_size, buf); @@ -336,9 +553,9 @@ get_header (void) hide_problematic_chars (cell); - table[nrows-1][field] = cell; + table[nrows - 1][col] = cell; - widths[field] = MAX (widths[field], mbswidth (cell, 0)); + columns[col]->width = MAX (columns[col]->width, mbswidth (cell, 0)); } } @@ -409,7 +626,7 @@ df_readable (bool negative, uintmax_t n, char *buf, #define LOG_EQ(a, b) (!(a) == !(b)) /* Add integral value while using uintmax_t for value part and separate - negation flag. It adds value of SRC and SRC_NEG to DEST and DEST_NEG. + negation flag. It adds value of SRC and SRC_NEG to DEST and DEST_NEG. The result will be in DEST and DEST_NEG. See df_readable to understand how the negation flag is used. */ static void @@ -451,6 +668,65 @@ has_uuid_suffix (char const *s) && strspn (s + len - 36, "-0123456789abcdefABCDEF") == 36); } +/* Obtain the block values BV and inode values IV + from the file system usage FSU. */ +static void +get_field_values (struct field_values_t *bv, + struct field_values_t *iv, + const struct fs_usage *fsu) +{ + /* Inode values. */ + iv->input_units = iv->output_units = 1; + iv->total = fsu->fsu_files; + iv->available = iv->available_to_root = fsu->fsu_ffree; + iv->negate_available = false; + + iv->used = UINTMAX_MAX; + iv->negate_used = false; + if (known_value (iv->total) && known_value (iv->available_to_root)) + { + iv->used = iv->total - iv->available_to_root; + iv->negate_used = (iv->total < iv->available_to_root); + } + + /* Block values. */ + bv->input_units = fsu->fsu_blocksize; + bv->output_units = output_block_size; + bv->total = fsu->fsu_blocks; + bv->available = fsu->fsu_bavail; + bv->available_to_root = fsu->fsu_bfree; + bv->negate_available = (fsu->fsu_bavail_top_bit_set + && known_value (fsu->fsu_bavail)); + + bv->used = UINTMAX_MAX; + bv->negate_used = false; + if (known_value (bv->total) && known_value (bv->available_to_root)) + { + bv->used = bv->total - bv->available_to_root; + bv->negate_used = (bv->total < bv->available_to_root); + } +} + +/* Add block and inode values to grand total. */ +static void +add_to_grand_total (struct field_values_t *bv, struct field_values_t *iv) +{ + if (known_value (iv->total)) + grand_fsu.fsu_files += iv->total; + if (known_value (iv->available)) + grand_fsu.fsu_ffree += iv->available; + + if (known_value (bv->total)) + grand_fsu.fsu_blocks += bv->input_units * bv->total; + if (known_value (bv->available_to_root)) + grand_fsu.fsu_bfree += bv->input_units * bv->available_to_root; + if (known_value (bv->available)) + add_uint_with_neg_flag (&grand_fsu.fsu_bavail, + &grand_fsu.fsu_bavail_top_bit_set, + bv->input_units * bv->available, + bv->negate_available); +} + /* Obtain a space listing for the disk device with absolute file name DISK. If MOUNT_POINT is non-NULL, it is the name of the root of the file system on DISK. @@ -473,20 +749,6 @@ get_dev (char const *disk, char const *mount_point, const struct fs_usage *force_fsu, bool process_all) { - struct fs_usage fsu; - char buf[LONGEST_HUMAN_READABLE + 2]; - uintmax_t input_units; - uintmax_t output_units; - uintmax_t total; - uintmax_t available; - bool negate_available; - uintmax_t available_to_root; - uintmax_t used; - bool negate_used; - double pct = -1; - char* cell; - size_t field; - if (me_remote && show_local_fs) return; @@ -503,6 +765,7 @@ get_dev (char const *disk, char const *mount_point, if (!stat_file) stat_file = mount_point ? mount_point : disk; + struct fs_usage fsu; if (force_fsu) fsu = *force_fsu; else if (get_fs_usage (stat_file, disk, &fsu)) @@ -515,12 +778,8 @@ get_dev (char const *disk, char const *mount_point, if (fsu.fsu_blocks == 0 && !show_all_fs && !show_listed_fs) return; - if (! file_systems_processed) - { - if (! force_fsu) - file_systems_processed = true; - get_header (); - } + if (! force_fsu) + file_systems_processed = true; alloc_table_row (); @@ -547,148 +806,142 @@ get_dev (char const *disk, char const *mount_point, if (! fstype) fstype = "-"; /* unknown */ - if (inode_format) - { - input_units = output_units = 1; - total = fsu.fsu_files; - available = fsu.fsu_ffree; - negate_available = false; - available_to_root = available; - - if (known_value (total)) - grand_fsu.fsu_files += total; - if (known_value (available)) - grand_fsu.fsu_ffree += available; - } - else - { - input_units = fsu.fsu_blocksize; - output_units = output_block_size; - total = fsu.fsu_blocks; - available = fsu.fsu_bavail; - negate_available = (fsu.fsu_bavail_top_bit_set - && known_value (available)); - available_to_root = fsu.fsu_bfree; - - if (known_value (total)) - grand_fsu.fsu_blocks += input_units * total; - if (known_value (available_to_root)) - grand_fsu.fsu_bfree += input_units * available_to_root; - if (known_value (available)) - add_uint_with_neg_flag (&grand_fsu.fsu_bavail, - &grand_fsu.fsu_bavail_top_bit_set, - input_units * available, negate_available); - } + struct field_values_t block_values; + struct field_values_t inode_values; + get_field_values (&block_values, &inode_values, &fsu); - used = UINTMAX_MAX; - negate_used = false; - if (known_value (total) && known_value (available_to_root)) - { - used = total - available_to_root; - negate_used = (total < available_to_root); - } + /* Add to grand total unless processing grand total line. */ + if (print_grand_total && ! force_fsu) + add_to_grand_total (&block_values, &inode_values); - for (field = 0; field < NFIELDS; field++) + size_t col; + for (col = 0; col < ncolumns; col++) { - switch (field) + char buf[LONGEST_HUMAN_READABLE + 2]; + char *cell; + + struct field_values_t *v; + switch (columns[col]->field_type) { - case DEV_FIELD: - cell = dev_name; + case BLOCK_FLD: + v = &block_values; + break; + case INODE_FLD: + v = &inode_values; + break; + case OTHER_FLD: + v = NULL; + break; + default: + assert (!"bad field_type"); + } + + switch (columns[col]->field) + { + case SOURCE_FIELD: + cell = xstrdup (dev_name); break; - case TYPE_FIELD: - cell = print_type ? xstrdup (fstype) : NULL; + case FSTYPE_FIELD: + cell = xstrdup (fstype); break; - case TOTAL_FIELD: - cell = xstrdup (df_readable (false, total, buf, - input_units, output_units)); + case SIZE_FIELD: + case ITOTAL_FIELD: + cell = xstrdup (df_readable (false, v->total, buf, + v->input_units, v->output_units)); break; + case USED_FIELD: - cell = xstrdup (df_readable (negate_used, used, buf, - input_units, output_units)); + case IUSED_FIELD: + cell = xstrdup (df_readable (v->negate_used, v->used, buf, + v->input_units, v->output_units)); break; - case FREE_FIELD: - cell = xstrdup (df_readable (negate_available, available, buf, - input_units, output_units)); + + case AVAIL_FIELD: + case IAVAIL_FIELD: + cell = xstrdup (df_readable (v->negate_available, v->available, buf, + v->input_units, v->output_units)); break; case PCENT_FIELD: - if (! known_value (used) || ! known_value (available)) - ; - else if (!negate_used - && used <= TYPE_MAXIMUM (uintmax_t) / 100 - && used + available != 0 - && (used + available < used) == negate_available) - { - uintmax_t u100 = used * 100; - uintmax_t nonroot_total = used + available; - pct = u100 / nonroot_total + (u100 % nonroot_total != 0); - } - else - { - /* The calculation cannot be done easily with integer - arithmetic. Fall back on floating point. This can suffer - from minor rounding errors, but doing it exactly requires - multiple precision arithmetic, and it's not worth the - aggravation. */ - double u = negate_used ? - (double) - used : used; - double a = negate_available ? - (double) - available : available; - double nonroot_total = u + a; - if (nonroot_total) - { - long int lipct = pct = u * 100 / nonroot_total; - double ipct = lipct; - - /* Like 'pct = ceil (dpct);', but avoid ceil so that - the math library needn't be linked. */ - if (ipct - 1 < pct && pct <= ipct + 1) - pct = ipct + (ipct < pct); - } - } + case IPCENT_FIELD: + { + double pct = -1; + if (! known_value (v->used) || ! known_value (v->available)) + ; + else if (!v->negate_used + && v->used <= TYPE_MAXIMUM (uintmax_t) / 100 + && v->used + v->available != 0 + && (v->used + v->available < v->used) + == v->negate_available) + { + uintmax_t u100 = v->used * 100; + uintmax_t nonroot_total = v->used + v->available; + pct = u100 / nonroot_total + (u100 % nonroot_total != 0); + } + else + { + /* The calculation cannot be done easily with integer + arithmetic. Fall back on floating point. This can suffer + from minor rounding errors, but doing it exactly requires + multiple precision arithmetic, and it's not worth the + aggravation. */ + double u = v->negate_used ? - (double) - v->used : v->used; + double a = v->negate_available + ? - (double) - v->available : v->available; + double nonroot_total = u + a; + if (nonroot_total) + { + long int lipct = pct = u * 100 / nonroot_total; + double ipct = lipct; - if (0 <= pct) - { - if (asprintf (&cell, "%.0f%%", pct) == -1) - cell = NULL; - } - else - cell = strdup ("-"); + /* Like 'pct = ceil (dpct);', but avoid ceil so that + the math library needn't be linked. */ + if (ipct - 1 < pct && pct <= ipct + 1) + pct = ipct + (ipct < pct); + } + } - if (!cell) - xalloc_die (); + if (0 <= pct) + { + if (asprintf (&cell, "%.0f%%", pct) == -1) + cell = NULL; + } + else + cell = strdup ("-"); - break; + if (!cell) + xalloc_die (); - case MNT_FIELD: - if (mount_point) - { + break; + } + + case TARGET_FIELD: #ifdef HIDE_AUTOMOUNT_PREFIX - /* Don't print the first directory name in MOUNT_POINT if it's an - artifact of an automounter. This is a bit too aggressive to be - the default. */ - if (STRNCMP_LIT (mount_point, "/auto/") == 0) - mount_point += 5; - else if (STRNCMP_LIT (mount_point, "/tmp_mnt/") == 0) - mount_point += 8; + /* Don't print the first directory name in MOUNT_POINT if it's an + artifact of an automounter. This is a bit too aggressive to be + the default. */ + if (STRNCMP_LIT (mount_point, "/auto/") == 0) + mount_point += 5; + else if (STRNCMP_LIT (mount_point, "/tmp_mnt/") == 0) + mount_point += 8; #endif - cell = xstrdup (mount_point); - } - else - cell = NULL; + cell = xstrdup (mount_point); break; default: assert (!"unhandled field"); } - if (cell) - { - hide_problematic_chars (cell); - widths[field] = MAX (widths[field], mbswidth (cell, 0)); - } - table[nrows-1][field] = cell; + if (!cell) + assert (!"empty cell"); + + hide_problematic_chars (cell); + columns[col]->width = MAX (columns[col]->width, mbswidth (cell, 0)); + table[nrows - 1][col] = cell; } + free (dev_name); } /* If DISK corresponds to a mount point, show its usage @@ -772,7 +1025,7 @@ get_point (const char *point, const struct stat *statp) exit_status = EXIT_FAILURE; } - /* So we won't try and fail repeatedly. */ + /* So we won't try and fail repeatedly. */ me->me_dev = (dev_t) -2; } } @@ -824,7 +1077,7 @@ get_entry (char const *name, struct stat const *statp) } /* Show all mounted file systems, except perhaps those that are of - an unselected type or are empty. */ + an unselected type or are empty. */ static void get_all_entries (void) @@ -836,7 +1089,7 @@ get_all_entries (void) me->me_dummy, me->me_remote, NULL, true); } -/* Add FSTYPE to the list of file system types to display. */ +/* Add FSTYPE to the list of file system types to display. */ static void add_fs_type (const char *fstype) @@ -849,7 +1102,7 @@ add_fs_type (const char *fstype) fs_select_list = fsp; } -/* Add FSTYPE to the list of file system types to be omitted. */ +/* Add FSTYPE to the list of file system types to be omitted. */ static void add_excluded_fs_type (const char *fstype) @@ -927,17 +1180,20 @@ main (int argc, char **argv) fs_select_list = NULL; fs_exclude_list = NULL; - inode_format = false; show_all_fs = false; show_listed_fs = false; human_output_opts = -1; print_type = false; file_systems_processed = false; - posix_format = false; exit_status = EXIT_SUCCESS; print_grand_total = false; grand_fsu.fsu_blocksize = 1; + /* If true, use the POSIX output format. */ + bool posix_format = false; + + const char *msg_mut_excl = _("options %s and %s are mutually exclusive"); + while (true) { int oi = -1; @@ -960,7 +1216,12 @@ main (int argc, char **argv) } break; case 'i': - inode_format = true; + if (header_mode == OUTPUT_MODE) + { + error (0, 0, msg_mut_excl, "-i", "--output"); + usage (EXIT_FAILURE); + } + header_mode = INODES_MODE; break; case 'h': human_output_opts = human_autoscale | human_SI | human_base_1024; @@ -989,9 +1250,19 @@ main (int argc, char **argv) output_block_size = 1024 * 1024; break; case 'T': + if (header_mode == OUTPUT_MODE) + { + error (0, 0, msg_mut_excl, "-T", "--output"); + usage (EXIT_FAILURE); + } print_type = true; break; case 'P': + if (header_mode == OUTPUT_MODE) + { + error (0, 0, msg_mut_excl, "-P", "--output"); + usage (EXIT_FAILURE); + } posix_format = true; break; case SYNC_OPTION: @@ -1007,14 +1278,35 @@ main (int argc, char **argv) add_fs_type (optarg); break; - case 'v': /* For SysV compatibility. */ + case 'v': /* For SysV compatibility. */ /* ignore */ break; case 'x': add_excluded_fs_type (optarg); break; - case 'c': + case OUTPUT_OPTION: + if (header_mode == INODES_MODE) + { + error (0, 0, msg_mut_excl, "-i", "--output"); + usage (EXIT_FAILURE); + } + if (posix_format && header_mode == DEFAULT_MODE) + { + error (0, 0, msg_mut_excl, "-P", "--output"); + usage (EXIT_FAILURE); + } + if (print_type) + { + error (0, 0, msg_mut_excl, "-T", "--output"); + usage (EXIT_FAILURE); + } + header_mode = OUTPUT_MODE; + if (optarg) + decode_output_arg (optarg); + break; + + case TOTAL_OPTION: print_grand_total = true; break; @@ -1038,8 +1330,8 @@ main (int argc, char **argv) &human_output_opts, &output_block_size); } - if (inode_format) - header_mode = INODES_MODE; + if (header_mode == INODES_MODE || header_mode == OUTPUT_MODE) + ; else if (human_output_opts & human_autoscale) header_mode = HUMAN_MODE; else if (posix_format) @@ -1097,6 +1389,7 @@ main (int argc, char **argv) read_file_system_list ((fs_select_list != NULL || fs_exclude_list != NULL || print_type + || field_data[FSTYPE_FIELD].used || show_local_fs)); if (mount_list == NULL) @@ -1104,7 +1397,7 @@ main (int argc, char **argv) /* Couldn't read the table of mounted file systems. Fail if df was invoked with no file name arguments, or when either of -a, -l, -t or -x is used with file name - arguments. Otherwise, merely give a warning and proceed. */ + arguments. Otherwise, merely give a warning and proceed. */ int status = 0; if ( ! (optind < argc) || (show_all_fs @@ -1122,11 +1415,14 @@ main (int argc, char **argv) if (require_sync) sync (); + get_field_list (); + get_header (); + if (optind < argc) { int i; - /* Display explicitly requested empty file systems. */ + /* Display explicitly requested empty file systems. */ show_listed_fs = true; for (i = optind; i < argc; ++i) @@ -1136,19 +1432,24 @@ main (int argc, char **argv) else get_all_entries (); - if (print_grand_total && file_systems_processed) + if (file_systems_processed) { - if (inode_format) - grand_fsu.fsu_blocks = 1; - get_dev ("total", NULL, NULL, NULL, false, false, &grand_fsu, false); - } + if (print_grand_total) + get_dev ("total", + (field_data[SOURCE_FIELD].used ? "-" : "total"), + NULL, NULL, false, false, &grand_fsu, false); - print_table (); + print_table (); + } + else + { + /* Print the "no FS processed" diagnostic only if there was no preceding + diagnostic, e.g., if all have been excluded. */ + if (exit_status == EXIT_SUCCESS) + error (EXIT_FAILURE, 0, _("no file systems processed")); + } - /* Print the "no FS processed" diagnostic only if there was no preceding - diagnostic, e.g., if all have been excluded. */ - if (exit_status == EXIT_SUCCESS && ! file_systems_processed) - error (EXIT_FAILURE, 0, _("no file systems processed")); + IF_LINT (free (columns)); exit (exit_status); } diff --git a/tests/df/total-verify.sh b/tests/df/total-verify.sh index 45c838ecf..18d215f11 100755 --- a/tests/df/total-verify.sh +++ b/tests/df/total-verify.sh @@ -31,10 +31,10 @@ while (<>) # Recognize df output lines like these: # /dev/sdc1 0 0 0 - /c # tmpfs 1536000 12965 1523035 1% /tmp - # total 5285932 787409 4498523 15% - /^(.*?) +(-?\d+|-) +(-?\d+|-) +(-?\d+|-) +(?:- |[0-9]+%)(.*)$/ + # total 5285932 787409 4498523 15% - + /^(.*?) +(-?\d+|-) +(-?\d+|-) +(-?\d+|-) +(?:- |[0-9]+%) (.*)$/ or die "$0: invalid input line\n: $_"; - if ($1 eq 'total' && $5 eq '') + if ($1 eq 'total' && $5 eq '-') { $total == $2 or die "$total != $2"; $used == $3 or die "$used != $3"; -- cgit v1.2.3-70-g09d2