lib: rework bitmap_parse()

bitmap_parse() is ineffective and full of opaque variables and opencoded
parts.  It leads to hard understanding and usage of it.  This rework
includes:

- remove bitmap_shift_left() call from the cycle.  Now it makes the
  complexity of the algorithm as O(nbits^2).  In the suggested approach
  the input string is parsed in reverse direction, so no shifts needed;

- relax requirement on a single comma and no white spaces between
  chunks.  It is considered useful in scripting, and it aligns with
  bitmap_parselist();

- split bitmap_parse() to small readable helpers;

- make an explicit calculation of the end of input line at the
  beginning, so users of the bitmap_parse() won't bother doing this.

Link: http://lkml.kernel.org/r/20200102043031.30357-6-yury.norov@gmail.com
Signed-off-by: Yury Norov <yury.norov@gmail.com>
Cc: Amritha Nambiar <amritha.nambiar@intel.com>
Cc: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Cc: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: Chris Wilson <chris@chris-wilson.co.uk>
Cc: Kees Cook <keescook@chromium.org>
Cc: Matthew Wilcox <willy@infradead.org>
Cc: Miklos Szeredi <mszeredi@redhat.com>
Cc: Rasmus Villemoes <linux@rasmusvillemoes.dk>
Cc: Steffen Klassert <steffen.klassert@secunet.com>
Cc: "Tobin C . Harding" <tobin@kernel.org>
Cc: Vineet Gupta <vineet.gupta1@synopsys.com>
Cc: Will Deacon <will.deacon@arm.com>
Cc: Willem de Bruijn <willemb@google.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
This commit is contained in:
Yury Norov 2020-02-03 17:37:34 -08:00 committed by Linus Torvalds
parent e66eda0615
commit 2d6261583b
2 changed files with 84 additions and 99 deletions

View File

@ -186,7 +186,7 @@ bitmap_find_next_zero_area(unsigned long *map,
align_mask, 0); align_mask, 0);
} }
extern int __bitmap_parse(const char *buf, unsigned int buflen, int is_user, extern int bitmap_parse(const char *buf, unsigned int buflen,
unsigned long *dst, int nbits); unsigned long *dst, int nbits);
extern int bitmap_parse_user(const char __user *ubuf, unsigned int ulen, extern int bitmap_parse_user(const char __user *ubuf, unsigned int ulen,
unsigned long *dst, int nbits); unsigned long *dst, int nbits);
@ -454,12 +454,6 @@ static inline void bitmap_replace(unsigned long *dst,
__bitmap_replace(dst, old, new, mask, nbits); __bitmap_replace(dst, old, new, mask, nbits);
} }
static inline int bitmap_parse(const char *buf, unsigned int buflen,
unsigned long *maskp, int nmaskbits)
{
return __bitmap_parse(buf, buflen, 0, maskp, nmaskbits);
}
static inline void bitmap_next_clear_region(unsigned long *bitmap, static inline void bitmap_next_clear_region(unsigned long *bitmap,
unsigned int *rs, unsigned int *re, unsigned int *rs, unsigned int *re,
unsigned int end) unsigned int end)

View File

