df: add --output to select which fields to display
authorBernhard Voelker <mail@bernhard-voelker.de>
Thu, 30 Aug 2012 12:58:22 +0000 (14:58 +0200)
committerPádraig Brady <P@draigBrady.com>
Thu, 8 Nov 2012 16:11:48 +0000 (16:11 +0000)
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
tests/df/total-verify.sh

index 83fef77..2f4208d 100644 (file)
--- a/src/df.c
+++ b/src/df.c
   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);
 }
index 45c838e..18d215f 100755 (executable)
@@ -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";