Browse Source

Implement statistics display in checkpoint actions.

* NEWS: Update.
* configure.ac: Version 1.27.90
* gnulib.modules: Add fprintftime.
* doc/tar.texi: Document the "totals" action and new format specifiers
for echo and ttyout checkpoint actions.
* src/buffer.c (compute_duration): Return computed value.
(print_stats): Don't print trailing newline.  Return number of
characters output.
(format_total_stats): New function.
(print_total_stats): Rewrite via format_total_stats.
* src/checkpoint.c (checkpoint_opcode) <cop_totals>: New opcode.
(checkpoint_compile_action): Handle cop_totals.
(expand_checkpoint_string): Remove.
(format_checkpoint_string): New function to be used instead of
expand_checkpoint_string.  All callers updated.
* src/common.h (TF_READ,TF_WRITE)
(TF_DELETED): New constants.
(format_total_stats,print_total_stats): New protos.
Sergey Poznyakoff 11 years ago
parent
commit
f0a1f78196
7 changed files with 295 additions and 88 deletions
  1. 20 1
      NEWS
  2. 1 1
      configure.ac
  3. 63 4
      doc/tar.texi
  4. 1 0
      gnulib.modules
  5. 58 24
      src/buffer.c
  6. 145 57
      src/checkpoint.c
  7. 7 1
      src/common.h

+ 20 - 1
NEWS

@@ -1,6 +1,25 @@
-GNU tar NEWS - User visible changes. 2013-11-17
+GNU tar NEWS - User visible changes. 2014-01-21
 Please send GNU tar bug reports to <bug-tar@gnu.org>
 Please send GNU tar bug reports to <bug-tar@gnu.org>
 
 
+
+version 1.27.90 (Git)
+
+* New checkpoint action: totals
+
+The --checkpoint-action=totals option instructs tar to output the
+total number of bytes transferred at each checkpoint.
+
+* Extended checkpoint format specification.
+
+New conversion specifiers are implemented:
+
+  %d  -  output number of seconds since tar started
+  %T  -  output I/O totals
+  %{FMT}t - output current local time using FMT as strftime(3) format
+            If {FMT} is omitted, use %c
+  %{N}*   - pad output with spaces to the Nth column, or to the 
+            current screen width, if {N} is not given.
+
 
 
 version 1.27.1 - Sergey Poznyakoff, 2013-11-17
 version 1.27.1 - Sergey Poznyakoff, 2013-11-17
 
 

+ 1 - 1
configure.ac

@@ -17,7 +17,7 @@
 # You should have received a copy of the GNU General Public License
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 
-AC_INIT([GNU tar], [1.27.1], [bug-tar@gnu.org])
+AC_INIT([GNU tar], [1.27.90], [bug-tar@gnu.org])
 AC_CONFIG_SRCDIR([src/tar.c])
 AC_CONFIG_SRCDIR([src/tar.c])
 AC_CONFIG_AUX_DIR([build-aux])
 AC_CONFIG_AUX_DIR([build-aux])
 AC_CONFIG_HEADERS([config.h])
 AC_CONFIG_HEADERS([config.h])

+ 63 - 4
doc/tar.texi

@@ -3961,10 +3961,10 @@ e.g.:
 @end smallexample
 @end smallexample
 
 
 The @samp{%s} and @samp{%u} in the above example are
 The @samp{%s} and @samp{%u} in the above example are
-@dfn{meta-characters}.  The @samp{%s} meta-character is replaced with
+@dfn{format specifiers}.  The @samp{%s} specifier is replaced with
 the @dfn{type} of the checkpoint: @samp{write} or
 the @dfn{type} of the checkpoint: @samp{write} or
 @samp{read} (or a corresponding translated version in locales other
 @samp{read} (or a corresponding translated version in locales other
-than @acronym{POSIX}).  The @samp{%u} meta-character is replaced with
+than @acronym{POSIX}).  The @samp{%u} specifier is replaced with
 the ordinal number of the checkpoint.  Thus, the above example could
 the ordinal number of the checkpoint.  Thus, the above example could
 produce the following output when used with the @option{--create}
 produce the following output when used with the @option{--create}
 option:
 option:
@@ -3975,7 +3975,46 @@ tar: Hit write checkpoint #20
 tar: Hit write checkpoint #30
 tar: Hit write checkpoint #30
 @end smallexample
 @end smallexample
 
 
