Prefer stoint to strtoul and variants

When parsing numbers prefer using strtosysint (renamed stoint)
to using strtoul and its variants.
This is simpler and faster and likely more reliable than
relying on quirks of the system strtoul etc,
and it standardizes how tar deals with parsing integers.
Among other things, the C standard and POSIX don’t specify
what strtol does to errno when conversions cannot be performed,
and it requires strtoul to support "-" before unsigned numbers.
* gnulib.modules (strtoimax, strtol, strtoumax, xstrtoimax):
Remove.
* src/checkpoint.c (checkpoint_compile_action, getwidth)
(format_checkpoint_string):
* src/incremen.c (read_incr_db_01, read_num)
* src/map.c (parse_id):
* src/misc.c (decode_timespec):
* src/sparse.c (decode_num):
* src/tar.c (parse_owner_group, parse_opt):
* src/transform.c (parse_transform_expr):
* src/xheader.c (decode_record, decode_signed_num)
(sparse_map_decoder):
Prefer stoint to strtol etc.
Don’t rely on errno == EINVAL as the standards don’t guarantee it.
* src/checkpoint.c (getwidth, format_checkpoint_string):
Check for invalid string suffix.
* src/checkpoint.c (getwidth):
Return intmax_t, not long.  All callers changed.
* src/incremen.c (read_directory_file):
It’s just a one-digit number, so just subtract '0'.
* src/map.c (parse_id): Return bool not int.  All callers changed.
* src/misc.c (stoint): Rename from strtosysint, and add
a bool * argument for reporting overflow.  All callers changed.
(decode_timespec): Simplify by using ckd_sub rather than
checking for overflow by hand.
* src/tar.c (incremental_level): Now signed char to
emphasize that it can be only -1, 0, 1.  All uses changed.
* src/xheader.c (decode_record): Avoid giant diagnostics.
This commit is contained in:
Paul Eggert
2024-08-08 16:32:49 -07:00
parent 3ffe2eb073
commit d1e72a536f
10 changed files with 232 additions and 278 deletions

View File

@@ -106,9 +106,6 @@ stdopen
strdup-posix strdup-posix
strerror strerror
strnlen strnlen
strtoimax
strtol
strtoumax
symlinkat symlinkat
sys_stat sys_stat
timespec timespec
@@ -122,7 +119,6 @@ xalignalloc
xalloc xalloc
xalloc-die xalloc-die
xgetcwd xgetcwd
xstrtoimax
xstrtoumax xstrtoumax
xvasprintf xvasprintf
year2038-recommended year2038-recommended

View File

