diff options
Diffstat (limited to 'lib/vsprintf.c')
| -rw-r--r-- | lib/vsprintf.c | 273 | 
1 files changed, 170 insertions, 103 deletions
diff --git a/lib/vsprintf.c b/lib/vsprintf.c index f9cee8e1233c..48ff9c36644d 100644 --- a/lib/vsprintf.c +++ b/lib/vsprintf.c @@ -31,6 +31,9 @@  #include <linux/dcache.h>  #include <linux/cred.h>  #include <net/addrconf.h> +#ifdef CONFIG_BLOCK +#include <linux/blkdev.h> +#endif  #include <asm/page.h>		/* for PAGE_SIZE */  #include <asm/sections.h>	/* for dereference_function_descriptor() */ @@ -380,13 +383,14 @@ enum format_type {  };  struct printf_spec { -	u8	type;		/* format_type enum */ -	u8	flags;		/* flags to number() */ -	u8	base;		/* number base, 8, 10 or 16 only */ -	u8	qualifier;	/* number qualifier, one of 'hHlLtzZ' */ -	s16	field_width;	/* width of output field */ -	s16	precision;	/* # of digits/chars */ -}; +	unsigned int	type:8;		/* format_type enum */ +	signed int	field_width:24;	/* width of output field */ +	unsigned int	flags:8;	/* flags to number() */ +	unsigned int	base:8;		/* number base, 8, 10 or 16 only */ +	signed int	precision:16;	/* # of digits/chars */ +} __packed; +#define FIELD_WIDTH_MAX ((1 << 23) - 1) +#define PRECISION_MAX ((1 << 15) - 1)  static noinline_for_stack  char *number(char *buf, char *end, unsigned long long num, @@ -399,6 +403,10 @@ char *number(char *buf, char *end, unsigned long long num,  	int need_pfx = ((spec.flags & SPECIAL) && spec.base != 10);  	int i;  	bool is_zero = num == 0LL; +	int field_width = spec.field_width; +	int precision = spec.precision; + +	BUILD_BUG_ON(sizeof(struct printf_spec) != 8);  	/* locase = 0 or 0x20. ORing digits or letters with 'locase'  	 * produces same digits or (maybe lowercased) letters */ @@ -410,20 +418,20 @@ char *number(char *buf, char *end, unsigned long long num,  		if ((signed long long)num < 0) {  			sign = '-';  			num = -(signed long long)num; -			spec.field_width--; +			field_width--;  		} else if (spec.flags & PLUS) {  			sign = '+'; -			spec.field_width--; +			field_width--;  		} else if (spec.flags & SPACE) {  			sign = ' '; -			spec.field_width--; +			field_width--;  		}  	}  	if (need_pfx) {  		if (spec.base == 16) -			spec.field_width -= 2; +			field_width -= 2;  		else if (!is_zero) -			spec.field_width--; +			field_width--;  	}  	/* generate full string in tmp[], in reverse order */ @@ -445,12 +453,12 @@ char *number(char *buf, char *end, unsigned long long num,  	}  	/* printing 100 using %2d gives "100", not "00" */ -	if (i > spec.precision) -		spec.precision = i; +	if (i > precision) +		precision = i;  	/* leading space padding */ -	spec.field_width -= spec.precision; +	field_width -= precision;  	if (!(spec.flags & (ZEROPAD | LEFT))) { -		while (--spec.field_width >= 0) { +		while (--field_width >= 0) {  			if (buf < end)  				*buf = ' ';  			++buf; @@ -479,14 +487,14 @@ char *number(char *buf, char *end, unsigned long long num,  	if (!(spec.flags & LEFT)) {  		char c = ' ' + (spec.flags & ZEROPAD);  		BUILD_BUG_ON(' ' + ZEROPAD != '0'); -		while (--spec.field_width >= 0) { +		while (--field_width >= 0) {  			if (buf < end)  				*buf = c;  			++buf;  		}  	}  	/* hmm even more zero padding? */ -	while (i <= --spec.precision) { +	while (i <= --precision) {  		if (buf < end)  			*buf = '0';  		++buf; @@ -498,7 +506,7 @@ char *number(char *buf, char *end, unsigned long long num,  		++buf;  	}  	/* trailing space padding */ -	while (--spec.field_width >= 0) { +	while (--field_width >= 0) {  		if (buf < end)  			*buf = ' ';  		++buf; @@ -508,37 +516,20 @@ char *number(char *buf, char *end, unsigned long long num,  }  static noinline_for_stack -char *string(char *buf, char *end, const char *s, struct printf_spec spec) +char *special_hex_number(char *buf, char *end, unsigned long long num, int size)  { -	int len, i; - -	if ((unsigned long)s < PAGE_SIZE) -		s = "(null)"; - -	len = strnlen(s, spec.precision); +	struct printf_spec spec; -	if (!(spec.flags & LEFT)) { -		while (len < spec.field_width--) { -			if (buf < end) -				*buf = ' '; -			++buf; -		} -	} -	for (i = 0; i < len; ++i) { -		if (buf < end) -			*buf = *s; -		++buf; ++s; -	} -	while (len < spec.field_width--) { -		if (buf < end) -			*buf = ' '; -		++buf; -	} +	spec.type = FORMAT_TYPE_PTR; +	spec.field_width = 2 + 2 * size;	/* 0x + hex */ +	spec.flags = SPECIAL | SMALL | ZEROPAD; +	spec.base = 16; +	spec.precision = -1; -	return buf; +	return number(buf, end, num, spec);  } -static void widen(char *buf, char *end, unsigned len, unsigned spaces) +static void move_right(char *buf, char *end, unsigned len, unsigned spaces)  {  	size_t size;  	if (buf >= end)	/* nowhere to put anything */ @@ -556,6 +547,56 @@ static void widen(char *buf, char *end, unsigned len, unsigned spaces)  	memset(buf, ' ', spaces);  } +/* + * Handle field width padding for a string. + * @buf: current buffer position + * @n: length of string + * @end: end of output buffer + * @spec: for field width and flags + * Returns: new buffer position after padding. + */ +static noinline_for_stack +char *widen_string(char *buf, int n, char *end, struct printf_spec spec) +{ +	unsigned spaces; + +	if (likely(n >= spec.field_width)) +		return buf; +	/* we want to pad the sucker */ +	spaces = spec.field_width - n; +	if (!(spec.flags & LEFT)) { +		move_right(buf - n, end, n, spaces); +		return buf + spaces; +	} +	while (spaces--) { +		if (buf < end) +			*buf = ' '; +		++buf; +	} +	return buf; +} + +static noinline_for_stack +char *string(char *buf, char *end, const char *s, struct printf_spec spec) +{ +	int len = 0; +	size_t lim = spec.precision; + +	if ((unsigned long)s < PAGE_SIZE) +		s = "(null)"; + +	while (lim--) { +		char c = *s++; +		if (!c) +			break; +		if (buf < end) +			*buf = c; +		++buf; +		++len; +	} +	return widen_string(buf, len, end, spec); +} +  static noinline_for_stack  char *dentry_name(char *buf, char *end, const struct dentry *d, struct printf_spec spec,  		  const char *fmt) @@ -597,21 +638,28 @@ char *dentry_name(char *buf, char *end, const struct dentry *d, struct printf_sp  			*buf = c;  	}  	rcu_read_unlock(); -	if (n < spec.field_width) { -		/* we want to pad the sucker */ -		unsigned spaces = spec.field_width - n; -		if (!(spec.flags & LEFT)) { -			widen(buf - n, end, n, spaces); -			return buf + spaces; -		} -		while (spaces--) { +	return widen_string(buf, n, end, spec); +} + +#ifdef CONFIG_BLOCK +static noinline_for_stack +char *bdev_name(char *buf, char *end, struct block_device *bdev, +		struct printf_spec spec, const char *fmt) +{ +	struct gendisk *hd = bdev->bd_disk; +	 +	buf = string(buf, end, hd->disk_name, spec); +	if (bdev->bd_part->partno) { +		if (isdigit(hd->disk_name[strlen(hd->disk_name)-1])) {  			if (buf < end) -				*buf = ' '; -			++buf; +				*buf = 'p'; +			buf++;  		} +		buf = number(buf, end, bdev->bd_part->partno, spec);  	}  	return buf;  } +#endif  static noinline_for_stack  char *symbol_string(char *buf, char *end, void *ptr, @@ -636,11 +684,7 @@ char *symbol_string(char *buf, char *end, void *ptr,  	return string(buf, end, sym, spec);  #else -	spec.field_width = 2 * sizeof(void *); -	spec.flags |= SPECIAL | SMALL | ZEROPAD; -	spec.base = 16; - -	return number(buf, end, value, spec); +	return special_hex_number(buf, end, value, sizeof(void *));  #endif  } @@ -1301,40 +1345,45 @@ char *uuid_string(char *buf, char *end, const u8 *addr,  	return string(buf, end, uuid, spec);  } -static -char *netdev_feature_string(char *buf, char *end, const u8 *addr, -		      struct printf_spec spec) +static noinline_for_stack +char *netdev_bits(char *buf, char *end, const void *addr, const char *fmt)  { -	spec.flags |= SPECIAL | SMALL | ZEROPAD; -	if (spec.field_width == -1) -		spec.field_width = 2 + 2 * sizeof(netdev_features_t); -	spec.base = 16; +	unsigned long long num; +	int size; -	return number(buf, end, *(const netdev_features_t *)addr, spec); +	switch (fmt[1]) { +	case 'F': +		num = *(const netdev_features_t *)addr; +		size = sizeof(netdev_features_t); +		break; +	default: +		num = (unsigned long)addr; +		size = sizeof(unsigned long); +		break; +	} + +	return special_hex_number(buf, end, num, size);  }  static noinline_for_stack -char *address_val(char *buf, char *end, const void *addr, -		  struct printf_spec spec, const char *fmt) +char *address_val(char *buf, char *end, const void *addr, const char *fmt)  {  	unsigned long long num; - -	spec.flags |= SPECIAL | SMALL | ZEROPAD; -	spec.base = 16; +	int size;  	switch (fmt[1]) {  	case 'd':  		num = *(const dma_addr_t *)addr; -		spec.field_width = sizeof(dma_addr_t) * 2 + 2; +		size = sizeof(dma_addr_t);  		break;  	case 'p':  	default:  		num = *(const phys_addr_t *)addr; -		spec.field_width = sizeof(phys_addr_t) * 2 + 2; +		size = sizeof(phys_addr_t);  		break;  	} -	return number(buf, end, num, spec); +	return special_hex_number(buf, end, num, size);  }  static noinline_for_stack @@ -1353,10 +1402,7 @@ char *clock(char *buf, char *end, struct clk *clk, struct printf_spec spec,  #ifdef CONFIG_COMMON_CLK  		return string(buf, end, __clk_get_name(clk), spec);  #else -		spec.base = 16; -		spec.field_width = sizeof(unsigned long) * 2 + 2; -		spec.flags |= SPECIAL | SMALL | ZEROPAD; -		return number(buf, end, (unsigned long)clk, spec); +		return special_hex_number(buf, end, (unsigned long)clk, sizeof(unsigned long));  #endif  	}  } @@ -1443,6 +1489,7 @@ int kptr_restrict __read_mostly;   *           (default assumed to be phys_addr_t, passed by reference)   * - 'd[234]' For a dentry name (optionally 2-4 last components)   * - 'D[234]' Same as 'd' but for a struct file + * - 'g' For block_device name (gendisk + partition number)   * - 'C' For a clock, it prints the name (Common Clock Framework) or address   *       (legacy clock framework) of the clock   * - 'Cn' For a clock, it prints the name (Common Clock Framework) or address @@ -1585,13 +1632,9 @@ char *pointer(const char *fmt, char *buf, char *end, void *ptr,  		break;  	case 'N': -		switch (fmt[1]) { -		case 'F': -			return netdev_feature_string(buf, end, ptr, spec); -		} -		break; +		return netdev_bits(buf, end, ptr, fmt);  	case 'a': -		return address_val(buf, end, ptr, spec, fmt); +		return address_val(buf, end, ptr, fmt);  	case 'd':  		return dentry_name(buf, end, ptr, spec, fmt);  	case 'C': @@ -1600,6 +1643,11 @@ char *pointer(const char *fmt, char *buf, char *end, void *ptr,  		return dentry_name(buf, end,  				   ((const struct file *)ptr)->f_path.dentry,  				   spec, fmt); +#ifdef CONFIG_BLOCK +	case 'g': +		return bdev_name(buf, end, ptr, spec, fmt); +#endif +  	}  	spec.flags |= SMALL;  	if (spec.field_width == -1) { @@ -1635,6 +1683,7 @@ static noinline_for_stack  int format_decode(const char *fmt, struct printf_spec *spec)  {  	const char *start = fmt; +	char qualifier;  	/* we finished early by reading the field width */  	if (spec->type == FORMAT_TYPE_WIDTH) { @@ -1717,16 +1766,16 @@ precision:  qualifier:  	/* get the conversion qualifier */ -	spec->qualifier = -1; +	qualifier = 0;  	if (*fmt == 'h' || _tolower(*fmt) == 'l' ||  	    _tolower(*fmt) == 'z' || *fmt == 't') { -		spec->qualifier = *fmt++; -		if (unlikely(spec->qualifier == *fmt)) { -			if (spec->qualifier == 'l') { -				spec->qualifier = 'L'; +		qualifier = *fmt++; +		if (unlikely(qualifier == *fmt)) { +			if (qualifier == 'l') { +				qualifier = 'L';  				++fmt; -			} else if (spec->qualifier == 'h') { -				spec->qualifier = 'H'; +			} else if (qualifier == 'h') { +				qualifier = 'H';  				++fmt;  			}  		} @@ -1783,19 +1832,19 @@ qualifier:  		return fmt - start;  	} -	if (spec->qualifier == 'L') +	if (qualifier == 'L')  		spec->type = FORMAT_TYPE_LONG_LONG; -	else if (spec->qualifier == 'l') { +	else if (qualifier == 'l') {  		BUILD_BUG_ON(FORMAT_TYPE_ULONG + SIGN != FORMAT_TYPE_LONG);  		spec->type = FORMAT_TYPE_ULONG + (spec->flags & SIGN); -	} else if (_tolower(spec->qualifier) == 'z') { +	} else if (_tolower(qualifier) == 'z') {  		spec->type = FORMAT_TYPE_SIZE_T; -	} else if (spec->qualifier == 't') { +	} else if (qualifier == 't') {  		spec->type = FORMAT_TYPE_PTRDIFF; -	} else if (spec->qualifier == 'H') { +	} else if (qualifier == 'H') {  		BUILD_BUG_ON(FORMAT_TYPE_UBYTE + SIGN != FORMAT_TYPE_BYTE);  		spec->type = FORMAT_TYPE_UBYTE + (spec->flags & SIGN); -	} else if (spec->qualifier == 'h') { +	} else if (qualifier == 'h') {  		BUILD_BUG_ON(FORMAT_TYPE_USHORT + SIGN != FORMAT_TYPE_SHORT);  		spec->type = FORMAT_TYPE_USHORT + (spec->flags & SIGN);  	} else { @@ -1806,6 +1855,24 @@ qualifier:  	return ++fmt - start;  } +static void +set_field_width(struct printf_spec *spec, int width) +{ +	spec->field_width = width; +	if (WARN_ONCE(spec->field_width != width, "field width %d too large", width)) { +		spec->field_width = clamp(width, -FIELD_WIDTH_MAX, FIELD_WIDTH_MAX); +	} +} + +static void +set_precision(struct printf_spec *spec, int prec) +{ +	spec->precision = prec; +	if (WARN_ONCE(spec->precision != prec, "precision %d too large", prec)) { +		spec->precision = clamp(prec, 0, PRECISION_MAX); +	} +} +  /**   * vsnprintf - Format a string and place it in a buffer   * @buf: The buffer to place the result into @@ -1873,11 +1940,11 @@ int vsnprintf(char *buf, size_t size, const char *fmt, va_list args)  		}  		case FORMAT_TYPE_WIDTH: -			spec.field_width = va_arg(args, int); +			set_field_width(&spec, va_arg(args, int));  			break;  		case FORMAT_TYPE_PRECISION: -			spec.precision = va_arg(args, int); +			set_precision(&spec, va_arg(args, int));  			break;  		case FORMAT_TYPE_CHAR: { @@ -2317,11 +2384,11 @@ int bstr_printf(char *buf, size_t size, const char *fmt, const u32 *bin_buf)  		}  		case FORMAT_TYPE_WIDTH: -			spec.field_width = get_arg(int); +			set_field_width(&spec, get_arg(int));  			break;  		case FORMAT_TYPE_PRECISION: -			spec.precision = get_arg(int); +			set_precision(&spec, get_arg(int));  			break;  		case FORMAT_TYPE_CHAR: {  | 