-Aside from meta-character expansion, the message string is subject to
+The complete list of available format specifiers follows:
+
+@table @samp
+@item %s
+Print type of the checkpoint (@samp{write} or @samp{read}).
+
+@item %u
+Print number of the checkpoint.
+
+@item %T
+Print number of bytes transferred so far and approximate transfer
+speed.  The number is preceded by @samp{W:}, when writing and by
+@samp{R:} when reading.  If @command{tar} is performing delete
+operation (@pxref{delete}), three numbers are printed: number of bytes
+read, written and deleted, each of them prefixed by @samp{R:},
+@samp{W:} and @samp{D:} correspondingy.  For example:
+
+@example
+$ @kbd{tar --delete -f f.tar --checkpoint-action=echo="#%u: %T" main.c}
+tar: #1: R: 0 (0B, ?/s),W: 0 (0B, ?/s),D: 0
+tar: #2: R: 10240 (10KiB, 19MiB/s),W: 0 (0B, 0B/s),D: 10240
+@end example
+
+@noindent
+See also the @samp{totals} action, described below.
+
+@item %@{@var{fmt}@}t
+Output current local time using @var{fmt} as format for @command{strftime}
+(@pxref{strftime, strftime,,strftime(3), strftime(3) man page}).  The
+@samp{@{@var{fmt}@}} part is optional.  If not present, the default
+format is @samp{%c}, i.e. the preferred date and time representation
+for the current locale.
+
+@item %@{@var{n}@}*
+Pad output with spaces to the @var{n}th column.  If the
+@samp{@{@var{n}@}} part is omitted, the current screen width
+is assumed.
+@end table
+
+Aside from format expansion, the message string is subject to
 @dfn{unquoting}, during which the backslash @dfn{escape sequences} are
 @dfn{unquoting}, during which the backslash @dfn{escape sequences} are
 replaced with their corresponding @acronym{ASCII} characters
 replaced with their corresponding @acronym{ASCII} characters
 (@pxref{escape sequences}).  E.g. the following action will produce an
 (@pxref{escape sequences}).  E.g. the following action will produce an
@@ -4002,9 +4041,23 @@ following action will print the checkpoint message at the same screen
 line, overwriting any previous message:
 line, overwriting any previous message:
 
 
 @smallexample
 @smallexample
---checkpoint-action="ttyout=\rHit %s checkpoint #%u"
+--checkpoint-action="ttyout=Hit %s checkpoint #%u%*\r"
 @end smallexample
 @end smallexample
 
 
+@noindent
+Notice the use of @samp{%*} specifier to clear out any eventual
+remains of the prior output line.  As as more complex example,
+consider this:
+
+@smallexample
+--checkpoint-action=ttyout='%@{%Y-%m-%d %H:%M:%S@}t (%d sec): #%u, %T%*\r'
+@end smallexample
+
+@noindent
+This prints the current local time, number of seconds expired since
+tar was started, the checkpoint ordinal number, transferred bytes and
+average computed I/O speed.
+
 @cindex @code{dot}, checkpoint action
 @cindex @code{dot}, checkpoint action
 Another available checkpoint action is @samp{dot} (or @samp{.}).  It
 Another available checkpoint action is @samp{dot} (or @samp{.}).  It
 instructs @command{tar} to print a single dot on the standard listing
 instructs @command{tar} to print a single dot on the standard listing
@@ -4019,6 +4072,12 @@ For compatibility with previous @GNUTAR{} versions, this action can
 be abbreviated by placing a dot in front of the checkpoint frequency,
 be abbreviated by placing a dot in front of the checkpoint frequency,
 as shown in the previous section.
 as shown in the previous section.
 
 
+@cindex @code{totals}, checkpoint action
+The @samp{totals} action prints the total number of bytes transferred
+so far.  The format of the data is the same as for the
+@option{--totals} option (@pxref{totals}).  See also @samp{%T} format
+specifier of the @samp{echo} or @samp{ttyout} action.
+
 @cindex @code{sleep}, checkpoint action
 @cindex @code{sleep}, checkpoint action
 Yet another action, @samp{sleep}, pauses @command{tar} for a specified
 Yet another action, @samp{sleep}, pauses @command{tar} for a specified
 amount of seconds.  The following example will stop for 30 seconds at each
 amount of seconds.  The following example will stop for 30 seconds at each

