mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-11-28 22:54:05 +08:00
c196e32a11
There is quite a lot of code which does copy_from_user() + strict_strto*() or simple_strto*() combo in slightly different ways. Before doing conversions all over tree, let's get final API correct. Enter kstrtoull_from_user() and friends. Typical code which uses them looks very simple: TYPE val; int rv; rv = kstrtoTYPE_from_user(buf, count, 0, &val); if (rv < 0) return rv; [use val] return count; There is a tiny semantic difference from the plain kstrto*() API -- the latter allows any amount of leading zeroes, while the former copies data into buffer on stack and thus allows leading zeroes as long as it fits into buffer. This shouldn't be a problem for typical usecase "echo 42 > /proc/x". The point is to make reading one integer from userspace _very_ simple and very bug free. Signed-off-by: Alexey Dobriyan <adobriyan@gmail.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
251 lines
5.1 KiB
C
251 lines
5.1 KiB
C
/*
|
|
* Convert integer string representation to an integer.
|
|
* If an integer doesn't fit into specified type, -E is returned.
|
|
*
|
|
* Integer starts with optional sign.
|
|
* kstrtou*() functions do not accept sign "-".
|
|
*
|
|
* Radix 0 means autodetection: leading "0x" implies radix 16,
|
|
* leading "0" implies radix 8, otherwise radix is 10.
|
|
* Autodetection hints work after optional sign, but not before.
|
|
*
|
|
* If -E is returned, result is not touched.
|
|
*/
|
|
#include <linux/ctype.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/math64.h>
|
|
#include <linux/module.h>
|
|
#include <linux/types.h>
|
|
#include <asm/uaccess.h>
|
|
|
|
static inline char _tolower(const char c)
|
|
{
|
|
return c | 0x20;
|
|
}
|
|
|
|
static int _kstrtoull(const char *s, unsigned int base, unsigned long long *res)
|
|
{
|
|
unsigned long long acc;
|
|
int ok;
|
|
|
|
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;
|
|
|
|
acc = 0;
|
|
ok = 0;
|
|
while (*s) {
|
|
unsigned int val;
|
|
|
|
if ('0' <= *s && *s <= '9')
|
|
val = *s - '0';
|
|
else if ('a' <= _tolower(*s) && _tolower(*s) <= 'f')
|
|
val = _tolower(*s) - 'a' + 10;
|
|
else if (*s == '\n' && *(s + 1) == '\0')
|
|
break;
|
|
else
|
|
return -EINVAL;
|
|
|
|
if (val >= base)
|
|
return -EINVAL;
|
|
if (acc > div_u64(ULLONG_MAX - val, base))
|
|
return -ERANGE;
|
|
acc = acc * base + val;
|
|
ok = 1;
|
|
|
|
s++;
|
|
}
|
|
if (!ok)
|
|
return -EINVAL;
|
|
*res = acc;
|
|
return 0;
|
|
}
|
|
|
|
int kstrtoull(const char *s, unsigned int base, unsigned long long *res)
|
|
{
|
|
if (s[0] == '+')
|
|
s++;
|
|
return _kstrtoull(s, base, res);
|
|
}
|
|
EXPORT_SYMBOL(kstrtoull);
|
|
|
|
int kstrtoll(const char *s, unsigned int base, long long *res)
|
|
{
|
|
unsigned long long tmp;
|
|
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;
|
|
}
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(kstrtoll);
|
|
|
|
/* Internal, do not use. */
|
|
int _kstrtoul(const char *s, unsigned int base, unsigned long *res)
|
|
{
|
|
unsigned long long tmp;
|
|
int rv;
|
|
|
|
rv = kstrtoull(s, base, &tmp);
|
|
if (rv < 0)
|
|
return rv;
|
|
if (tmp != (unsigned long long)(unsigned long)tmp)
|
|
return -ERANGE;
|
|
*res = tmp;
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(_kstrtoul);
|
|
|
|
/* Internal, do not use. */
|
|
int _kstrtol(const char *s, unsigned int base, long *res)
|
|
{
|
|
long long tmp;
|
|
int rv;
|
|
|
|
rv = kstrtoll(s, base, &tmp);
|
|
if (rv < 0)
|
|
return rv;
|
|
if (tmp != (long long)(long)tmp)
|
|
return -ERANGE;
|
|
*res = tmp;
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(_kstrtol);
|
|
|
|
int kstrtouint(const char *s, unsigned int base, unsigned int *res)
|
|
{
|
|
unsigned long long tmp;
|
|
int rv;
|
|
|
|
rv = kstrtoull(s, base, &tmp);
|
|
if (rv < 0)
|
|
return rv;
|
|
if (tmp != (unsigned long long)(unsigned int)tmp)
|
|
return -ERANGE;
|
|
*res = tmp;
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(kstrtouint);
|
|
|
|
int kstrtoint(const char *s, unsigned int base, int *res)
|
|
{
|
|
long long tmp;
|
|
int rv;
|
|
|
|
rv = kstrtoll(s, base, &tmp);
|
|
if (rv < 0)
|
|
return rv;
|
|
if (tmp != (long long)(int)tmp)
|
|
return -ERANGE;
|
|
*res = tmp;
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(kstrtoint);
|
|
|
|
int kstrtou16(const char *s, unsigned int base, u16 *res)
|
|
{
|
|
unsigned long long tmp;
|
|
int rv;
|
|
|
|
rv = kstrtoull(s, base, &tmp);
|
|
if (rv < 0)
|
|
return rv;
|
|
if (tmp != (unsigned long long)(u16)tmp)
|
|
return -ERANGE;
|
|
*res = tmp;
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(kstrtou16);
|
|
|
|
int kstrtos16(const char *s, unsigned int base, s16 *res)
|
|
{
|
|
long long tmp;
|
|
int rv;
|
|
|
|
rv = kstrtoll(s, base, &tmp);
|
|
if (rv < 0)
|
|
return rv;
|
|
if (tmp != (long long)(s16)tmp)
|
|
return -ERANGE;
|
|
*res = tmp;
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(kstrtos16);
|
|
|
|
int kstrtou8(const char *s, unsigned int base, u8 *res)
|
|
{
|
|
unsigned long long tmp;
|
|
int rv;
|
|
|
|
rv = kstrtoull(s, base, &tmp);
|
|
if (rv < 0)
|
|
return rv;
|
|
if (tmp != (unsigned long long)(u8)tmp)
|
|
return -ERANGE;
|
|
*res = tmp;
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(kstrtou8);
|
|
|
|
int kstrtos8(const char *s, unsigned int base, s8 *res)
|
|
{
|
|
long long tmp;
|
|
int rv;
|
|
|
|
rv = kstrtoll(s, base, &tmp);
|
|
if (rv < 0)
|
|
return rv;
|
|
if (tmp != (long long)(s8)tmp)
|
|
return -ERANGE;
|
|
*res = tmp;
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(kstrtos8);
|
|
|
|
#define kstrto_from_user(f, g, type) \
|
|
int f(const char __user *s, size_t count, unsigned int base, type *res) \
|
|
{ \
|
|
/* sign, base 2 representation, newline, terminator */ \
|
|
char buf[1 + sizeof(type) * 8 + 1 + 1]; \
|
|
\
|
|
count = min(count, sizeof(buf) - 1); \
|
|
if (copy_from_user(buf, s, count)) \
|
|
return -EFAULT; \
|
|
buf[count] = '\0'; \
|
|
return g(buf, base, res); \
|
|
} \
|
|
EXPORT_SYMBOL(f)
|
|
|
|
kstrto_from_user(kstrtoull_from_user, kstrtoull, unsigned long long);
|
|
kstrto_from_user(kstrtoll_from_user, kstrtoll, long long);
|
|
kstrto_from_user(kstrtoul_from_user, kstrtoul, unsigned long);
|
|
kstrto_from_user(kstrtol_from_user, kstrtol, long);
|
|
kstrto_from_user(kstrtouint_from_user, kstrtouint, unsigned int);
|
|
kstrto_from_user(kstrtoint_from_user, kstrtoint, int);
|
|
kstrto_from_user(kstrtou16_from_user, kstrtou16, u16);
|
|
kstrto_from_user(kstrtos16_from_user, kstrtos16, s16);
|
|
kstrto_from_user(kstrtou8_from_user, kstrtou8, u8);
|
|
kstrto_from_user(kstrtos8_from_user, kstrtos8, s8);
|