]> git.ipfire.org Git - thirdparty/tar.git/commitdiff
Fix unlikely problems with time overflow
authorPaul Eggert <eggert@cs.ucla.edu>
Thu, 1 Aug 2024 17:02:06 +0000 (10:02 -0700)
committerPaul Eggert <eggert@cs.ucla.edu>
Sun, 4 Aug 2024 08:41:43 +0000 (01:41 -0700)
Also, fix some rounding errors while we’re in the neighborhood.
* src/buffer.c (duration_ns, compute_duration_ns): Rename from
‘duration’ and ‘compute_duration’, and count ns rather than s, to
lessen rounding error.  All uses changed.
(compute_duration_ns): Work even if the clock moves backward
and time_t is unsigned.
(print_stats): Don’t worry about null or empty TEXT, as that
cannot happen.  Compare double to UINTMAX_MAX + 1.0, not
to UINTMAX_MAX, so that the comparison is exact.
Handle the unlikely case that numbytes >= UINTMAX_MAX.
* src/tar.c (parse_opt): Treat -L hugenumber as effectively
infinity rather than erroring out.
Prefer ckd_add to checking overflow by hand.

src/buffer.c
src/checkpoint.c
src/common.h
src/tar.c

index 267b43eb4cd66a67fdeb18d7850d91465d8b931b..b55a132c2775236ca5d42389f4b3f6d64e032e11 100644 (file)
@@ -249,7 +249,8 @@ clear_read_error_count (void)
 \f
 /* Time-related functions */
 
-static double duration;
+/* Time consumed during run.  It is counted in ns to lessen rounding error.  */
+static double duration_ns;
 
 void
 set_start_time (void)
@@ -267,14 +268,18 @@ set_volume_start_time (void)
 }
 
 double
-compute_duration (void)
+compute_duration_ns (void)
 {
-  struct timespec now;
-  gettime (&now);
-  duration += ((now.tv_sec - last_stat_time.tv_sec)
-               + (now.tv_nsec - last_stat_time.tv_nsec) / 1e9);
-  gettime (&last_stat_time);
-  return duration;
+  struct timespec now = current_timespec ();
+
+  /* If the clock moves back, treat it as duration 0.
+     This works even if time_t is unsigned.  */
+  if (timespec_cmp (last_stat_time, now) < 0)
+    duration_ns += (1e9 * (now.tv_sec - last_stat_time.tv_sec)
+                   + (now.tv_nsec - last_stat_time.tv_nsec));
+
+  last_stat_time = current_timespec ();
+  return duration_ns;
 }
 
 \f