+ 1 - 0
gnulib.modules

@@ -38,6 +38,7 @@ fdopendir
 fdutimensat
 fdutimensat
 fileblocks
 fileblocks
 fnmatch-gnu
 fnmatch-gnu
+fprintftime
 fseeko
 fseeko
 fstatat
 fstatat
 full-write
 full-write

+ 58 - 24
src/buffer.c

@@ -247,7 +247,7 @@ set_volume_start_time (void)
   last_stat_time = volume_start_time;
   last_stat_time = volume_start_time;
 }
 }
 
 
-void
+double
 compute_duration (void)
 compute_duration (void)
 {
 {
   struct timespec now;
   struct timespec now;
@@ -255,6 +255,7 @@ compute_duration (void)
   duration += ((now.tv_sec - last_stat_time.tv_sec)
   duration += ((now.tv_sec - last_stat_time.tv_sec)
                + (now.tv_nsec - last_stat_time.tv_nsec) / 1e9);
                + (now.tv_nsec - last_stat_time.tv_nsec) / 1e9);
   gettime (&last_stat_time);
   gettime (&last_stat_time);
+  return duration;
 }
 }
 
 
 
 
@@ -488,8 +489,7 @@ open_compressed_archive (void)
   return archive;
   return archive;
 }
 }
 
 
-
-static void
+static int
 print_stats (FILE *fp, const char *text, tarlong numbytes)
 print_stats (FILE *fp, const char *text, tarlong numbytes)
 {
 {
   char bytes[sizeof (tarlong) * CHAR_BIT];
   char bytes[sizeof (tarlong) * CHAR_BIT];
@@ -500,52 +500,86 @@ print_stats (FILE *fp, const char *text, tarlong numbytes)
 
 
   sprintf (bytes, TARLONG_FORMAT, numbytes);
   sprintf (bytes, TARLONG_FORMAT, numbytes);
 
 
-  fprintf (fp, "%s: %s (%s, %s/s)\n",
-           text, bytes,
-           human_readable (numbytes, abbr, human_opts, 1, 1),
-           (0 < duration && numbytes / duration < (uintmax_t) -1
-            ? human_readable (numbytes / duration, rate, human_opts, 1, 1)
-            : "?"));
+  return fprintf (fp, "%s: %s (%s, %s/s)",
+		  text, bytes,
+		  human_readable (numbytes, abbr, human_opts, 1, 1),
+		  (0 < duration && numbytes / duration < (uintmax_t) -1
+		   ? human_readable (numbytes / duration, rate, human_opts, 1, 1)
+		   : "?"));
 }
 }
 
 
