*
* - ``-EINVAL``: @buffer is not a proper numerical string
* - ``-EOVERFLOW``: results does not fit into 64 bits.
- * - ``-E2BIG ``: @buffer is not large
+ * - ``-E2BIG ``: @buffer is too large (not a valid number)
*/
int string_to_size(u64 *size, const char *buffer, size_t count)
{
* up into block size units so we don't support ZiB or YiB.
*/
static const char *const units_10[] = {
- "kB", "MB", "GB", "TB", "PB", "EB"
+ "kB", "MB", "GB", "TB", "PB", "EB",
};
static const char *const units_2[] = {
- "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"
+ "K", "M", "G", "T", "P", "E",
};
static const char *const *const units_str[] = {
[STRING_UNITS_2] = units_2,
[STRING_UNITS_10] = 1000,
[STRING_UNITS_2] = 1024,
};
- enum string_size_units unit;
+ enum string_size_units unit = STRING_UNITS_2;
u64 whole, blk_size = 1;
char kernbuf[22], *end;
size_t len = count;
int rc;
int i;
- if (count >= sizeof(kernbuf))
+ if (count >= sizeof(kernbuf)) {
+ CERROR("count %zd > buffer %zd\n", count, sizeof(kernbuf));
return -E2BIG;
+ }
*size = 0;
- /* 'iB' is used for based 2 numbers. If @buffer contains only a 'B'
- * or only numbers then we treat it as a direct number which doesn't
- * matter if its STRING_UNITS_2 or STRING_UNIT_10.
+ /* The "iB" suffix is optionally allowed for indicating base-2 numbers.
+ * If suffix is only "B" and not "iB" then we treat it as base-10.
*/
- unit = strstr(buffer, "iB") ? STRING_UNITS_2 : STRING_UNITS_10;
+ end = strstr(buffer, "B");
+ if (end && *(end - 1) != 'i')
+ unit = STRING_UNITS_10;
+
i = unit == STRING_UNITS_2 ? ARRAY_SIZE(units_2) - 1 :
ARRAY_SIZE(units_10) - 1;
do {
- end = strstr(buffer, units_str[unit][i]);
+ end = strnstr(buffer, units_str[unit][i], count);
if (end) {
for (; i >= 0; i--)
blk_size *= coeff[unit];
- len -= strlen(end);
+ len = end - buffer;
break;
}
} while (i--);
*/
if (!end) {
/* 'B' is only acceptable letter at this point */
- end = strchr(buffer, 'B');
+ end = strnchr(buffer, count, 'B');
if (end) {
- len -= strlen(end);
+ len = end - buffer;
if (count - len > 2 ||
- (count - len == 2 && strcmp(end, "B\n") != 0))
+ (count - len == 2 && strcmp(end, "B\n") != 0)) {
+ CDEBUG(D_INFO, "unknown suffix '%s'\n", buffer);
return -EINVAL;
+ }
}
/* kstrtoull will error out if it has non digits */
goto numbers_only;
}
- end = strchr(buffer, '.');
+ end = strnchr(buffer, count, '.');
if (end) {
/* need to limit 3 decimal places */
char rem[4] = "000";
int sysfs_memparse(const char *buffer, size_t count, u64 *val,
const char *defunit)
{
- char param[23];
+ const char *param = buffer;
+ char tmp_buf[23];
int rc;
- if (count >= sizeof(param))
- return -E2BIG;
-
count = strlen(buffer);
- if (count && buffer[count - 1] == '\n')
+ while (count > 0 && isspace(buffer[count - 1]))
count--;
if (!count)
- return -EINVAL;
+ RETURN(-EINVAL);
- if (isalpha(buffer[count - 1])) {
- if (buffer[count - 1] != 'B') {
- scnprintf(param, sizeof(param), "%.*siB",
- (int)count, buffer);
- } else {
- memcpy(param, buffer, sizeof(param));
+ /* If there isn't already a unit on this value, append @defunit.
+ * Units of 'B' don't affect the value, so don't bother adding.
+ */
+ if (!isalpha(buffer[count - 1]) && defunit[0] != 'B') {
+ if (count + 3 >= sizeof(tmp_buf)) {
+ CERROR("count %zd > size %zd\n", count, sizeof(param));
+ RETURN(-E2BIG);
}
- } else {
- scnprintf(param, sizeof(param), "%.*s%s", (int)count,
+
+ scnprintf(tmp_buf, sizeof(tmp_buf), "%.*s%s", (int)count,
buffer, defunit);
+ param = tmp_buf;
+ count = strlen(param);
}
- rc = string_to_size(val, param, strlen(param));
+ rc = string_to_size(val, param, count);
+
return rc < 0 ? rc : 0;
}
EXPORT_SYMBOL(sysfs_memparse);
-/* Obtains the conversion factor for the unit specified */
-static int get_mult(char unit, __u64 *mult)
-{
- __u64 units = 1;
-
- switch (unit) {
- /* peta, tera, giga, mega, and kilo */
- case 'p':
- case 'P':
- units <<= 10;
- /* fallthrough */
- case 't':
- case 'T':
- units <<= 10;
- /* fallthrough */
- case 'g':
- case 'G':
- units <<= 10;
- /* fallthrough */
- case 'm':
- case 'M':
- units <<= 10;
- /* fallthrough */
- case 'k':
- case 'K':
- units <<= 10;
- break;
- /* some tests expect % to be accepted */
- case '%':
- units = 1;
- break;
- default:
- return -EINVAL;
- }
-
- *mult = units;
-
- return 0;
-}
-
-/*
- * Ensures the numeric string is valid. The function provides the final
- * multiplier in the case a unit exists at the end of the string. It also
- * locates the start of the whole and fractional parts (if any). This
- * function modifies the string so kstrtoull can be used to parse both
- * the whole and fraction portions. This function also figures out
- * the base of the number.
- */
-static int preprocess_numeric_str(char *buffer, __u64 *mult, __u64 def_mult,
- bool allow_units, char **whole, char **frac,
- unsigned int *base)
-{
- bool hit_decimal = false;
- bool hit_unit = false;
- int rc = 0;
- char *start;
- *mult = def_mult;
- *whole = NULL;
- *frac = NULL;
- *base = 10;
-
- /* a hex string if it starts with "0x" */
- if (buffer[0] == '0' && tolower(buffer[1]) == 'x') {
- *base = 16;
- buffer += 2;
- }
-
- start = buffer;
-
- while (*buffer) {
- /* allow for a single new line before the null terminator */
- if (*buffer == '\n') {
- *buffer = '\0';
- buffer++;
-
- if (*buffer)
- return -EINVAL;
-
- break;
- }
-
- /* any chars after our unit indicates a malformed string */
- if (hit_unit)
- return -EINVAL;
-
- /* ensure we only hit one decimal */
- if (*buffer == '.') {
- if (hit_decimal)
- return -EINVAL;
-
- /* if past start, there's a whole part */
- if (start != buffer)
- *whole = start;
-
- *buffer = '\0';
- start = buffer + 1;
- hit_decimal = true;
- } else if (!isdigit(*buffer) &&
- !(*base == 16 && isxdigit(*buffer))) {
- if (allow_units) {
- /* if we allow units, attempt to get mult */
- hit_unit = true;
- rc = get_mult(*buffer, mult);
- if (rc)
- return rc;
-
- /* string stops here, but keep processing */
- *buffer = '\0';
- } else {
- /* bad string */
- return -EINVAL;
- }
- }
-
- buffer++;
- }
-
- if (hit_decimal) {
- /* hit a decimal, make sure there's a fractional part */
- if (!*start)
- return -EINVAL;
-
- *frac = start;
- } else {
- /* didn't hit a decimal, but may have a whole part */
- if (start != buffer && *start)
- *whole = start;
- }
-
- /* malformed string if we didn't get anything */
- if (!*frac && !*whole)
- return -EINVAL;
-
- return 0;
-}
-
-/*
- * Parses a numeric string which can contain a whole and fraction portion
- * into a __u64. Accepts a multiplier to apply to the value parsed. Also
- * allows the string to have a unit at the end. The function handles
- * wrapping of the final unsigned value.
- */
-static int str_to_u64_parse(char *buffer, unsigned long count,
- __u64 *val, __u64 def_mult, bool allow_units)
-{
- __u64 whole = 0;
- __u64 frac = 0;
- unsigned int frac_d = 1;
- __u64 wrap_indicator = ULLONG_MAX;
- int rc = 0;
- __u64 mult;
- char *strwhole;
- char *strfrac;
- unsigned int base = 10;
-
- rc = preprocess_numeric_str(buffer, &mult, def_mult, allow_units,
- &strwhole, &strfrac, &base);
-
- if (rc)
- return rc;
-
- if (mult == 0) {
- *val = 0;
- return 0;
- }
-
- /* the multiplier limits how large the value can be */
- wrap_indicator = div64_u64(wrap_indicator, mult);
-
- if (strwhole) {
- rc = kstrtoull(strwhole, base, &whole);
- if (rc)
- return rc;
-
- if (whole > wrap_indicator)
- return -ERANGE;
-
- whole *= mult;
- }
-
- if (strfrac) {
- if (strlen(strfrac) > 10)
- strfrac[10] = '\0';
-
- rc = kstrtoull(strfrac, base, &frac);
- if (rc)
- return rc;
-
- /* determine power of fractional portion */
- while (*strfrac) {
- frac_d *= base;
- strfrac++;
- }
-
- /* fractional portion is too large to perform calculation */
- if (frac > wrap_indicator)
- return -ERANGE;
-
- frac *= mult;
- do_div(frac, frac_d);
- }
-
- /* check that the sum of whole and fraction fits in u64 */
- if (whole > (ULLONG_MAX - frac))
- return -ERANGE;
-
- *val = whole + frac;
-
- return 0;
-}
-
-/*
- * This function parses numeric/hex strings into __s64. It accepts a multiplier
- * which will apply to the value parsed. It also can allow the string to
- * have a unit as the last character. The function handles overflow/underflow
- * of the signed integer.
- */
-int lu_str_to_s64(char *buffer, unsigned long count, __s64 *val, char defunit)
-{
- __u64 mult = 1;
- __u64 tmp;
- unsigned int offset = 0;
- int signed sign = 1;
- __u64 max = LLONG_MAX;
- int rc = 0;
-
- if (defunit != '1') {
- rc = get_mult(defunit, &mult);
- if (rc)
- return rc;
- }
-
- /* keep track of our sign */
- if (*buffer == '-') {
- sign = -1;
- offset++;
- /* equivalent to max = -LLONG_MIN, avoids overflow */
- max++;
- }
-
- rc = str_to_u64_parse(buffer + offset, count - offset,
- &tmp, mult, true);
- if (rc)
- return rc;
-
- /* check for overflow/underflow */
- if (max < tmp)
- return -ERANGE;
-
- *val = (__s64)tmp * sign;
-
- return 0;
-}
-EXPORT_SYMBOL(lu_str_to_s64);
-
-/* identical to s64 version, but does not handle overflow */
-static int str_to_u64_internal(const char __user *buffer, unsigned long count,
- __u64 *val, __u64 def_mult, bool allow_units)
-{
- char kernbuf[22];
- unsigned int offset = 0;
- int rc = 0;
-
- if (count > (sizeof(kernbuf) - 1))
- return -EINVAL;
-
- if (copy_from_user(kernbuf, buffer, count))
- return -EFAULT;
-
- kernbuf[count] = '\0';
-
- rc = str_to_u64_parse(kernbuf + offset, count - offset,
- val, def_mult, allow_units);
- if (rc)
- return rc;
-
- return 0;
-}
-/**
- * Convert a user string into a signed 64 bit number. This function produces
- * an error when the value parsed from the string times multiplier underflows or
- * overflows. This function only accepts strings that contains digits, an
- * optional decimal, and a char representing a unit at the end. If a unit is
- * specified in the string, the multiplier provided by the caller is ignored.
- * This function can also accept hexadecimal strings which are prefixed with
- * "0x".
- *
- * \param[in] buffer string consisting of numbers, a decimal, and a unit
- * \param[in] count buffer length
- * \param[in] val if successful, the value represented by the string
- * \param[in] defunit default unit if string doesn't contain one
- *
- * \retval 0 on success
- * \retval negative number on error
- */
-int lprocfs_str_with_units_to_s64(const char __user *buffer,
- unsigned long count, __s64 *val, char defunit)
-{
- char kernbuf[22];
-
- if (count > (sizeof(kernbuf) - 1))
- return -EINVAL;
-
- if (copy_from_user(kernbuf, buffer, count))
- return -EFAULT;
-
- kernbuf[count] = '\0';
-
- return lu_str_to_s64(kernbuf, count, val, defunit);
-}
-EXPORT_SYMBOL(lprocfs_str_with_units_to_s64);
-
-/* identical to s64 version above, but does not handle overflow */
-int lprocfs_str_with_units_to_u64(const char __user *buffer,
- unsigned long count, __u64 *val, char defunit)
-{
- __u64 mult = 1;
- int rc;
-
- if (defunit != '1') {
- rc = get_mult(defunit, &mult);
- if (rc)
- return rc;
- }
-
- return str_to_u64_internal(buffer, count, val, mult, true);
-}
-EXPORT_SYMBOL(lprocfs_str_with_units_to_u64);
-
char *lprocfs_strnstr(const char *s1, const char *s2, size_t len)
{
size_t l2;