Fix unlikely problems with time overflow

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.
This commit is contained in:
Paul Eggert
2024-08-01 10:02:06 -07:00
parent aae99e863d
commit 6c91bd82e1
4 changed files with 60 additions and 36 deletions

View File

@@ -249,7 +249,8 @@ clear_read_error_count (void)
/* Time-related functions */ /* Time-related functions */
static double duration; /* Time consumed during run. It is counted in ns to lessen rounding error. */
static double duration_ns;
void void
set_start_time (void) set_start_time (void)
@@ -267,14 +268,18 @@ set_volume_start_time (void)
} }
double double
compute_duration (void) compute_duration_ns (void)
{ {
struct timespec now; struct timespec now = current_timespec ();
gettime (&now);
duration += ((now.tv_sec - last_stat_time.tv_sec) /* If the clock moves back, treat it as duration 0.
+ (now.tv_nsec - last_stat_time.tv_nsec) / 1e9); This works even if time_t is unsigned. */
gettime (&last_stat_time); if (timespec_cmp (last_stat_time, now) < 0)
return duration; 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;
} }
@@ -491,19 +496,29 @@ static int
print_stats (FILE *fp, const char *text, tarlong numbytes) print_stats (FILE *fp, const char *text, tarlong numbytes)
{ {
char abbr[LONGEST_HUMAN_READABLE + 1]; 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; int human_opts = human_autoscale | human_base_1024 | human_SI | human_B;
double ulim = UINTMAX_MAX + 1.0;
if (text && text[0]) int n = fprintf (fp, "%s: "TARLONG_FORMAT" (", gettext (text), numbytes);
n += fprintf (fp, "%s: ", gettext (text));
return n + fprintf (fp, TARLONG_FORMAT " (%s, %s/s)", if (numbytes < ulim)
numbytes, n += fprintf (fp, "%s", human_readable (numbytes, abbr, human_opts, 1, 1));
human_readable (numbytes, abbr, human_opts, 1, 1), else
(0 < duration && numbytes / duration < (uintmax_t) -1 n += fprintf (fp, "%g", numbytes);
? 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 /* 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); while (current_block > record_start);
} }
compute_duration (); compute_duration_ns ();
if (verify_option) if (verify_option)
verify_volume (); verify_volume ();

View File

@@ -291,14 +291,14 @@ format_checkpoint_string (FILE *fp, size_t len,
break; break;
case 'd': case 'd':
len += fprintf (fp, "%.0f", compute_duration ()); len += fprintf (fp, "%.0f", compute_duration_ns () / BILLION);
break; break;
case 'T': case 'T':
{ {
const char **fmt = checkpoint_total_format, *fmtbuf[3]; const char **fmt = checkpoint_total_format, *fmtbuf[3];
struct wordsplit ws; struct wordsplit ws;
compute_duration (); compute_duration_ns ();
if (arg) if (arg)
{ {
@@ -420,7 +420,7 @@ run_checkpoint_actions (bool do_write)
break; break;
case cop_totals: case cop_totals:
compute_duration (); compute_duration_ns ();
print_total_stats (); print_total_stats ();
break; break;

View File

@@ -455,7 +455,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);
double compute_duration (void); double compute_duration_ns (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);

View File

@@ -1083,7 +1083,7 @@ set_use_compress_program_option (const char *string, struct option_locus *loc)
static void static void
sigstat (int signo) sigstat (int signo)
{ {
compute_duration (); compute_duration_ns ();
print_total_stats (); print_total_stats ();
#ifndef HAVE_SIGACTION #ifndef HAVE_SIGACTION
signal (signo, sigstat); signal (signo, sigstat);
@@ -1688,13 +1688,24 @@ parse_opt (int key, char *arg, struct argp_state *state)
uintmax_t u; uintmax_t u;
char *p; char *p;
if (xstrtoumax (arg, &p, 10, &u, TAR_SIZE_SUFFIXES) != LONGINT_OK) switch (xstrtoumax (arg, &p, 10, &u, TAR_SIZE_SUFFIXES))
USAGE_ERROR ((0, 0, "%s: %s", quotearg_colon (arg), {
_("Invalid tape length"))); case LONGINT_OK:
if (p > arg && !strchr (TAR_SIZE_SUFFIXES, p[-1])) tape_length_option = u;
tape_length_option = 1024 * (tarlong) u; if (arg < p && !strchr (TAR_SIZE_SUFFIXES, p[-1]))
else tape_length_option *= 1024;
tape_length_option = (tarlong) u; 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; multi_volume_option = true;
} }
break; break;
@@ -2102,10 +2113,9 @@ parse_opt (int key, char *arg, struct argp_state *state)
uintmax_t u; uintmax_t u;
if (! (xstrtoumax (arg, NULL, 10, &u, TAR_SIZE_SUFFIXES) == LONGINT_OK 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), USAGE_ERROR ((0, 0, "%s: %s", quotearg_colon (arg),
_("Invalid record size"))); _("Invalid record size")));
record_size = u;
if (record_size % BLOCKSIZE != 0) if (record_size % BLOCKSIZE != 0)
USAGE_ERROR ((0, 0, _("Record size must be a multiple of %d."), USAGE_ERROR ((0, 0, _("Record size must be a multiple of %d."),
BLOCKSIZE)); BLOCKSIZE));
@@ -2151,10 +2161,9 @@ parse_opt (int key, char *arg, struct argp_state *state)
{ {
uintmax_t u; uintmax_t u;
if (! (xstrtoumax (arg, 0, 10, &u, "") == LONGINT_OK 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), USAGE_ERROR ((0, 0, "%s: %s", quotearg_colon (arg),
_("Invalid number of elements"))); _("Invalid number of elements")));
strip_name_components = u;
} }
break; break;