From d402ecc9c1c1657cd5b213758968efd880364831 Mon Sep 17 00:00:00 2001 From: Kamil Dudka Date: Wed, 3 Sep 2008 10:33:06 +0200 Subject: [PATCH] df: new option: --total to produce grand total * src/df.c (add_uint_with_neg_flag): New function to add two integral values with separate negation flag. (show_dev): New parameter force_fsu to display numbers directly. Collect summary statistics on each printed device. (usage): Mention new option --total in --help. (main): Initialize summary on program start. Handle new option --total. * tests/df/total: Dummy test case for new --total option. * tests/df/total-awk: Better test case for new --total option (requires awk). * doc/coreutils.texi: Mention new parameter --total. * NEWS: Mention the change. * TODO: Removed completed task. --- NEWS | 3 ++ TODO | 2 - doc/coreutils.texi | 7 ++++ src/df.c | 81 ++++++++++++++++++++++++++++++++++++++++++++++++---- tests/Makefile.am | 2 + tests/check.mk | 1 + tests/df/total | 42 +++++++++++++++++++++++++++ tests/df/total-awk | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 208 insertions(+), 8 deletions(-) create mode 100755 tests/df/total create mode 100755 tests/df/total-awk diff --git a/NEWS b/NEWS index 4979dd5..97e6313 100644 --- a/NEWS +++ b/NEWS @@ -42,6 +42,9 @@ GNU coreutils NEWS -*- outline -*- sort accepts a new option --version-sort (-V, --sort=version), specifying that ordering is to be based on strverscmp(3). + df accepts a new option --total, which produces a grand total of all + arguments after all arguments have been processed. + ** Bug fixes chcon --verbose now prints a newline after each message diff --git a/TODO b/TODO index c7b095c..c964933 100644 --- a/TODO +++ b/TODO @@ -66,8 +66,6 @@ Should printf '\0123' print "\n3"? printf: consider adapting builtins/printf.def from bash -df: add `--total' option, suggested here http://bugs.debian.org/186007 - tail: don't use xlseek; it *exits*. Instead, maybe use a macro and return nonzero. diff --git a/doc/coreutils.texi b/doc/coreutils.texi index 3a04176..be59ae9 100644 --- a/doc/coreutils.texi +++ b/doc/coreutils.texi @@ -9692,6 +9692,13 @@ pseudo-file-systems, such as automounter entries. Scale sizes by @var{size} before printing them (@pxref{Block size}). For example, @option{-BG} prints sizes in units of 1,073,741,824 bytes. address@hidden --total address@hidden --total address@hidden grand total of disk size, usage and available space +Print a grand total of all arguments after all arguments have +been processed. This can be used to find out the total disk size, usage +and available space of all listed devices. + @optHumanReadable @item -H diff --git a/src/df.c b/src/df.c index 0769a1e..0bb3b1e 100644 --- a/src/df.c +++ b/src/df.c @@ -108,6 +108,12 @@ static struct mount_entry *mount_list; /* If true, print file system type as well. */ static bool print_type; +/* If true, print a grand total at the end. */ +static bool print_grand_total; + +/* Grand total data. */ +static struct fs_usage grand_fsu; + /* For long options that have no equivalent short option, use a non-character as a pseudo short option, starting with CHAR_MAX + 1. */ enum @@ -129,6 +135,7 @@ static struct option const long_options[] = {"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'}, {"type", required_argument, NULL, 't'}, {"exclude-type", required_argument, NULL, 'x'}, {GETOPT_HELP_OPTION_DECL}, @@ -247,6 +254,41 @@ df_readable (bool negative, uintmax_t n, char *buf, } } +/* Logical equivalence */ +#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. + The result will be in DEST and DEST_NEG. See df_readable to understand + how the negation flag is used. */ +static void +add_uint_with_neg_flag (uintmax_t *dest, bool *dest_neg, + uintmax_t src, bool src_neg) +{ + if (LOG_EQ (*dest_neg, src_neg)) + { + *dest += src; + return; + } + + if (*dest_neg) + *dest = -*dest; + + if (src_neg) + src = -src; + + if (src < *dest) + *dest -= src; + else + { + *dest = src - *dest; + *dest_neg = src_neg; + } + + if (*dest_neg) + *dest = -*dest; +} + /* Display 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. @@ -263,7 +305,8 @@ df_readable (bool negative, uintmax_t n, char *buf, static void show_dev (char const *disk, char const *mount_point, char const *stat_file, char const *fstype, - bool me_dummy, bool me_remote) + bool me_dummy, bool me_remote, + const struct fs_usage *force_fsu) { struct fs_usage fsu; char buf[3][LONGEST_HUMAN_READABLE + 2]; @@ -296,7 +339,9 @@ show_dev (char const *disk, char const *mount_point, if (!stat_file) stat_file = mount_point ? mount_point : disk; - if (get_fs_usage (stat_file, disk, &fsu)) + if (force_fsu) + fsu = *force_fsu; + else if (get_fs_usage (stat_file, disk, &fsu)) { error (0, errno, "%s", quote (stat_file)); exit_status = EXIT_FAILURE; @@ -347,6 +392,9 @@ show_dev (char const *disk, char const *mount_point, available = fsu.fsu_ffree; negate_available = false; available_to_root = available; + + grand_fsu.fsu_files += total; + grand_fsu.fsu_ffree += available; } else { @@ -373,6 +421,12 @@ show_dev (char const *disk, char const *mount_point, negate_available = (fsu.fsu_bavail_top_bit_set & (available != UINTMAX_MAX)); available_to_root = fsu.fsu_bfree; + + grand_fsu.fsu_blocks += input_units * total; + grand_fsu.fsu_bfree += input_units * available_to_root; + add_uint_with_neg_flag (&grand_fsu.fsu_bavail, + &grand_fsu.fsu_bavail_top_bit_set, + input_units * available, negate_available); } used = UINTMAX_MAX; @@ -550,7 +604,7 @@ show_disk (char const *disk) { show_dev (best_match->me_devname, best_match->me_mountdir, NULL, best_match->me_type, best_match->me_dummy, - best_match->me_remote); + best_match->me_remote, NULL); return true; } @@ -654,7 +708,8 @@ show_point (const char *point, const struct stat *statp) if (best_match) show_dev (best_match->me_devname, best_match->me_mountdir, point, - best_match->me_type, best_match->me_dummy, best_match->me_remote); + best_match->me_type, best_match->me_dummy, best_match->me_remote, + NULL); else { /* We couldn't find the mount entry corresponding to POINT. Go ahead and @@ -665,7 +720,7 @@ show_point (const char *point, const struct stat *statp) char *mp = find_mount_point (point, statp); if (mp) { - show_dev (NULL, mp, NULL, NULL, false, false); + show_dev (NULL, mp, NULL, NULL, false, false, NULL); free (mp); } } @@ -694,7 +749,7 @@ show_all_entries (void) for (me = mount_list; me; me = me->me_next) show_dev (me->me_devname, me->me_mountdir, NULL, me->me_type, - me->me_dummy, me->me_remote); + me->me_dummy, me->me_remote, NULL); } /* Add FSTYPE to the list of file system types to display. */ @@ -743,6 +798,7 @@ Mandatory arguments to long options are mandatory for short options too.\n\ fputs (_("\ -a, --all include dummy file systems\n\ -B, --block-size=SIZE use SIZE-byte blocks\n\ + --total produce a grand total\n\ -h, --human-readable print sizes in human readable format (e.g., 1K 234M 2G)\n\ -H, --si likewise, but use powers of 1000 not 1024\n\ "), stdout); @@ -794,6 +850,8 @@ main (int argc, char **argv) file_systems_processed = false; posix_format = false; exit_status = EXIT_SUCCESS; + print_grand_total = false; + grand_fsu.fsu_blocksize = 1; for (;;) { @@ -864,6 +922,10 @@ main (int argc, char **argv) add_excluded_fs_type (optarg); break; + case 'c': + print_grand_total = true; + break; + case_GETOPT_HELP_CHAR; case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); @@ -959,6 +1021,13 @@ main (int argc, char **argv) else show_all_entries (); + if (print_grand_total) + { + if (inode_format) + grand_fsu.fsu_blocks = 1; + show_dev ("total", NULL, NULL, NULL, false, false, &grand_fsu); + } + if (! file_systems_processed) error (EXIT_FAILURE, 0, _("no file systems processed")); diff --git a/tests/Makefile.am b/tests/Makefile.am index 5a57ca9..59d1e48 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -284,6 +284,8 @@ TESTS = \ dd/skip-seek \ dd/skip-seek2 \ dd/unblock-sync \ + df/total \ + df/total-awk \ du/2g \ du/8gb \ du/basic \ diff --git a/tests/check.mk b/tests/check.mk index 4fca283..03b89dc 100644 --- a/tests/check.mk +++ b/tests/check.mk @@ -79,6 +79,7 @@ TESTS_ENVIRONMENT = \ top_srcdir='$(top_srcdir)' \ CONFIG_HEADER='$(abs_top_builddir)/lib/config.h' \ CU_TEST_NAME=`basename '$(abs_srcdir)'`,$$tst \ + AWK='$(AWK)' \ EGREP='$(EGREP)' \ EXEEXT='$(EXEEXT)' \ MAKE=$(MAKE) \ diff --git a/tests/df/total b/tests/df/total new file mode 100755 index 0000000..186bf8d --- /dev/null +++ b/tests/df/total @@ -0,0 +1,42 @@ +#!/bin/sh +# Ensure "df --total" produces /^total.../ line + +# Copyright (C) 2008 Free Software Foundation, Inc. + +# 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 . + +if test "$VERBOSE" = yes; then + set -x + ls --version +fi + +. $srcdir/test-lib.sh + +fail=0 + +# Don't let a different umask perturb the results. +umask 22 + +RE_TOTAL='^total( +(-?[0-9]+|-)){3} +-?[0-9]+%$' + +df > tmp || fail=1 +$EGREP "$RE_TOTAL" tmp && fail=1 + +df -i > tmp || fail=1 +$EGREP "$RE_TOTAL" tmp && fail=1 + +df --total | $EGREP "$RE_TOTAL" || fail=1 +df -i --total | $EGREP "$RE_TOTAL" || fail=1 + +(exit $fail); exit $fail diff --git a/tests/df/total-awk b/tests/df/total-awk new file mode 100755 index 0000000..632e945 --- /dev/null +++ b/tests/df/total-awk @@ -0,0 +1,78 @@ +#!/bin/sh +# Ensure "df --total" computes well summary statistics + +# Copyright (C) 2008 Free Software Foundation, Inc. + +# 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 . + +if test "$VERBOSE" = yes; then + set -x + ls --version +fi + +. $srcdir/test-lib.sh + +fail=0 + +# Don't let a different umask perturb the results. +umask 22 + +echo ' +BEGIN { + total = 0 + used = 0 + available = 0 +} +{ + if (NR==1 || $0==$1 || $0~/^total +(-?[0-9]+|-) +(-?[0-9]+|-) +(-?[0-9]+|-) +-?[0-9]+%$/) + next + if ($1~/^[0-9]/) + { + total += $1 + used += $2 + available += $3 + } + else + { + total += $2 + used += $3 + available += $4 + } +} +END { + print total + print used + print available +} +' > compute_sum.awk || fail=1 + +echo ' +/^total +(-?[0-9]+|-) +(-?[0-9]+|-) +(-?[0-9]+|-) +-?[0-9]+%$/ { + print $2; + print $3; + print $4 +} +' > parse_total.awk || fail=1 + +df --total > tmp || fail=1 +$AWK -f compute_sum.awk tmp > out1 || fail=1 +$AWK -f parse_total.awk tmp > out2 || fail=1 +compare out1 out2 || fail=1 + +df -i --total > tmp || fail=1 +$AWK -f compute_sum.awk tmp > out1 || fail=1 +$AWK -f parse_total.awk tmp > out2 || fail=1 +compare out1 out2 || fail=1 + +(exit $fail); exit $fail -- 1.5.4.1