Commit e371c481 authored by Yury Norov's avatar Yury Norov Committed by Linus Torvalds
Browse files

lib: rework bitmap_parselist

Remove __bitmap_parselist helper and split the function to logical
parts.

[ynorov@marvell.com: v5]
  Link: http://lkml.kernel.org/r/20190416063801.20134-3-ynorov@marvell.com
Link: http://lkml.kernel.org/r/20190405173211.11373-3-ynorov@marvell.com


Signed-off-by: default avatarYury Norov <ynorov@marvell.com>
Reviewed-by: default avatarAndy Shevchenko <andriy.shevchenko@linux.intel.com>
Cc: Arnd Bergmann <arnd@arndb.de>
Cc: Kees Cook <keescook@chromium.org>
Cc: Matthew Wilcox <willy@infradead.org>
Cc: Mike Travis <travis@sgi.com>
Cc: Rasmus Villemoes <linux@rasmusvillemoes.dk>
Cc: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Cc: Guenter Roeck <linux@roeck-us.net>
Signed-off-by: default avatarAndrew Morton <akpm@linux-foundation.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@linux-foundation.org>
parent 281327c9
Loading
Loading
Loading
Loading
+142 −113
Original line number Diff line number Diff line
@@ -20,6 +20,8 @@

#include <asm/page.h>

#include "kstrtox.h"

/**
 * DOC: bitmap introduction
 *
@@ -477,12 +479,128 @@ int bitmap_print_to_pagebuf(bool list, char *buf, const unsigned long *maskp,
}
EXPORT_SYMBOL(bitmap_print_to_pagebuf);

/*
 * Region 9-38:4/10 describes the following bitmap structure:
 * 0	   9  12    18			38
 * .........****......****......****......
 *	    ^  ^     ^			 ^
 *      start  off   group_len	       end
 */
struct region {
	unsigned int start;
	unsigned int off;
	unsigned int group_len;
	unsigned int end;
};

static int bitmap_set_region(const struct region *r,
				unsigned long *bitmap, int nbits)
{
	unsigned int start;

	if (r->end >= nbits)
		return -ERANGE;

	for (start = r->start; start <= r->end; start += r->group_len)
		bitmap_set(bitmap, start, min(r->end - start + 1, r->off));

	return 0;
}

static int bitmap_check_region(const struct region *r)
{
	if (r->start > r->end || r->group_len == 0 || r->off > r->group_len)
		return -EINVAL;

	return 0;
}

static const char *bitmap_getnum(const char *str, unsigned int *num)
{
	unsigned long long n;
	unsigned int len;

	len = _parse_integer(str, 10, &n);
	if (!len)
		return ERR_PTR(-EINVAL);
	if (len & KSTRTOX_OVERFLOW || n != (unsigned int)n)
		return ERR_PTR(-EOVERFLOW);

	*num = n;
	return str + len;
}

static inline bool end_of_str(char c)
{
	return c == '\0' || c == '\n';
}

static inline bool __end_of_region(char c)
{
	return isspace(c) || c == ',';
}

static inline bool end_of_region(char c)
{
	return __end_of_region(c) || end_of_str(c);
}

/*
 * The format allows commas and whitespases at the beginning
 * of the region.
 */
static const char *bitmap_find_region(const char *str)
{
	while (__end_of_region(*str))
		str++;

	return end_of_str(*str) ? NULL : str;
}

static const char *bitmap_parse_region(const char *str, struct region *r)
{
	str = bitmap_getnum(str, &r->start);
	if (IS_ERR(str))
		return str;

	if (end_of_region(*str))
		goto no_end;

	if (*str != '-')
		return ERR_PTR(-EINVAL);

	str = bitmap_getnum(str + 1, &r->end);
	if (IS_ERR(str))
		return str;

	if (end_of_region(*str))
		goto no_pattern;

	if (*str != ':')
		return ERR_PTR(-EINVAL);

	str = bitmap_getnum(str + 1, &r->off);
	if (IS_ERR(str))
		return str;

	if (*str != '/')
		return ERR_PTR(-EINVAL);

	return bitmap_getnum(str + 1, &r->group_len);

no_end:
	r->end = r->start;
no_pattern:
	r->off = r->end + 1;
	r->group_len = r->end + 1;

	return end_of_str(*str) ? NULL : str;
}