@@ -127,13 +127,12 @@ checkpoint_compile_action (const char *str)
} }
else if (strncmp (str, "sleep=", 6) == 0) else if (strncmp (str, "sleep=", 6) == 0)
{ {
char const *arg = str + 6;
char *p; char *p;
intmax_t sleepsec = strtoimax (str + 6, &p, 10);
if (*p || sleepsec < 0)
FATAL_ERROR ((0, 0, _("%s: not a valid timeout"), str));
time_t n = ckd_add (&n, sleepsec, 0) ? TYPE_MAXIMUM (time_t) : n;
act = alloc_action (cop_sleep); act = alloc_action (cop_sleep);
act->v.time = n; act->v.time = stoint (arg, &p, NULL, 0, TYPE_MAXIMUM (time_t));
if ((p == arg) | *p)
FATAL_ERROR ((0, 0, _("%s: not a valid timeout"), str));
} }
else if (strcmp (str, "totals") == 0) else if (strcmp (str, "totals") == 0)
alloc_action (cop_totals); alloc_action (cop_totals);
@@ -176,7 +175,7 @@ static const char *checkpoint_total_format[] = {
"D" "D"
}; };
static long static intmax_t
getwidth (FILE *fp) getwidth (FILE *fp)
{ {
char const *columns; char const *columns;
@@ -190,8 +189,9 @@ getwidth (FILE *fp)
columns = getenv ("COLUMNS"); columns = getenv ("COLUMNS");
if (columns) if (columns)
{ {
long int col = strtol (columns, NULL, 10); char *end;
if (0 < col) intmax_t col = stoint (columns, &end, NULL, 0, INTMAX_MAX);
if (! (*end | !col))
return col; return col;
} }
@@ -337,7 +337,16 @@ format_checkpoint_string (FILE *fp, size_t len,
case '*': case '*':
{ {
long w = arg ? strtol (arg, NULL, 10) : getwidth (fp); intmax_t w;
if (!arg)
w = getwidth (fp);
else
{
char *end;
w = stoint (arg, &end, NULL, 0, INTMAX_MAX);
if ((end == arg) | *end)
w = 80;
}
for (; w > len; len++) for (; w > len; len++)
fputc (' ', fp); fputc (' ', fp);
} }

View File

@@ -190,8 +190,8 @@ extern bool keep_directory_symlink_option;
/* Specified file name for incremental list. */ /* Specified file name for incremental list. */
extern const char *listed_incremental_option; extern const char *listed_incremental_option;
/* Incremental dump level */ /* Incremental dump level: either -1, 0, or 1. */
extern int incremental_level; extern signed char incremental_level;
/* Check device numbers when doing incremental dumps. */ /* Check device numbers when doing incremental dumps. */
extern bool check_device_option; extern bool check_device_option;
@@ -701,7 +701,7 @@ enum { UINTMAX_STRSIZE_BOUND = INT_BUFSIZE_BOUND (intmax_t) };
enum { SYSINT_BUFSIZE = enum { SYSINT_BUFSIZE =
max (UINTMAX_STRSIZE_BOUND, INT_BUFSIZE_BOUND (intmax_t)) }; max (UINTMAX_STRSIZE_BOUND, INT_BUFSIZE_BOUND (intmax_t)) };
char *sysinttostr (uintmax_t, intmax_t, uintmax_t, char buf[SYSINT_BUFSIZE]); char *sysinttostr (uintmax_t, intmax_t, uintmax_t, char buf[SYSINT_BUFSIZE]);
intmax_t strtosysint (char const *, char **, intmax_t, uintmax_t); intmax_t stoint (char const *, char **, bool *, intmax_t, uintmax_t);
char *timetostr (time_t, char buf[SYSINT_BUFSIZE]); char *timetostr (time_t, char buf[SYSINT_BUFSIZE]);
void code_ns_fraction (int ns, char *p); void code_ns_fraction (int ns, char *p);
enum { BILLION = 1000000000, LOG10_BILLION = 9 }; enum { BILLION = 1000000000, LOG10_BILLION = 9 };

View File

@@ -976,7 +976,6 @@ static void
read_incr_db_01 (int version, const char *initbuf) read_incr_db_01 (int version, const char *initbuf)
{ {
int n; int n;
uintmax_t u;
char *buf = NULL; char *buf = NULL;
size_t bufsize = 0; size_t bufsize = 0;
char *ebuf; char *ebuf;
@@ -1010,21 +1009,18 @@ read_incr_db_01 (int version, const char *initbuf)
if (version == 1 && *ebuf) if (version == 1 && *ebuf)
{ {
char const *buf_ns = ebuf + 1; char const *buf_ns = ebuf + 1;
errno = 0; bool overflow;
u = strtoumax (buf_ns, &ebuf, 10); newer_mtime_option.tv_nsec
if (!errno && BILLION <= u) = stoint (buf_ns, &ebuf, &overflow, 0, BILLION - 1);
errno = ERANGE; if ((ebuf == buf_ns) | *ebuf | overflow)
if (errno || buf_ns == ebuf)
{ {
ERROR ((0, errno, "%s:%ld: %s", ERROR ((0, 0, "%s:%ld: %s",
quotearg_colon (listed_incremental_option), quotearg_colon (listed_incremental_option),
lineno, lineno,
_("Invalid time stamp"))); _("Invalid time stamp")));
newer_mtime_option.tv_sec = TYPE_MINIMUM (time_t); newer_mtime_option.tv_sec = TYPE_MINIMUM (time_t);
newer_mtime_option.tv_nsec = -1; newer_mtime_option.tv_nsec = -1;
} }
else
newer_mtime_option.tv_nsec = u;
} }
} }
@@ -1050,41 +1046,37 @@ read_incr_db_01 (int version, const char *initbuf)
quotearg_colon (listed_incremental_option), lineno, quotearg_colon (listed_incremental_option), lineno,
_("Invalid modification time"))); _("Invalid modification time")));
errno = 0; bool overflow;
u = strtoumax (strp, &ebuf, 10); mtime.tv_nsec = stoint (strp, &ebuf, &overflow, 0, BILLION - 1);
if (!errno && BILLION <= u) if ((ebuf == strp) | (*ebuf != ' ') | overflow)
errno = ERANGE;
if (errno || strp == ebuf || *ebuf != ' ')
{ {
FATAL_ERROR ((0, errno, "%s:%ld: %s", FATAL_ERROR ((0, 0, "%s:%ld: %s",
quotearg_colon (listed_incremental_option), lineno, quotearg_colon (listed_incremental_option), lineno,
_("Invalid modification time (nanoseconds)"))); _("Invalid modification time (nanoseconds)")));
mtime.tv_nsec = -1; mtime.tv_nsec = -1;
} }
else
mtime.tv_nsec = u;
strp = ebuf; strp = ebuf;
} }
else else
mtime.tv_sec = mtime.tv_nsec = 0; mtime.tv_sec = mtime.tv_nsec = 0;
dev = strtosysint (strp, &ebuf, bool overflow;
TYPE_MINIMUM (dev_t), TYPE_MAXIMUM (dev_t)); dev = stoint (strp, &ebuf, &overflow,
strp = ebuf; TYPE_MINIMUM (dev_t), TYPE_MAXIMUM (dev_t));
if (errno || *strp != ' ') if ((ebuf == strp) | (*ebuf != ' ') | overflow)
FATAL_ERROR ((0, errno, "%s:%ld: %s", FATAL_ERROR ((0, 0, "%s:%ld: %s",
quotearg_colon (listed_incremental_option), lineno, quotearg_colon (listed_incremental_option), lineno,
_("Invalid device number"))); _("Invalid device number")));
strp = ebuf + 1;
ino = strtosysint (strp, &ebuf, ino = stoint (strp, &ebuf, &overflow,
TYPE_MINIMUM (ino_t), TYPE_MAXIMUM (ino_t)); TYPE_MINIMUM (ino_t), TYPE_MAXIMUM (ino_t));
strp = ebuf; if ((ebuf == strp) | (*ebuf != ' ') | overflow)
if (errno || *strp != ' ') FATAL_ERROR ((0, 0, "%s:%ld: %s",
FATAL_ERROR ((0, errno, "%s:%ld: %s",
quotearg_colon (listed_incremental_option), lineno, quotearg_colon (listed_incremental_option), lineno,
_("Invalid inode number"))); _("Invalid inode number")));
strp = ebuf + 1;
strp++;
unquote_string (strp); unquote_string (strp);
note_directory (strp, mtime, dev, ino, nfs, false, NULL); note_directory (strp, mtime, dev, ino, nfs, false, NULL);
} }
@@ -1126,7 +1118,6 @@ read_num (FILE *fp, char const *fieldname,
{ {
int i; int i;
char buf[INT_BUFSIZE_BOUND (intmax_t)]; char buf[INT_BUFSIZE_BOUND (intmax_t)];
int conversion_errno;
int c = getc (fp); int c = getc (fp);
bool negative = c == '-'; bool negative = c == '-';
@@ -1165,24 +1156,20 @@ read_num (FILE *fp, char const *fieldname,
fieldname, buf, uc)); fieldname, buf, uc));
} }
*pval = strtosysint (buf, NULL, min_val, max_val); char *bufend;
conversion_errno = errno; bool overflow;
*pval = stoint (buf, &bufend, &overflow, min_val, max_val);
switch (conversion_errno) if (buf == bufend)
{ FATAL_ERROR ((0, EINVAL,
case ERANGE: _("%s: byte %jd: %s %s"),
FATAL_ERROR ((0, conversion_errno, quotearg_colon (listed_incremental_option),
_("%s: byte %jd: (valid range %jd..%ju)\n\t%s %s"), intmax (ftello (fp)), fieldname, buf));
quotearg_colon (listed_incremental_option), if (overflow)
intmax (ftello (fp)), min_val, max_val, fieldname, buf)); FATAL_ERROR ((0, ERANGE,
default: _("%s: byte %jd: (valid range %jd..%ju)\n\t%s %s"),
FATAL_ERROR ((0, conversion_errno, quotearg_colon (listed_incremental_option),
_("%s: byte %jd: %s %s"), intmax (ftello (fp)), min_val, max_val, fieldname, buf));
quotearg_colon (listed_incremental_option),
intmax (ftello (fp)), fieldname, buf));
case 0:
break;
}
return true; return true;
} }
@@ -1361,7 +1348,7 @@ read_directory_file (void)
if (0 < getline (&buf, &bufsize, listed_incremental_stream)) if (0 < getline (&buf, &bufsize, listed_incremental_stream))
{ {
char *ebuf; char *ebuf;
uintmax_t incremental_version; int incremental_version;
if (strncmp (buf, PACKAGE_NAME, sizeof PACKAGE_NAME - 1) == 0) if (strncmp (buf, PACKAGE_NAME, sizeof PACKAGE_NAME - 1) == 0)
{ {
@@ -1372,7 +1359,12 @@ read_directory_file (void)
if (!*ebuf) if (!*ebuf)
ERROR((1, 0, _("Bad incremental file format"))); ERROR((1, 0, _("Bad incremental file format")));
incremental_version = strtoumax (ebuf + 1, NULL, 10); ebuf++;
if (! ('0' <= *ebuf && *ebuf <= '0' + TAR_INCREMENTAL_VERSION
&& !c_isdigit (ebuf[1])))
ERROR ((1, 0, _("Unsupported incremental format version: %s"),
ebuf));
incremental_version = *ebuf - '0';
} }
else else
incremental_version = 0; incremental_version = 0;
@@ -1389,10 +1381,8 @@ read_directory_file (void)
break; break;
default: default:
ERROR ((1, 0, _("Unsupported incremental format version: %"PRIuMAX), unreachable ();
incremental_version));
} }
} }
if (ferror (listed_incremental_stream)) if (ferror (listed_incremental_stream))