-void
-print_total_stats (void)
+/* Format totals to file FP.  FORMATS is an array of strings to output
+   before each data item (bytes read, written, deleted, in that order).
+   EOR is a delimiter to output after each item (used only if deleting
+   from the archive), EOL is a delimiter to add at the end of the output
+   line. */ 
+int
+format_total_stats (FILE *fp, char **formats, int eor, int eol)
 {
 {
+  int n;
+  
   switch (subcommand_option)
   switch (subcommand_option)
     {
     {
     case CREATE_SUBCOMMAND:
     case CREATE_SUBCOMMAND:
     case CAT_SUBCOMMAND:
     case CAT_SUBCOMMAND:
     case UPDATE_SUBCOMMAND:
     case UPDATE_SUBCOMMAND:
     case APPEND_SUBCOMMAND:
     case APPEND_SUBCOMMAND:
-      /* Amanda 2.4.1p1 looks for "Total bytes written: [0-9][0-9]*".  */
-      print_stats (stderr, _("Total bytes written"),
-                   prev_written + bytes_written);
+      n = print_stats (fp, _(formats[TF_WRITE]),
+		       prev_written + bytes_written);
       break;
       break;
 
 
     case DELETE_SUBCOMMAND:
     case DELETE_SUBCOMMAND:
       {
       {
         char buf[UINTMAX_STRSIZE_BOUND];
         char buf[UINTMAX_STRSIZE_BOUND];
-        print_stats (stderr, _("Total bytes read"),
-                     records_read * record_size);
-        print_stats (stderr, _("Total bytes written"),
-                     prev_written + bytes_written);
-        fprintf (stderr, _("Total bytes deleted: %s\n"),
-                 STRINGIFY_BIGINT ((records_read - records_skipped)
-                                    * record_size
-                                   - (prev_written + bytes_written), buf));
+        n = print_stats (fp, _(formats[TF_READ]),
+			 records_read * record_size);
+
+	fputc (eor, fp);
+	n++;
+	
+        n += print_stats (fp, _(formats[TF_WRITE]),
+			  prev_written + bytes_written);
+
+	fputc (eor, fp);
+	n++;
+	
+        n += fprintf (fp, "%s: %s",
+		      _(formats[TF_DELETED]),
+		      STRINGIFY_BIGINT ((records_read - records_skipped)
+					* record_size
+					- (prev_written + bytes_written), buf));
       }
       }
       break;
       break;
 
 
     case EXTRACT_SUBCOMMAND:
     case EXTRACT_SUBCOMMAND:
     case LIST_SUBCOMMAND:
     case LIST_SUBCOMMAND:
     case DIFF_SUBCOMMAND:
     case DIFF_SUBCOMMAND:
-      print_stats (stderr, _("Total bytes read"),
-                   records_read * record_size);
+      n = print_stats (fp, _(formats[TF_READ]),
+		       records_read * record_size);
       break;
       break;
 
 
     default:
     default:
       abort ();
       abort ();
     }
     }
+  if (eol)
+    {
+      fputc (eol, fp);
+      n++;
+    }
+  return n;
+}
+
+char *default_total_format[] = {
+  N_("Total bytes read"),
+  /* Amanda 2.4.1p1 looks for "Total bytes written: [0-9][0-9]*".  */
+  N_("Total bytes written"),
+  N_("Total bytes deleted")
+};
+
+void
+print_total_stats (void)
+{
+  format_total_stats (stderr, default_total_format, '\n', '\n');
 }
 }
 
 
 /* Compute and return the block ordinal at current_block.  */
 /* Compute and return the block ordinal at current_block.  */

+ 145 - 57
src/checkpoint.c

@@ -19,6 +19,9 @@
 
 
 #include <system.h>
 #include <system.h>
 #include "common.h"
 #include "common.h"
+#include "wordsplit.h"
+#include <sys/ioctl.h>
+#include "fprintftime.h"
 
 
 enum checkpoint_opcode
 enum checkpoint_opcode
   {
   {
@@ -27,7 +30,8 @@ enum checkpoint_opcode
     cop_echo,
     cop_echo,
     cop_ttyout,
     cop_ttyout,
     cop_sleep,
     cop_sleep,
-    cop_exec
+    cop_exec,
+    cop_totals
   };
   };
 
 
 struct checkpoint_action
 struct checkpoint_action
@@ -110,6 +114,8 @@ checkpoint_compile_action (const char *str)
       act = alloc_action (cop_sleep);
       act = alloc_action (cop_sleep);
       act->v.time = n;
       act->v.time = n;
     }
     }
+  else if (strcmp (str, "totals") == 0)
+    alloc_action (cop_totals);
   else
   else
     FATAL_ERROR ((0, 0, _("%s: unknown checkpoint action"), str));
     FATAL_ERROR ((0, 0, _("%s: unknown checkpoint action"), str));
 }
 }
@@ -128,61 +134,160 @@ checkpoint_finish_compile (void)
     checkpoint_option = DEFAULT_CHECKPOINT;
     checkpoint_option = DEFAULT_CHECKPOINT;
 }
 }
 
 
+static char *checkpoint_total_format[] = {
+  "R",
+  "W",
+  "D"
+};
+
+static int
+getwidth(FILE *fp)
+{
+  struct winsize ws;
+
+  ws.ws_col = ws.ws_row = 0;
+  if ((ioctl (fileno (fp), TIOCGWINSZ, (char *) &ws) < 0) || ws.ws_col == 0)
+    {
+      const char *col = getenv ("COLUMNS");
+      if (col)
+	return strtol (col, NULL, 10);
+      else
+	return 80;
+    }
+  return ws.ws_col;
+}
+
 static char *
 static char *
