/* A more useful interface to strtol.
Copyright (C) 1995-2024
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 . */
/* Written by Jim Meyering. */
#include
/* Some pre-ANSI implementations (e.g. SunOS 4)
need stderr defined if assertion checking is enabled. */
#include
#include
#include
#include
#include
#include
#include
#include "lib/strutil.h"
/*** global variables ****************************************************************************/
/*** file scope macro definitions ****************************************************************/
/*** file scope type declarations ****************************************************************/
/*** forward declarations (file scope functions) *************************************************/
/*** file scope variables ************************************************************************/
/* --------------------------------------------------------------------------------------------- */
/*** file scope functions ************************************************************************/
/* --------------------------------------------------------------------------------------------- */
static strtol_error_t
bkm_scale (uintmax_t *x, int scale_factor)
{
if (UINTMAX_MAX / scale_factor < *x)
{
*x = UINTMAX_MAX;
return LONGINT_OVERFLOW;
}
*x *= scale_factor;
return LONGINT_OK;
}
/* --------------------------------------------------------------------------------------------- */
static strtol_error_t
bkm_scale_by_power (uintmax_t *x, int base, int power)
{
strtol_error_t err = LONGINT_OK;
while (power-- != 0)
err |= bkm_scale (x, base);
return err;
}
/* --------------------------------------------------------------------------------------------- */
/*** public functions ****************************************************************************/
/* --------------------------------------------------------------------------------------------- */
strtol_error_t
xstrtoumax (const char *s, char **ptr, int base, uintmax_t *val, const char *valid_suffixes)
{
char *t_ptr;
char **p;
uintmax_t tmp;
strtol_error_t err = LONGINT_OK;
g_assert (0 <= base && base <= 36);
p = (ptr != NULL ? ptr : &t_ptr);
{
const char *q = s;
unsigned char ch = *q;
while (isspace (ch))
ch = *++q;
if (ch == '-')
return LONGINT_INVALID;
}
errno = 0;
tmp = strtol (s, p, base);
if (*p == s)
{
/* If there is no number but there is a valid suffix, assume the
number is 1. The string is invalid otherwise. */
if (valid_suffixes != NULL && **p != '\0' && strchr (valid_suffixes, **p) != NULL)
tmp = 1;
else
return LONGINT_INVALID;
}
else if (errno != 0)
{
if (errno != ERANGE)
return LONGINT_INVALID;
err = LONGINT_OVERFLOW;
}
/* Let valid_suffixes == NULL mean "allow any suffix". */
/* FIXME: update all callers except the ones that allow suffixes
after the number, changing last parameter NULL to "". */
if (valid_suffixes == NULL)
{
*val = tmp;
return err;
}
if (**p != '\0')
{
int suffixes = 1;
strtol_error_t overflow;
if (strchr (valid_suffixes, **p) == NULL)
{
*val = tmp;
return err | LONGINT_INVALID_SUFFIX_CHAR;
}
base = 1024;
switch (**p)
{
case 'E':
case 'G':
case 'g':
case 'k':
case 'K':
case 'M':
case 'm':
case 'P':
case 'Q':
case 'R':
case 'T':
case 't':
case 'Y':
case 'Z':
if (strchr (valid_suffixes, '0') != NULL)
{
/* The "valid suffix" '0' is a special flag meaning that
an optional second suffix is allowed, which can change
the base. A suffix "B" (e.g. "100MB") stands for a power
of 1000, whereas a suffix "iB" (e.g. "100MiB") stands for
a power of 1024. If no suffix (e.g. "100M"), assume
power-of-1024. */
switch (p[0][1])
{
case 'i':
if (p[0][2] == 'B')
suffixes += 2;
break;
case 'B':
case 'D': /* 'D' is obsolescent */
base = 1000;
suffixes++;
break;
default:
break;
}
}
break;
default:
break;
}
switch (**p)
{
case 'b':
overflow = bkm_scale (&tmp, 512);
break;
case 'B':
/* This obsolescent first suffix is distinct from the 'B'
second suffix above. E.g., 'tar -L 1000B' means change
the tape after writing 1000 KiB of data. */
overflow = bkm_scale (&tmp, 1024);
break;
case 'c':
overflow = LONGINT_OK;
break;
case 'E': /* exa or exbi */
overflow = bkm_scale_by_power (&tmp, base, 6);
break;
case 'G': /* giga or gibi */
case 'g': /* 'g' is undocumented; for compatibility only */
overflow = bkm_scale_by_power (&tmp, base, 3);
break;
case 'k': /* kilo */
case 'K': /* kibi */
overflow = bkm_scale_by_power (&tmp, base, 1);
break;
case 'M': /* mega or mebi */
case 'm': /* 'm' is undocumented; for compatibility only */
overflow = bkm_scale_by_power (&tmp, base, 2);
break;
case 'P': /* peta or pebi */
overflow = bkm_scale_by_power (&tmp, base, 5);
break;
case 'Q': /* quetta or 2**100 */
overflow = bkm_scale_by_power (&tmp, base, 10);
break;
case 'R': /* ronna or 2**90 */
overflow = bkm_scale_by_power (&tmp, base, 9);
break;
case 'T': /* tera or tebi */
case 't': /* 't' is undocumented; for compatibility only */
overflow = bkm_scale_by_power (&tmp, base, 4);
break;
case 'w':
overflow = bkm_scale (&tmp, 2);
break;
case 'Y': /* yotta or 2**80 */
overflow = bkm_scale_by_power (&tmp, base, 8);
break;
case 'Z': /* zetta or 2**70 */
overflow = bkm_scale_by_power (&tmp, base, 7);
break;
default:
*val = tmp;
return err | LONGINT_INVALID_SUFFIX_CHAR;
}
err |= overflow;
*p += suffixes;
if (**p != '\0')
err |= LONGINT_INVALID_SUFFIX_CHAR;
}
*val = tmp;
return err;
}
/* --------------------------------------------------------------------------------------------- */