View File

@@ -45,28 +45,26 @@ map_compare (void const *entry1, void const *entry2)
return map1->orig_id == map2->orig_id; return map1->orig_id == map2->orig_id;
} }
static int static bool
parse_id (uintmax_t *retval, parse_id (uintmax_t *retval,
char const *arg, char const *what, uintmax_t maxval, char const *arg, char const *what, uintmax_t maxval,
char const *file, unsigned line) char const *file, unsigned line)
{ {
uintmax_t v;
char *p; char *p;
bool overflow;
errno = 0; *retval = stoint (arg, &p, &overflow, 0, maxval);
v = strtoumax (arg, &p, 10);
if (*p || errno) if ((p == arg) | *p)
{ {
error (0, 0, _("%s:%u: invalid %s: %s"), file, line, what, arg); error (0, 0, _("%s:%u: invalid %s: %s"), file, line, what, arg);
return -1; return false;
} }
if (v > maxval) if (overflow)
{ {
error (0, 0, _("%s:%u: %s out of range: %s"), file, line, what, arg); error (0, 0, _("%s:%u: %s out of range: %s"), file, line, what, arg);
return -1; return false;
} }
*retval = v; return true;
return 0;
} }
static void static void
@@ -82,7 +80,7 @@ map_read (Hash_table **ptab, char const *file,
int wsopt; int wsopt;
unsigned line; unsigned line;
int err = 0; int err = 0;
fp = fopen (file, "r"); fp = fopen (file, "r");
if (!fp) if (!fp)
open_fatal (file); open_fatal (file);
@@ -97,7 +95,7 @@ map_read (Hash_table **ptab, char const *file,
uintmax_t orig_id, new_id; uintmax_t orig_id, new_id;
char *name = NULL; char *name = NULL;
char *colon; char *colon;
++line; ++line;
if (wordsplit (buf, &ws, wsopt)) if (wordsplit (buf, &ws, wsopt))
FATAL_ERROR ((0, 0, _("%s:%u: cannot split line: %s"), FATAL_ERROR ((0, 0, _("%s:%u: cannot split line: %s"),
@@ -114,7 +112,7 @@ map_read (Hash_table **ptab, char const *file,
if (ws.ws_wordv[0][0] == '+') if (ws.ws_wordv[0][0] == '+')
{ {
if (parse_id (&orig_id, ws.ws_wordv[0]+1, what, maxval, file, line)) if (!parse_id (&orig_id, ws.ws_wordv[0]+1, what, maxval, file, line))
{ {
err = 1; err = 1;
continue; continue;
@@ -138,7 +136,7 @@ map_read (Hash_table **ptab, char const *file,
if (colon > ws.ws_wordv[1]) if (colon > ws.ws_wordv[1])
name = ws.ws_wordv[1]; name = ws.ws_wordv[1];
*colon++ = 0; *colon++ = 0;
if (parse_id (&new_id, colon, what, maxval, file, line)) if (!parse_id (&new_id, colon, what, maxval, file, line))
{ {
err = 1; err = 1;
continue; continue;
@@ -146,7 +144,7 @@ map_read (Hash_table **ptab, char const *file,
} }
else if (ws.ws_wordv[1][0] == '+') else if (ws.ws_wordv[1][0] == '+')
{ {
if (parse_id (&new_id, ws.ws_wordv[1], what, maxval, file, line)) if (!parse_id (&new_id, ws.ws_wordv[1], what, maxval, file, line))
{ {
err = 1; err = 1;
continue; continue;
@@ -169,7 +167,7 @@ map_read (Hash_table **ptab, char const *file,
ent->orig_id = orig_id; ent->orig_id = orig_id;
ent->new_id = new_id; ent->new_id = new_id;
ent->new_name = name ? xstrdup (name) : NULL; ent->new_name = name ? xstrdup (name) : NULL;
if (!((*ptab if (!((*ptab
|| (*ptab = hash_initialize (0, 0, map_hash, map_compare, 0))) || (*ptab = hash_initialize (0, 0, map_hash, map_compare, 0)))
&& hash_insert (*ptab, ent))) && hash_insert (*ptab, ent)))
@@ -203,11 +201,11 @@ int
owner_map_translate (uid_t uid, uid_t *new_uid, char const **new_name) owner_map_translate (uid_t uid, uid_t *new_uid, char const **new_name)
{ {
int rc = 1; int rc = 1;
if (owner_map) if (owner_map)
{ {
struct mapentry ent, *res; struct mapentry ent, *res;
ent.orig_id = uid; ent.orig_id = uid;
res = hash_lookup (owner_map, &ent); res = hash_lookup (owner_map, &ent);
if (res) if (res)
@@ -253,11 +251,11 @@ int
group_map_translate (gid_t gid, gid_t *new_gid, char const **new_name) group_map_translate (gid_t gid, gid_t *new_gid, char const **new_name)
{ {
int rc = 1; int rc = 1;
if (group_map) if (group_map)
{ {
struct mapentry ent, *res; struct mapentry ent, *res;
ent.orig_id = gid; ent.orig_id = gid;
res = hash_lookup (group_map, &ent); res = hash_lookup (group_map, &ent);
if (res) if (res)
@@ -278,6 +276,6 @@ group_map_translate (gid_t gid, gid_t *new_gid, char const **new_name)
*new_name = group_name_option; *new_name = group_name_option;
rc = 0; rc = 0;
} }
return rc; return rc;
} }

View File

@@ -400,51 +400,102 @@ timetostr (time_t t, char buf[SYSINT_BUFSIZE])
return sysinttostr (t, TYPE_MINIMUM (time_t), TYPE_MAXIMUM (time_t), buf); return sysinttostr (t, TYPE_MINIMUM (time_t), TYPE_MAXIMUM (time_t), buf);
} }
/* Convert a prefix of the string ARG to a system integer type whose /* Convert a prefix of the string ARG to a system integer type.
minimum value is MINVAL and maximum MAXVAL. If MINVAL is negative, If ARGLIM, set *ARGLIM to point to just after the prefix.
If OVERFLOW, set *OVERFLOW to true or false
depending on whether the input is out of MINVAL..MAXVAL range.
If the input is out of that range, return an extreme value.
MINVAL must not be positive.
If MINVAL is negative, MAXVAL can be at most INTMAX_MAX, and
negative integers MINVAL .. -1 are assumed to be represented using negative integers MINVAL .. -1 are assumed to be represented using
leading '-' in the usual way. If the represented value exceeds leading '-' in the usual way. If the represented value exceeds
INTMAX_MAX, return a negative integer V such that (uintmax_t) V INTMAX_MAX, return a negative integer V such that (uintmax_t) V
yields the represented value. If ARGLIM is nonnull, store into yields the represented value.
*ARGLIM a pointer to the first character after the prefix.
On conversion error: if ARGLIM set *ARGLIM = ARG; if OVERFLOW set
*OVERFLOW = false; then return 0.
This is the inverse of sysinttostr. This is the inverse of sysinttostr.
On a normal return, set errno = 0. Sample call to this function:
On conversion error, return 0 and set errno = EINVAL.
On overflow, return an extreme value and set errno = ERANGE. */ char *s_end;
bool overflow;
idx_t i = stoint (s, &s_end, &overflow, 0, IDX_MAX);
if ((s_end == s) | *s_end | overflow)
diagnose_invalid (s);
This example uses "|" instead of "||" for fewer branches at runtime,
which tends to be more efficient on modern processors.
This function is named "stoint" instead of "strtoint" because
<string.h> reserves names beginning with "str". */
intmax_t intmax_t
strtosysint (char const *arg, char **arglim, intmax_t minval, uintmax_t maxval) stoint (char const *arg, char **arglim, bool *overflow,
intmax_t minval, uintmax_t maxval)
{ {
static_assert (INTMAX_MAX <= UINTMAX_MAX); static_assert (INTMAX_MAX <= UINTMAX_MAX);
char const *p = arg;
intmax_t i;
bool v = false;
errno = 0; if (c_isdigit (*p))
if (maxval <= INTMAX_MAX)
{ {
if (c_isdigit (arg[*arg == '-'])) if (minval < 0)
{ {
intmax_t i = strtoimax (arg, arglim, 10); i = *p - '0';
intmax_t imaxval = maxval;
if (minval <= i && i <= imaxval) while (c_isdigit (*++p))
return i; {
errno = ERANGE; v |= ckd_mul (&i, i, 10);
return i < minval ? minval : maxval; v |= ckd_add (&i, i, *p - '0');
}
v |= maxval < i;
if (v)
i = maxval;
} }
else
{
uintmax_t u = *p - '0';
while (c_isdigit (*++p))
{
v |= ckd_mul (&u, u, 10);
v |= ckd_add (&u, u, *p - '0');
}
v |= maxval < u;
if (v)
u = maxval;
i = represent_uintmax (u);
}
}
else if (minval < 0 && *p == '-' && c_isdigit (p[1]))
{
p++;
i = - (*p - '0');
while (c_isdigit (*++p))
{
v |= ckd_mul (&i, i, 10);
v |= ckd_sub (&i, i, *p - '0');
}
v |= i < minval;
if (v)
i = minval;
} }
else else
{ i = 0;
if (c_isdigit (*arg))
{
uintmax_t i = strtoumax (arg, arglim, 10);
if (i <= maxval)
return represent_uintmax (i);
errno = ERANGE;
return maxval;
}
}
errno = EINVAL; if (arglim)
return 0; *arglim = (char *) p;
if (overflow)
*overflow = v;
return i;
} }
/* Output fraction and trailing digits appropriate for a nanoseconds /* Output fraction and trailing digits appropriate for a nanoseconds
@@ -507,36 +558,13 @@ code_timespec (struct timespec t, char tsbuf[TIMESPEC_STRSIZE_BOUND])
struct timespec struct timespec
decode_timespec (char const *arg, char **arg_lim, bool parse_fraction) decode_timespec (char const *arg, char **arg_lim, bool parse_fraction)
{ {
time_t s = TYPE_MINIMUM (time_t);
int ns = -1; int ns = -1;
char const *p = arg; bool overflow;
bool negative = *arg == '-'; time_t s = stoint (arg, arg_lim, &overflow,
struct timespec r; TYPE_MINIMUM (time_t), TYPE_MAXIMUM (time_t));
char const *p = *arg_lim;
if (! c_isdigit (arg[negative])) if (p != arg)
errno = EINVAL;
else
{ {
errno = 0;
if (negative)
{
intmax_t i = strtoimax (arg, arg_lim, 10);
if (TYPE_SIGNED (time_t) ? TYPE_MINIMUM (time_t) <= i : 0 <= i)
s = i;
else
errno = ERANGE;
}
else
{
uintmax_t i = strtoumax (arg, arg_lim, 10);
if (i <= TYPE_MAXIMUM (time_t))
s = i;
else
errno = ERANGE;
}
p = *arg_lim;
ns = 0; ns = 0;
if (parse_fraction && *p == '.') if (parse_fraction && *p == '.')
@@ -550,36 +578,27 @@ decode_timespec (char const *arg, char **arg_lim, bool parse_fraction)
else else
trailing_nonzero |= *p != '0'; trailing_nonzero |= *p != '0';
*arg_lim = (char *) p;
while (digits < LOG10_BILLION) while (digits < LOG10_BILLION)
digits++, ns *= 10; digits++, ns *= 10;
if (negative) if (*arg == '-')
{ {
/* Convert "-1.10000000000001" to s == -2, ns == 89999999. /* Convert "-1.10000000000001" to s == -2, ns == 89999999.
I.e., truncate time stamps towards minus infinity while I.e., truncate time stamps towards minus infinity while
converting them to internal form. */ converting them to internal form. */
ns += trailing_nonzero; ns += trailing_nonzero;
if (ns != 0) if (ns != 0)
{ ns = ckd_sub (&s, s, 1) ? -1 : BILLION - ns;
if (s == TYPE_MINIMUM (time_t))
ns = -1;
else
{
s--;
ns = BILLION - ns;
}
}
} }
} }
if (errno == ERANGE) if (overflow)
ns = -1; ns = -1;
} }
*arg_lim = (char *) p; return (struct timespec) { .tv_sec = s, .tv_nsec = ns };
r.tv_sec = s;
r.tv_nsec = ns;
return r;
} }
/* File handling. */ /* File handling. */

View File

@@ -1249,20 +1249,10 @@ pax_dump_header (struct tar_sparse_file *file)
static bool static bool
decode_num (uintmax_t *num, char const *arg, uintmax_t maxval) decode_num (uintmax_t *num, char const *arg, uintmax_t maxval)
{ {
uintmax_t u;
char *arg_lim; char *arg_lim;
bool overflow;
if (!c_isdigit (*arg)) *num = stoint (arg, &arg_lim, &overflow, 0, maxval);
return false; return ! ((arg_lim == arg) | *arg_lim | overflow);
errno = 0;
u = strtoumax (arg, &arg_lim, 10);
if (! (u <= maxval && errno != ERANGE) || *arg_lim)
return false;
*num = u;
return true;
} }
static bool static bool

109
src/tar.c
View File

@@ -60,7 +60,7 @@ uintmax_t occurrence_option;
enum old_files old_files_option; enum old_files old_files_option;
bool keep_directory_symlink_option; bool keep_directory_symlink_option;
const char *listed_incremental_option; const char *listed_incremental_option;
int incremental_level; signed char incremental_level;
bool check_device_option; bool check_device_option;
struct mode_change *mode_option; struct mode_change *mode_option;
mode_t initial_umask; mode_t initial_umask;
@@ -1345,50 +1345,24 @@ expand_pax_option (struct tar_args *targs, const char *arg)
static uintmax_t static uintmax_t
parse_owner_group (char *arg, uintmax_t field_max, char const **name_option) parse_owner_group (char *arg, uintmax_t field_max, char const **name_option)
{ {
uintmax_t u = UINTMAX_MAX; char const *name = NULL;
char *end; char const *num = arg;
char const *name = 0;
char const *invalid_num = 0;
char *colon = strchr (arg, ':'); char *colon = strchr (arg, ':');
if (colon) if (colon)
{ {
char const *num = colon + 1; num = colon + 1;
*colon = '\0'; *colon = '\0';
if (*arg) if (arg != colon)
name = arg; name = arg;
if (num && (! (xstrtoumax (num, &end, 10, &u, "") == LONGINT_OK
&& u <= field_max)))
invalid_num = num;
}
else
{
uintmax_t u1;
switch ('0' <= *arg && *arg <= '9'
? xstrtoumax (arg, &end, 10, &u1, "")
: LONGINT_INVALID)
{
default:
name = arg;
break;
case LONGINT_OK:
if (u1 <= field_max)
{
u = u1;
break;
}
FALLTHROUGH;
case LONGINT_OVERFLOW:
invalid_num = arg;
break;
}
} }
if (invalid_num) bool overflow;
FATAL_ERROR ((0, 0, "%s: %s", quotearg_colon (invalid_num), char *end;
uintmax_t u = stoint (num, &end, &overflow, 0, field_max);
if ((end == num) | *end | overflow)
FATAL_ERROR ((0, 0, "%s: %s", quotearg_colon (num),
_("Invalid owner or group ID"))); _("Invalid owner or group ID")));
if (name) if (name_option)
*name_option = name; *name_option = name;
return u; return u;
} }
@@ -1498,14 +1472,15 @@ parse_opt (int key, char *arg, struct argp_state *state)
case 'b': case 'b':
{ {
uintmax_t u; bool overflow;
if (! (xstrtoumax (arg, 0, 10, &u, "") == LONGINT_OK char *end;
&& !ckd_add (&blocking_factor, u, 0) blocking_factor = stoint (arg, &end, &overflow, 0,
&& 0 < blocking_factor (min (IDX_MAX, min (SSIZE_MAX, SIZE_MAX))
&& !ckd_mul (&record_size, u, BLOCKSIZE) / BLOCKSIZE));
&& record_size <= min (SSIZE_MAX, SIZE_MAX))) if ((end == arg) | *end | overflow | !blocking_factor)
USAGE_ERROR ((0, 0, "%s: %s", quotearg_colon (arg), USAGE_ERROR ((0, 0, "%s: %s", quotearg_colon (arg),
_("Invalid blocking factor"))); _("Invalid blocking factor")));
record_size = blocking_factor * BLOCKSIZE;
} }
break; break;
@@ -1712,9 +1687,9 @@ parse_opt (int key, char *arg, struct argp_state *state)
case LEVEL_OPTION: case LEVEL_OPTION:
{ {
uintmax_t u; char *end;
if (! (xstrtoumax (arg, nullptr, 10, &u, "") == LONGINT_OK incremental_level = stoint (arg, &end, NULL, 0, 1);
&& ckd_add (&incremental_level, u, 0))) if ((end == arg) | *end)
USAGE_ERROR ((0, 0, _("Invalid incremental level value"))); USAGE_ERROR ((0, 0, _("Invalid incremental level value")));
} }
break; break;
@@ -1832,26 +1807,11 @@ parse_opt (int key, char *arg, struct argp_state *state)
sparse_option = true; sparse_option = true;
{ {
char *p; char *p;
bool ok; bool vmajor, vminor;
switch (xstrtoimax (arg, &p, 10, &tar_sparse_major, "")) tar_sparse_major = stoint (arg, &p, &vmajor, 0, INTMAX_MAX);
{ if ((p != arg) & (*p == '.'))
case LONGINT_OK: tar_sparse_minor = stoint (p + 1, &p, &vminor, 0, INTMAX_MAX);
tar_sparse_minor = 0; if ((p == arg) | *p | vmajor | vminor)
ok = 0 <= tar_sparse_major;
break;
case LONGINT_INVALID_SUFFIX_CHAR:
ok = (*p == '.'
&& (xstrtoimax (p + 1, nullptr, 10, &tar_sparse_minor, "")
== LONGINT_OK)
&& 0 <= tar_sparse_minor && 0 <= tar_sparse_major);
break;
default:
ok = false;
break;
}
if (!ok)
USAGE_ERROR ((0, 0, _("Invalid sparse version value"))); USAGE_ERROR ((0, 0, _("Invalid sparse version value")));
} }
break; break;
@@ -1949,8 +1909,8 @@ parse_opt (int key, char *arg, struct argp_state *state)
checkpoint_compile_action ("."); checkpoint_compile_action (".");
arg++; arg++;
} }
checkpoint_option = strtoimax (arg, &p, 0); checkpoint_option = stoint (arg, &p, NULL, 0, INTMAX_MAX);
if (*p || checkpoint_option <= 0) if (*p | (checkpoint_option <= 0))
FATAL_ERROR ((0, 0, FATAL_ERROR ((0, 0,
_("invalid --checkpoint value"))); _("invalid --checkpoint value")));
} }
@@ -2058,10 +2018,9 @@ parse_opt (int key, char *arg, struct argp_state *state)
occurrence_option = 1; occurrence_option = 1;
else else
{ {
uintmax_t u; char *end;
if (xstrtoumax (arg, 0, 10, &u, "") == LONGINT_OK) occurrence_option = stoint (arg, &end, NULL, 0, UINTMAX_MAX);
occurrence_option = u; if (*end)
else
FATAL_ERROR ((0, 0, "%s: %s", quotearg_colon (arg), FATAL_ERROR ((0, 0, "%s: %s", quotearg_colon (arg),
_("Invalid number"))); _("Invalid number")));
} }
@@ -2172,9 +2131,9 @@ parse_opt (int key, char *arg, struct argp_state *state)
case STRIP_COMPONENTS_OPTION: case STRIP_COMPONENTS_OPTION:
{ {
uintmax_t u; char *end;
if (! (xstrtoumax (arg, 0, 10, &u, "") == LONGINT_OK strip_name_components = stoint (arg, &end, NULL, 0, SIZE_MAX);
&& !ckd_add (&strip_name_components, u, 0))) if (*end)
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")));
} }

View File

@@ -250,10 +250,7 @@ parse_transform_expr (const char *expr)
case '5': case '6': case '7': case '8': case '9': case '5': case '6': case '7': case '8': case '9':
{ {
char *endp; char *endp;
intmax_t match_number = strtoimax (p, &endp, 10); tf->match_number = stoint (p, &endp, NULL, 0, IDX_MAX);
assume (0 <= match_number);
if (ckd_add (&tf->match_number, match_number, 0))
tf->match_number = IDX_MAX;
p = endp - 1; p = endp - 1;
} }
break; break;
@@ -302,8 +299,7 @@ parse_transform_expr (const char *expr)
case '0': case '1': case '2': case '3': case '4': case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9': case '5': case '6': case '7': case '8': case '9':
{ {
intmax_t n = strtoimax (cur, &cur, 10); idx_t n = stoint (cur, &cur, NULL, 0, IDX_MAX);
assume (0 <= n);
if (tf->regex.re_nsub < n) if (tf->regex.re_nsub < n)
USAGE_ERROR ((0, 0, _("Invalid transform replacement:" USAGE_ERROR ((0, 0, _("Invalid transform replacement:"
" back reference out of range"))); " back reference out of range")));

View File

@@ -608,27 +608,30 @@ decode_record (struct xheader *xhdr,
{ {
char *start = *ptr; char *start = *ptr;
char *p = start; char *p = start;
size_t len;
char *len_lim; char *len_lim;
char const *keyword; char const *keyword;
char *nextp; char *nextp;
size_t len_max = xhdr->buffer + xhdr->size - start; idx_t len_max = xhdr->buffer + xhdr->size - start;
while (*p == ' ' || *p == '\t') while (*p == ' ' || *p == '\t')
p++; p++;
if (! c_isdigit (*p)) idx_t len = stoint (p, &len_lim, NULL, 0, IDX_MAX);
if (len_lim == p)
{ {
/* The length is missing.
FIXME: Comment why this is diagnosed only if (*p), or change code. */
if (*p) if (*p)
ERROR ((0, 0, _("Malformed extended header: missing length"))); ERROR ((0, 0, _("Malformed extended header: missing length")));
return false; return false;
} }
len = strtoumax (p, &len_lim, 10);
if (len_max < len) if (len_max < len)
{ {
int len_len = len_lim - p; /* Avoid giant diagnostics, as this won't help user. */
int len_len = min (len_lim - p, 1000);
ERROR ((0, 0, _("Extended header length %.*s is out of range"), ERROR ((0, 0, _("Extended header length %.*s is out of range"),
len_len, p)); len_len, p));
return false; return false;
@@ -1087,16 +1090,17 @@ decode_signed_num (intmax_t *num, char const *arg,
char const *keyword) char const *keyword)
{ {
char *arg_lim; char *arg_lim;
intmax_t u = strtosysint (arg, &arg_lim, minval, maxval); bool overflow;
intmax_t u = stoint (arg, &arg_lim, &overflow, minval, maxval);
if (errno == EINVAL || *arg_lim) if ((arg_lim == arg) | *arg_lim)
{ {
ERROR ((0, 0, _("Malformed extended header: invalid %s=%s"), ERROR ((0, 0, _("Malformed extended header: invalid %s=%s"),
keyword, arg)); keyword, arg));
return false; return false;
} }
if (errno == ERANGE) if (overflow)
{ {
out_of_range_header (keyword, arg, minval, maxval); out_of_range_header (keyword, arg, minval, maxval);
return false; return false;
@@ -1433,33 +1437,26 @@ sparse_map_decoder (struct tar_stat_info *st,
char const *arg, char const *arg,
MAYBE_UNUSED size_t size) MAYBE_UNUSED size_t size)
{ {
int offset = 1; bool offset = true;
struct sp_array e; struct sp_array e;
st->sparse_map_avail = 0; st->sparse_map_avail = 0;
while (1) while (true)
{ {
intmax_t u;
char *delim; char *delim;
bool overflow;
if (!c_isdigit (*arg)) off_t u = stoint (arg, &delim, &overflow, 0, TYPE_MAXIMUM (off_t));
if (delim == arg)
{ {
ERROR ((0, 0, _("Malformed extended header: invalid %s=%s"), ERROR ((0, 0, _("Malformed extended header: invalid %s=%s"),
keyword, arg)); keyword, arg));
return; return;
} }
errno = 0;
u = strtoimax (arg, &delim, 10);
if (TYPE_MAXIMUM (off_t) < u)
{
u = TYPE_MAXIMUM (off_t);
errno = ERANGE;
}
if (offset) if (offset)
{ {
e.offset = u; e.offset = u;
if (errno == ERANGE) if (overflow)
{ {
out_of_range_header (keyword, arg, 0, TYPE_MAXIMUM (off_t)); out_of_range_header (keyword, arg, 0, TYPE_MAXIMUM (off_t));
return; return;
@@ -1468,7 +1465,7 @@ sparse_map_decoder (struct tar_stat_info *st,
else else
{ {
e.numbytes = u; e.numbytes = u;
if (errno == ERANGE) if (overflow)
{ {
out_of_range_header (keyword, arg, 0, TYPE_MAXIMUM (off_t)); out_of_range_header (keyword, arg, 0, TYPE_MAXIMUM (off_t));
return; return;