-expand_checkpoint_string (const char *input, bool do_write, unsigned cpn)
+getarg (const char *input, const char ** endp, char **argbuf, size_t *arglen)
+{
+  if (input[0] == '{')
+    {
+      char *p = strchr (input + 1, '}');
+      if (p)
+	{
+	  size_t n = p - input;
+	  if (n > *arglen)
+	    {
+	      *arglen = n;
+	      *argbuf = xrealloc (*argbuf, *arglen);
+	    }
+	  n--;
+	  memcpy (*argbuf, input + 1, n);
+	  (*argbuf)[n] = 0;
+	  *endp = p + 1;
+	  return *argbuf;
+	}
+    }
+
+  *endp = input;
+  return NULL;
+}
+
+
+static void
+format_checkpoint_string (FILE *fp, const char *input, bool do_write,
+			  unsigned cpn)
 {
 {
   const char *opstr = do_write ? gettext ("write") : gettext ("read");
   const char *opstr = do_write ? gettext ("write") : gettext ("read");
-  size_t opstrlen = strlen (opstr);
   char uintbuf[UINTMAX_STRSIZE_BOUND];
   char uintbuf[UINTMAX_STRSIZE_BOUND];
   char *cps = STRINGIFY_BIGINT (cpn, uintbuf);
   char *cps = STRINGIFY_BIGINT (cpn, uintbuf);
-  size_t cpslen = strlen (cps);
   const char *ip;
   const char *ip;
-  char *op;
-  char *output;
-  size_t outlen = strlen (input); /* Initial guess */
+  size_t len = 0;
 
 
-  /* Fix the initial length guess */
-  for (ip = input; (ip = strchr (ip, '%')) != NULL; )
+  static char *argbuf = NULL;
+  static size_t arglen = 0;
+  char *arg = NULL;
+  
+  if (!input)
     {
     {
-      switch (ip[1])
-	{
-	case 'u':
-	  outlen += cpslen - 2;
-	  break;
-
-	case 's':
-	  outlen += opstrlen - 2;
-	}
-      ip++;
+      if (do_write)
+	/* TRANSLATORS: This is a "checkpoint of write operation",
+	 *not* "Writing a checkpoint".
+	 E.g. in Spanish "Punto de comprobaci@'on de escritura",
+	 *not* "Escribiendo un punto de comprobaci@'on" */
+	input = gettext ("Write checkpoint %u");
+      else
+	/* TRANSLATORS: This is a "checkpoint of read operation",
+	 *not* "Reading a checkpoint".
+	 E.g. in Spanish "Punto de comprobaci@'on de lectura",
+	 *not* "Leyendo un punto de comprobaci@'on" */
+	input = gettext ("Read checkpoint %u");
     }
     }
-
-  output = xmalloc (outlen + 1);
-  for (ip = input, op = output; *ip; )
+  
+  for (ip = input; *ip; ip++)
     {
     {
       if (*ip == '%')
       if (*ip == '%')
 	{
 	{
-	  switch (*++ip)
+	  if (*++ip == '{')
+	    {
+	      arg = getarg (ip, &ip, &argbuf, &arglen);
+	      if (!arg)
+		{
+		  fputc ('%', fp);
+		  fputc (*ip, fp);
+		  len += 2;
+		  continue;
+		}
+	    }
+	  switch (*ip)
 	    {
 	    {
 	    case 'u':
 	    case 'u':
-	      op = stpcpy (op, cps);
+	      fputs (cps, fp);
+	      len += strlen (cps);
 	      break;
 	      break;
 
 
 	    case 's':
 	    case 's':
-	      op = stpcpy (op, opstr);
+	      fputs (opstr, fp);
+	      len += strlen (opstr);
 	      break;
 	      break;
 
 
+	    case 'd':
+	      len += fprintf (fp, "%.0f", compute_duration ());
+	      break;
+	      
+	    case 'T':
+	      compute_duration ();
+	      len += format_total_stats (fp, checkpoint_total_format, ',', 0);
+	      break;
+
+	    case 't':
+	      {
+		struct timeval tv;
+		struct tm *tm;
+		char *fmt = arg ? arg : "%c";
+
+		gettimeofday (&tv, NULL);
+		tm = localtime (&tv.tv_sec);
+		len += fprintftime (fp, fmt, tm, 0, tv.tv_usec * 1000);
+	      }
+	      break;
+	      
+	    case '*':
+	      {
+		int w = arg ? strtoul (arg, NULL, 10) : getwidth (fp);
+		for (; w > len; len++)
+		  fputc (' ', fp);
+	      }
+	      break;
+	      
 	    default:
 	    default:
-	      *op++ = '%';
-	      *op++ = *ip;
+	      fputc ('%', fp);
+	      fputc (*ip, fp);
+	      len += 2;
 	      break;
 	      break;
 	    }
 	    }
-	  ip++;
+	  arg = NULL;
 	}
 	}
       else
       else
-	*op++ = *ip++;
+	{
+	  fputc (*ip, fp);
+	  if (*ip == '\r')
+	    len = 0;
+	  else
+	    len++;
+	}
     }
     }
-  *op = 0;
-  return output;
+  fflush (fp);
 }
 }
 
 
 static void
 static void
