aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorAlexey Dobriyan <adobriyan@gmail.com>2015-07-24 09:11:44 +1000
committerStephen Rothwell <sfr@canb.auug.org.au>2015-07-24 09:11:44 +1000
commita8e4db27688468a8705fd377f8dac8d90cca81ec (patch)
treea7bb293ef86869b5f415f71199a8693f6ad929c8 /lib
parent5c1a43afd7320e3375af0f3aeb15c713b5606d1f (diff)
lib/: add parse_integer() (replacement for simple_strto*())
kstrto*() and kstrto*_from_user() family of functions were added to help with parsing one integer written as string to proc/sysfs/debugfs files. But they have a limitation: string passed must end with \0 or \n\0. There are enough places where kstrto*() functions can't be used because of this limitation. Trivial example: major:minor "%u:%u". Currently the only way to parse everything is simple_strto*() functions. But they are suboptimal: * they do not detect overflow (can be fixed, but no one bothered since ~0.99.11), * there are only 4 of them -- long and "long long" versions, This leads to silent truncation in the most simple case: val = strtoul(s, NULL, 0); * half of the people think that "char **endp" argument is necessary and add unnecessary variable. OpenBSD people, fed up with how complex correct integer parsing is, added strtonum(3) to fixup for deficiencies of libc-style integer parsing: http://www.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man3/strtonum.3?query=strtonum&arch=i386 It'd be OK to copy that but it relies on "errno" and fixed strings as error reporting channel which I think is not OK for kernel. strtonum() also doesn't report number of characted consumed. What to do? Enter parse_integer(). int parse_integer(const char *s, unsigned int base, T *val); Rationale: * parse_integer() is exactly 1 (one) interface not 4 or many, one for each type. * parse_integer() reports -E errors reliably in a canonical kernel way: rv = parse_integer(str, 10, &val); if (rv < 0) return rv; * parse_integer() writes result only if there were no errors, at least one digit has to be consumed, * parse_integer doesn't mix error reporting channel and value channel, it does mix error and number-of-character-consumed channel, though. * parse_integer() reports number of characters consumed, makes parsing multiple values easy: rv = parse_integer(str, 0, &val1); if (rv < 0) return rv; str += rv; if (*str++ != ':') return -EINVAL; rv = parse_integer(str, 0, &val2); if (rv < 0) return rv; if (str[rv] != '\0') return -EINVAL; There are several deficiencies in parse_integer() compared to strto*(): * can't be used in initializers: const T x = strtoul(); * can't be used with bitfields, * can't be used in expressions: x = strtol() * 1024; x = y = strtol(); x += strtol(); * currently there is no support for _Bool and at least one place where simple_strtoul() is directly assigned to _Bool variable. It is trivial to add, but not clear if it should only accept "0" and "1", because, say, module param code accepts 0, 1, y, n, Y and N. In defense of parse_integer() all I can say, is that using strtol() in expression or initializer promotes no error checking and thus probably should not be encouraged in C, language with no built-in error checking anyway. The amount of "x = y = strtol()" expressions in kernel is very small. The amount of direct usage in expression is not small, but can be counted as an acceptable loss. Misc suggestions and ideas from Rasmus Villemoes. Signed-off-by: Alexey Dobriyan <adobriyan@gmail.com> Cc: David Howells <dhowells@redhat.com> Cc: Jan Kara <jack@suse.cz> Cc: Joel Becker <jlbec@evilplan.org> Cc: Mark Fasheh <mfasheh@suse.com> Cc: Theodore Ts'o <tytso@mit.edu> Cc: Rasmus Villemoes <linux@rasmusvillemoes.dk> Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Diffstat (limited to 'lib')
-rw-r--r--lib/Makefile1
-rw-r--r--lib/kstrtox.c76
-rw-r--r--lib/kstrtox.h1
-rw-r--r--lib/parse-integer.c198
4 files changed, 221 insertions, 55 deletions
diff --git a/lib/Makefile b/lib/Makefile
index 6897b527581a..5c52e96cef1b 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -32,6 +32,7 @@ obj-$(CONFIG_TEST_STRING_HELPERS) += test-string_helpers.o
obj-y += hexdump.o
obj-$(CONFIG_TEST_HEXDUMP) += test-hexdump.o
obj-y += kstrtox.o
+obj-y += parse-integer.o
obj-$(CONFIG_TEST_BPF) += test_bpf.o
obj-$(CONFIG_TEST_FIRMWARE) += test_firmware.o
obj-$(CONFIG_TEST_KASAN) += test_kasan.o
diff --git a/lib/kstrtox.c b/lib/kstrtox.c
index 94be244e8441..652fd6be066a 100644
--- a/lib/kstrtox.c
+++ b/lib/kstrtox.c
@@ -20,22 +20,6 @@
#include <asm/uaccess.h>
#include "kstrtox.h"
-const char *_parse_integer_fixup_radix(const char *s, unsigned int *base)
-{
- if (*base == 0) {
- if (s[0] == '0') {
- if (_tolower(s[1]) == 'x' && isxdigit(s[2]))
- *base = 16;
- else
- *base = 8;
- } else
- *base = 10;
- }
- if (*base == 16 && s[0] == '0' && _tolower(s[1]) == 'x')
- s += 2;
- return s;
-}
-
/*
* Convert non-negative integer string representation in explicitly given radix
* to an integer.
@@ -83,26 +67,6 @@ unsigned int _parse_integer(const char *s, unsigned int base, unsigned long long
return rv;
}
-static int _kstrtoull(const char *s, unsigned int base, unsigned long long *res)
-{
- unsigned long long _res;
- unsigned int rv;
-
- s = _parse_integer_fixup_radix(s, &base);
- rv = _parse_integer(s, base, &_res);
- if (rv & KSTRTOX_OVERFLOW)
- return -ERANGE;
- if (rv == 0)
- return -EINVAL;
- s += rv;
- if (*s == '\n')
- s++;
- if (*s)
- return -EINVAL;
- *res = _res;
- return 0;
-}
-
/**
* kstrtoull - convert a string to an unsigned long long
* @s: The start of the string. The string must be null-terminated, and may also
@@ -121,9 +85,19 @@ static int _kstrtoull(const char *s, unsigned int base, unsigned long long *res)
*/
int kstrtoull(const char *s, unsigned int base, unsigned long long *res)
{
- if (s[0] == '+')
+ unsigned long long _res;
+ int rv;
+
+ rv = parse_integer(s, base, &_res);
+ if (rv < 0)
+ return rv;
+ s += rv;
+ if (*s == '\n')
s++;
- return _kstrtoull(s, base, res);
+ if (*s)
+ return -EINVAL;
+ *res = _res;
+ return 0;
}
EXPORT_SYMBOL(kstrtoull);
@@ -145,24 +119,18 @@ EXPORT_SYMBOL(kstrtoull);
*/
int kstrtoll(const char *s, unsigned int base, long long *res)
{
- unsigned long long tmp;
+ long long _res;
int rv;
- if (s[0] == '-') {
- rv = _kstrtoull(s + 1, base, &tmp);
- if (rv < 0)
- return rv;
- if ((long long)-tmp > 0)
- return -ERANGE;
- *res = -tmp;
- } else {
- rv = kstrtoull(s, base, &tmp);
- if (rv < 0)
- return rv;
- if ((long long)tmp < 0)
- return -ERANGE;
- *res = tmp;
- }
+ rv = parse_integer(s, base, &_res);
+ if (rv < 0)
+ return rv;
+ s += rv;
+ if (*s == '\n')
+ s++;
+ if (*s)
+ return -EINVAL;
+ *res = _res;
return 0;
}
EXPORT_SYMBOL(kstrtoll);
diff --git a/lib/kstrtox.h b/lib/kstrtox.h
index f13eeeaf441d..7b1f447cbcc1 100644
--- a/lib/kstrtox.h
+++ b/lib/kstrtox.h
@@ -2,7 +2,6 @@
#define _LIB_KSTRTOX_H
#define KSTRTOX_OVERFLOW (1U << 31)
-const char *_parse_integer_fixup_radix(const char *s, unsigned int *base);
unsigned int _parse_integer(const char *s, unsigned int base, unsigned long long *res);
#endif
diff --git a/lib/parse-integer.c b/lib/parse-integer.c
new file mode 100644
index 000000000000..eb243079c218
--- /dev/null
+++ b/lib/parse-integer.c
@@ -0,0 +1,198 @@
+/*
+ * See parse_integer().
+ *
+ * Individual dispatch functions in this file aren't supposed to be used
+ * directly and thus aren't advertised and documented despited being exported.
+ *
+ * Do not use any function in this file for any reason.
+ */
+#include <linux/ctype.h>
+#include <linux/errno.h>
+#include <linux/export.h>
+#include <linux/kernel.h>
+#include <linux/math64.h>
+#include <linux/parse-integer.h>
+#include <asm/bug.h>
+
+const char *_parse_integer_fixup_radix(const char *s, unsigned int *base)
+{
+ if (*base == 0) {
+ if (s[0] == '0') {
+ if (_tolower(s[1]) == 'x' && isxdigit(s[2]))
+ *base = 16;
+ else
+ *base = 8;
+ } else
+ *base = 10;
+ }
+ if (*base == 16 && s[0] == '0' && _tolower(s[1]) == 'x')
+ s += 2;
+ BUG_ON(*base < 2 || *base > 16);
+ return s;
+}
+
+static int __parse_integer(const char *s, unsigned int base, unsigned long long *val)
+{
+ const char *s0 = s, *sd;
+ unsigned long long acc;
+
+ s = sd = _parse_integer_fixup_radix(s0, &base);
+ acc = 0;
+ while (*s) {
+ unsigned int d;
+
+ if ('0' <= *s && *s <= '9')
+ d = *s - '0';
+ else if ('a' <= _tolower(*s) && _tolower(*s) <= 'f')
+ d = _tolower(*s) - 'a' + 10;
+ else
+ break;
+ if (d >= base)
+ break;
+ /* Overflow can't happen early enough. */
+ if ((acc >> 60) && acc > div_u64(ULLONG_MAX - d, base))
+ return -ERANGE;
+ acc = acc * base + d;
+ s++;
+ }
+ /* At least one digit has to be converted. */
+ if (s == sd)
+ return -EINVAL;
+ *val = acc;
+ /* Radix 1 is not supported otherwise returned length can overflow. */
+ return s - s0;
+}
+
+int _parse_integer_ull(const char *s, unsigned int base, unsigned long long *val)
+{
+ char sign;
+ int rv;
+
+ sign = 0;
+ if (*s == '-')
+ return -EINVAL;
+ else if (*s == '+')
+ sign = *s++;
+
+ rv = __parse_integer(s, base, val);
+ if (rv < 0)
+ return rv;
+ return rv + !!sign;
+}
+EXPORT_SYMBOL(_parse_integer_ull);
+
+int _parse_integer_ll(const char *s, unsigned int base, long long *val)
+{
+ unsigned long long tmp;
+ char sign;
+ int rv;
+
+ sign = 0;
+ if (*s == '-' || *s == '+')
+ sign = *s++;
+
+ rv = __parse_integer(s, base, &tmp);
+ if (rv < 0)
+ return rv;
+ if (sign == '-') {
+ if ((long long)-tmp > 0)
+ return -ERANGE;
+ *val = -tmp;
+ } else {
+ if ((long long)tmp < 0)
+ return -ERANGE;
+ *val = tmp;
+ }
+ return rv + !!sign;
+}
+EXPORT_SYMBOL(_parse_integer_ll);
+
+int _parse_integer_u(const char *s, unsigned int base, unsigned int *val)
+{
+ unsigned long long tmp;
+ int rv;
+
+ rv = _parse_integer_ull(s, base, &tmp);
+ if (rv < 0)
+ return rv;
+ if (tmp != (unsigned int)tmp)
+ return -ERANGE;
+ *val = tmp;
+ return rv;
+}
+EXPORT_SYMBOL(_parse_integer_u);
+
+int _parse_integer_i(const char *s, unsigned int base, int *val)
+{
+ long long tmp;
+ int rv;
+
+ rv = _parse_integer_ll(s, base, &tmp);
+ if (rv < 0)
+ return rv;
+ if (tmp != (int)tmp)
+ return -ERANGE;
+ *val = tmp;
+ return rv;
+}
+EXPORT_SYMBOL(_parse_integer_i);
+
+int _parse_integer_us(const char *s, unsigned int base, unsigned short *val)
+{
+ unsigned long long tmp;
+ int rv;
+
+ rv = _parse_integer_ull(s, base, &tmp);
+ if (rv < 0)
+ return rv;
+ if (tmp != (unsigned short)tmp)
+ return -ERANGE;
+ *val = tmp;
+ return rv;
+}
+EXPORT_SYMBOL(_parse_integer_us);
+
+int _parse_integer_s(const char *s, unsigned int base, short *val)
+{
+ long long tmp;
+ int rv;
+
+ rv = _parse_integer_ll(s, base, &tmp);
+ if (rv < 0)
+ return rv;
+ if (tmp != (short)tmp)
+ return -ERANGE;
+ *val = tmp;
+ return rv;
+}
+EXPORT_SYMBOL(_parse_integer_s);
+
+int _parse_integer_uc(const char *s, unsigned int base, unsigned char *val)
+{
+ unsigned long long tmp;
+ int rv;
+
+ rv = _parse_integer_ull(s, base, &tmp);
+ if (rv < 0)
+ return rv;
+ if (tmp != (unsigned char)tmp)
+ return -ERANGE;
+ *val = tmp;
+ return rv;
+}
+EXPORT_SYMBOL(_parse_integer_uc);
+
+int _parse_integer_sc(const char *s, unsigned int base, signed char *val)
+{
+ long long tmp;
+ int rv;
+
+ rv = _parse_integer_ll(s, base, &tmp);
+ if (rv < 0)
+ return rv;
+ if (tmp != (signed char)tmp)
+ return -ERANGE;
+ *val = tmp;
+ return rv;
+}
+EXPORT_SYMBOL(_parse_integer_sc);