@@ -491,19 +496,29 @@ static int
 print_stats (FILE *fp, const char *text, tarlong numbytes)
 {
   char abbr[LONGEST_HUMAN_READABLE + 1];
-  char rate[LONGEST_HUMAN_READABLE + 1];
-  int n = 0;
-
   int human_opts = human_autoscale | human_base_1024 | human_SI | human_B;
+  double ulim = UINTMAX_MAX + 1.0;
+
+  int n = fprintf (fp, "%s: "TARLONG_FORMAT" (", gettext (text), numbytes);
+
+  if (numbytes < ulim)
+    n += fprintf (fp, "%s", human_readable (numbytes, abbr, human_opts, 1, 1));
+  else
+    n += fprintf (fp, "%g", numbytes);
 
-  if (text && text[0])
-    n += fprintf (fp, "%s: ", gettext (text));
-  return n + fprintf (fp, TARLONG_FORMAT " (%s, %s/s)",
-                     numbytes,
-                     human_readable (numbytes, abbr, human_opts, 1, 1),
-                     (0 < duration && numbytes / duration < (uintmax_t) -1
-                      ? human_readable (numbytes / duration, rate, human_opts, 1, 1)
-                      : "?"));
+  if (!duration_ns)
+    n += fprintf (fp, ")");
+  else
+    {
+      double rate = 1e9 * numbytes / duration_ns;
+      if (rate < ulim)
+       n += fprintf (fp, ", %s/s)",
+                     human_readable (rate, abbr, human_opts, 1, 1));
+      else
+       n += fprintf (fp, ", %g/s)", rate);
+    }
+
+  return n;
 }
 
 /* Format totals to file FP.  FORMATS is an array of strings to output
@@ -1121,7 +1136,7 @@ close_archive (void)
       while (current_block > record_start);
     }
 
-  compute_duration ();
+  compute_duration_ns ();
   if (verify_option)
     verify_volume ();
 
index f1710bb533ab47dd38e49b15c95ceff0dcb5008c..8194463e8db34258357f849a11a7b3bcc27272e6 100644 (file)
@@ -291,14 +291,14 @@ format_checkpoint_string (FILE *fp, size_t len,
              break;
 
            case 'd':
-             len += fprintf (fp, "%.0f", compute_duration ());
+             len += fprintf (fp, "%.0f", compute_duration_ns () / BILLION);
              break;
 
            case 'T':
              {
                const char **fmt = checkpoint_total_format, *fmtbuf[3];
                struct wordsplit ws;
-               compute_duration ();
+               compute_duration_ns ();
 
                if (arg)
                  {
@@ -420,7 +420,7 @@ run_checkpoint_actions (bool do_write)
          break;
 
        case cop_totals:
-         compute_duration ();
+         compute_duration_ns ();
          print_total_stats ();
          break;
 
index ba4a4f7d86c4e6741f6656dd69e949101b505d60..c65e64d691f33db6e80fdd9ea9d9d3ab89f5068c 100644 (file)
@@ -455,7 +455,7 @@ size_t available_space_after (union block *pointer);
 off_t current_block_ordinal (void);
 void close_archive (void);
 void closeout_volume_number (void);
-double compute_duration (void);
+double compute_duration_ns (void);
 union block *find_next_block (void);
 void flush_read (void);
 void flush_write (void);
index 8efa379434d0ddeb36d56f531b160428d0cb3ebf..d47cdac33619deec5a4750d43f9a507724054f1f 100644 (file)
--- a/src/tar.c
+++ b/src/tar.c
@@ -1083,7 +1083,7 @@ set_use_compress_program_option (const char *string, struct option_locus *loc)
 static void
 sigstat (int signo)
 {
-  compute_duration ();
+  compute_duration_ns ();
   print_total_stats ();
 #ifndef HAVE_SIGACTION
   signal (signo, sigstat);
@@ -1688,13 +1688,24 @@ parse_opt (int key, char *arg, struct argp_state *state)
        uintmax_t u;
        char *p;
 
-       if (xstrtoumax (arg, &p, 10, &u, TAR_SIZE_SUFFIXES) != LONGINT_OK)
-         USAGE_ERROR ((0, 0, "%s: %s", quotearg_colon (arg),
-                       _("Invalid tape length")));
-       if (p > arg && !strchr (TAR_SIZE_SUFFIXES, p[-1]))
-         tape_length_option = 1024 * (tarlong) u;
-       else
-         tape_length_option = (tarlong) u;
+       switch (xstrtoumax (arg, &p, 10, &u, TAR_SIZE_SUFFIXES))
+         {
+         case LONGINT_OK:
+           tape_length_option = u;
+           if (arg < p && !strchr (TAR_SIZE_SUFFIXES, p[-1]))
+             tape_length_option *= 1024;
+           break;
+
+         case LONGINT_OVERFLOW:
+           /* Treat enormous values as effectively infinity.  */
+           tape_length_option = 0;
+           break;
+
+         default:
+           USAGE_ERROR ((0, 0, "%s: %s", quotearg_colon (arg),
+                         _("Invalid tape length")));
+         }
+
        multi_volume_option = true;
       }
       break;
@@ -2102,10 +2113,9 @@ parse_opt (int key, char *arg, struct argp_state *state)
        uintmax_t u;
 
        if (! (xstrtoumax (arg, NULL, 10, &u, TAR_SIZE_SUFFIXES) == LONGINT_OK
-              && u == (size_t) u))
+              && !ckd_add (&record_size, u, 0)))
          USAGE_ERROR ((0, 0, "%s: %s", quotearg_colon (arg),
                        _("Invalid record size")));
-       record_size = u;
        if (record_size % BLOCKSIZE != 0)
          USAGE_ERROR ((0, 0, _("Record size must be a multiple of %d."),
                        BLOCKSIZE));
@@ -2151,10 +2161,9 @@ parse_opt (int key, char *arg, struct argp_state *state)
       {
        uintmax_t u;
        if (! (xstrtoumax (arg, 0, 10, &u, "") == LONGINT_OK
-              && u == (size_t) u))
+              && !ckd_add (&strip_name_components, u, 0)))
          USAGE_ERROR ((0, 0, "%s: %s", quotearg_colon (arg),
                        _("Invalid number of elements")));
-       strip_name_components = u;
       }
       break;