@@ -211,28 +316,9 @@ run_checkpoint_actions (bool do_write)
 	  break;
 	  break;
 
 
 	case cop_echo:
 	case cop_echo:
-	  {
-	    char *tmp;
-	    const char *str = p->v.command;
-	    if (!str)
-	      {
-		if (do_write)
-		  /* TRANSLATORS: This is a "checkpoint of write operation",
-		     *not* "Writing a checkpoint".
-		     E.g. in Spanish "Punto de comprobaci@'on de escritura",
-		     *not* "Escribiendo un punto de comprobaci@'on" */
-		  str = gettext ("Write checkpoint %u");
-		else
-		  /* TRANSLATORS: This is a "checkpoint of read operation",
-	             *not* "Reading a checkpoint".
-		     E.g. in Spanish "Punto de comprobaci@'on de lectura",
-		     *not* "Leyendo un punto de comprobaci@'on" */
-		  str = gettext ("Read checkpoint %u");
-	      }
-	    tmp = expand_checkpoint_string (str, do_write, checkpoint);
-	    WARN ((0, 0, "%s", tmp));
-	    free (tmp);
-	  }
+	  fprintf (stderr, "%s: ", program_name);
+	  format_checkpoint_string (stderr, p->v.command, do_write, checkpoint);
+	  fputc ('\n', stderr);
 	  break;
 	  break;
 
 
 	case cop_ttyout:
 	case cop_ttyout:
@@ -240,11 +326,9 @@ run_checkpoint_actions (bool do_write)
 	    tty = fopen ("/dev/tty", "w");
 	    tty = fopen ("/dev/tty", "w");
 	  if (tty)
 	  if (tty)
 	    {
 	    {
-	      char *tmp = expand_checkpoint_string (p->v.command, do_write,
-						    checkpoint);
-	      fprintf (tty, "%s", tmp);
+	      format_checkpoint_string (tty, p->v.command, do_write,
+					checkpoint);
 	      fflush (tty);
 	      fflush (tty);
-	      free (tmp);
 	    }
 	    }
 	  break;
 	  break;
 
 
@@ -257,6 +341,10 @@ run_checkpoint_actions (bool do_write)
 				      archive_name_cursor[0],
 				      archive_name_cursor[0],
 				      checkpoint);
 				      checkpoint);
 	  break;
 	  break;
+
+	case cop_totals:
+	  compute_duration ();
+	  print_total_stats ();
 	}
 	}
     }
     }
   if (tty)
   if (tty)

+ 7 - 1
src/common.h

@@ -427,7 +427,7 @@ size_t available_space_after (union block *pointer);
 off_t current_block_ordinal (void);
 off_t current_block_ordinal (void);
 void close_archive (void);
 void close_archive (void);
 void closeout_volume_number (void);
 void closeout_volume_number (void);
-void compute_duration (void);
+double compute_duration (void);
 union block *find_next_block (void);
 union block *find_next_block (void);
 void flush_read (void);
 void flush_read (void);
 void flush_write (void);
 void flush_write (void);
@@ -444,6 +444,12 @@ void archive_read_error (void);
 off_t seek_archive (off_t size);
 off_t seek_archive (off_t size);
 void set_start_time (void);
 void set_start_time (void);
 
 
+#define TF_READ    0
+#define TF_WRITE   1
+#define TF_DELETED 2
+int format_total_stats (FILE *fp, char **formats, int eor, int eol);
+void print_total_stats (void);
+
 void mv_begin_write (const char *file_name, off_t totsize, off_t sizeleft);
 void mv_begin_write (const char *file_name, off_t totsize, off_t sizeleft);
 
 
 void mv_begin_read (struct tar_stat_info *st);
 void mv_begin_read (struct tar_stat_info *st);