/**
 * __bitmap_parselist - convert list format ASCII string to bitmap
 * @buf: read nul-terminated user string from this buffer
 * @buflen: buffer size in bytes.  If string is smaller than this
 *    then it must be terminated with a \0.
 * @is_user: location of buffer, 0 indicates kernel space
 * bitmap_parselist - convert list format ASCII string to bitmap
 * @buf: read user string from this buffer; must be terminated
 *    with a \0 or \n.
 * @maskp: write resulting mask here
 * @nmaskbits: number of bits in mask to be written
 *
@@ -498,127 +616,38 @@ EXPORT_SYMBOL(bitmap_print_to_pagebuf);
 *
 * Returns: 0 on success, -errno on invalid input strings. Error values:
 *
 *   - ``-EINVAL``: second number in range smaller than first
 *   - ``-EINVAL``: wrong region format
 *   - ``-EINVAL``: invalid character in string
 *   - ``-ERANGE``: bit number specified too large for mask
 *   - ``-EOVERFLOW``: integer overflow in the input parameters
 */
static int __bitmap_parselist(const char *buf, unsigned int buflen,
		int is_user, unsigned long *maskp,
		int nmaskbits)
int bitmap_parselist(const char *buf, unsigned long *maskp, int nmaskbits)
{
	unsigned int a, b, old_a, old_b;
	unsigned int group_size, used_size, off;
	int c, old_c, totaldigits, ndigits;
	const char __user __force *ubuf = (const char __user __force *)buf;
	int at_start, in_range, in_partial_range;
	struct region r;
	long ret;

	totaldigits = c = 0;
	old_a = old_b = 0;
	group_size = used_size = 0;
	bitmap_zero(maskp, nmaskbits);
	do {
		at_start = 1;
		in_range = 0;
		in_partial_range = 0;
		a = b = 0;
		ndigits = totaldigits;

		/* Get the next cpu# or a range of cpu#'s */
		while (buflen) {
			old_c = c;
			if (is_user) {
				if (__get_user(c, ubuf++))
					return -EFAULT;
			} else
				c = *buf++;
			buflen--;
			if (isspace(c))
				continue;

			/* A '\0' or a ',' signal the end of a cpu# or range */
			if (c == '\0' || c == ',')
				break;
			/*
			* whitespaces between digits are not allowed,
			* but it's ok if whitespaces are on head or tail.
			* when old_c is whilespace,
			* if totaldigits == ndigits, whitespace is on head.
			* if whitespace is on tail, it should not run here.
			* as c was ',' or '\0',
			* the last code line has broken the current loop.
			*/
			if ((totaldigits != ndigits) && isspace(old_c))
				return -EINVAL;
	while (buf) {
		buf = bitmap_find_region(buf);
		if (buf == NULL)
			return 0;

			if (c == '/') {
				used_size = a;
				at_start = 1;
				in_range = 0;
				a = b = 0;
				continue;
			}
		buf = bitmap_parse_region(buf, &r);
		if (IS_ERR(buf))
			return PTR_ERR(buf);

			if (c == ':') {
				old_a = a;
				old_b = b;
				at_start = 1;
				in_range = 0;
				in_partial_range = 1;
				a = b = 0;
				continue;
			}
		ret = bitmap_check_region(&r);
		if (ret)
			return ret;

			if (c == '-') {
				if (at_start || in_range)
					return -EINVAL;
				b = 0;
				in_range = 1;
				at_start = 1;
				continue;
		ret = bitmap_set_region(&r, maskp, nmaskbits);
		if (ret)
			return ret;
	}

			if (!isdigit(c))
				return -EINVAL;

			b = b * 10 + (c - '0');
			if (!in_range)
				a = b;
			at_start = 0;
			totaldigits++;
		}
		if (ndigits == totaldigits)
			continue;
		if (in_partial_range) {
			group_size = a;
			a = old_a;
			b = old_b;
			old_a = old_b = 0;
		} else {
			used_size = group_size = b - a + 1;
		}
		/* if no digit is after '-', it's wrong*/
		if (at_start && in_range)
			return -EINVAL;
		if (!(a <= b) || group_size == 0 || !(used_size <= group_size))
			return -EINVAL;
		if (b >= nmaskbits)
			return -ERANGE;
		while (a <= b) {
			off = min(b - a + 1, used_size);
			bitmap_set(maskp, a, off);
			a += group_size;
		}
	} while (buflen && c == ',');
	return 0;
}

int bitmap_parselist(const char *bp, unsigned long *maskp, int nmaskbits)
{
	char *nl  = strchrnul(bp, '\n');
	int len = nl - bp;

	return __bitmap_parselist(bp, len, 0, maskp, nmaskbits);
}
EXPORT_SYMBOL(bitmap_parselist);