@ -431,97 +431,6 @@ EXPORT_SYMBOL(bitmap_find_next_zero_area_off);
* second version by Paul Jackson, third by Joe Korty. * second version by Paul Jackson, third by Joe Korty.
*/ */
#define CHUNKSZ 32
#define nbits_to_hold_value(val) fls(val)
#define BASEDEC 10 /* fancier cpuset lists input in decimal */
/**
* __bitmap_parse - convert an ASCII hex string into a bitmap.
* @buf: pointer to buffer containing string.
* @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
* @maskp: pointer to bitmap array that will contain result.
* @nmaskbits: size of bitmap, in bits.
*
* Commas group hex digits into chunks. Each chunk defines exactly 32
* bits of the resultant bitmask. No chunk may specify a value larger
* than 32 bits (%-EOVERFLOW), and if a chunk specifies a smaller value
* then leading 0-bits are prepended. %-EINVAL is returned for illegal
* characters and for grouping errors such as "1,,5", ",44", "," and "".
* Leading and trailing whitespace accepted, but not embedded whitespace.
*/
int __bitmap_parse(const char *buf, unsigned int buflen,
int is_user, unsigned long *maskp,
int nmaskbits)
{
int c, old_c, totaldigits, ndigits, nchunks, nbits;
u32 chunk;
const char __user __force *ubuf = (const char __user __force *)buf;
bitmap_zero(maskp, nmaskbits);
nchunks = nbits = totaldigits = c = 0;
do {
chunk = 0;
ndigits = totaldigits;
/* Get the next chunk of the bitmap */
while (buflen) {
old_c = c;
if (is_user) {
if (__get_user(c, ubuf++))
return -EFAULT;
}
else
c = *buf++;
buflen--;
if (isspace(c))
continue;
/*
* If the last character was a space and the current
* character isn't '\0', we've got embedded whitespace.
* This is a no-no, so throw an error.
*/
if (totaldigits && c && isspace(old_c))
return -EINVAL;
/* A '\0' or a ',' signal the end of the chunk */
if (c == '\0' || c == ',')
break;
if (!isxdigit(c))
return -EINVAL;
/*
* Make sure there are at least 4 free bits in 'chunk'.
* If not, this hexdigit will overflow 'chunk', so
* throw an error.
*/
if (chunk & ~((1UL << (CHUNKSZ - 4)) - 1))
return -EOVERFLOW;
chunk = (chunk << 4) | hex_to_bin(c);
totaldigits++;
}
if (ndigits == totaldigits)
return -EINVAL;
if (nchunks == 0 && chunk == 0)
continue;
__bitmap_shift_left(maskp, maskp, CHUNKSZ, nmaskbits);
*maskp |= chunk;
nchunks++;
nbits += (nchunks == 1) ? nbits_to_hold_value(chunk) : CHUNKSZ;
if (nbits > nmaskbits)
return -EOVERFLOW;
} while (buflen && c == ',');
return 0;
}
EXPORT_SYMBOL(__bitmap_parse);
/** /**
* bitmap_parse_user - convert an ASCII hex string in a user buffer into a bitmap * bitmap_parse_user - convert an ASCII hex string in a user buffer into a bitmap
* *
@ -542,7 +451,7 @@ int bitmap_parse_user(const char __user *ubuf,
if (IS_ERR(buf)) if (IS_ERR(buf))
return PTR_ERR(buf); return PTR_ERR(buf);
ret = bitmap_parse(buf, ulen, maskp, nmaskbits); ret = bitmap_parse(buf, UINT_MAX, maskp, nmaskbits);
kfree(buf); kfree(buf);
return ret; return ret;
@ -653,6 +562,14 @@ static const char *bitmap_find_region(const char *str)
return end_of_str(*str) ? NULL : str; return end_of_str(*str) ? NULL : str;
} }
static const char *bitmap_find_region_reverse(const char *start, const char *end)
{
while (start <= end && __end_of_region(*end))
end--;
return end;
}
static const char *bitmap_parse_region(const char *str, struct region *r) static const char *bitmap_parse_region(const char *str, struct region *r)
{ {
str = bitmap_getnum(str, &r->start); str = bitmap_getnum(str, &r->start);
@ -776,6 +693,80 @@ int bitmap_parselist_user(const char __user *ubuf,
} }
EXPORT_SYMBOL(bitmap_parselist_user); EXPORT_SYMBOL(bitmap_parselist_user);
static const char *bitmap_get_x32_reverse(const char *start,
const char *end, u32 *num)
{
u32 ret = 0;
int c, i;
for (i = 0; i < 32; i += 4) {
c = hex_to_bin(*end--);
if (c < 0)
return ERR_PTR(-EINVAL);
ret |= c << i;
if (start > end || __end_of_region(*end))
goto out;
}
if (hex_to_bin(*end--) >= 0)
return ERR_PTR(-EOVERFLOW);
out:
*num = ret;
return end;
}
/**
* bitmap_parse - convert an ASCII hex string into a bitmap.
* @start: pointer to buffer containing string.
* @buflen: buffer size in bytes. If string is smaller than this
* then it must be terminated with a \0 or \n. In that case,
* UINT_MAX may be provided instead of string length.
* @maskp: pointer to bitmap array that will contain result.
* @nmaskbits: size of bitmap, in bits.
*
* Commas group hex digits into chunks. Each chunk defines exactly 32
* bits of the resultant bitmask. No chunk may specify a value larger
* than 32 bits (%-EOVERFLOW), and if a chunk specifies a smaller value
* then leading 0-bits are prepended. %-EINVAL is returned for illegal
* characters. Grouping such as "1,,5", ",44", "," or "" is allowed.
* Leading, embedded and trailing whitespace accepted.
*/
int bitmap_parse(const char *start, unsigned int buflen,
unsigned long *maskp, int nmaskbits)
{
const char *end = strnchrnul(start, buflen, '\n') - 1;
int chunks = BITS_TO_U32(nmaskbits);
u32 *bitmap = (u32 *)maskp;
int unset_bit;
while (1) {
end = bitmap_find_region_reverse(start, end);
if (start > end)
break;
if (!chunks--)
return -EOVERFLOW;
end = bitmap_get_x32_reverse(start, end, bitmap++);
if (IS_ERR(end))
return PTR_ERR(end);
}
unset_bit = (BITS_TO_U32(nmaskbits) - chunks) * 32;
if (unset_bit < nmaskbits) {
bitmap_clear(maskp, unset_bit, nmaskbits - unset_bit);
return 0;
}
if (find_next_bit(maskp, unset_bit, nmaskbits) != unset_bit)
return -EOVERFLOW;
return 0;
}
EXPORT_SYMBOL(bitmap_parse);
#ifdef CONFIG_NUMA #ifdef CONFIG_NUMA
/** /**