summaryrefslogtreecommitdiffstats
path: root/lib/vsprintf.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/vsprintf.c')
-rw-r--r--lib/vsprintf.c353
1 files changed, 233 insertions, 120 deletions
diff --git a/lib/vsprintf.c b/lib/vsprintf.c
index ac3f9476b776..525c8e19bda2 100644
--- a/lib/vsprintf.c
+++ b/lib/vsprintf.c
@@ -35,6 +35,8 @@
#include <linux/blkdev.h>
#endif
+#include "../mm/internal.h" /* For the trace_print_flags arrays */
+
#include <asm/page.h> /* for PAGE_SIZE */
#include <asm/sections.h> /* for dereference_function_descriptor() */
#include <asm/byteorder.h> /* cpu_to_le16 */
@@ -383,13 +385,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,
@@ -402,6 +405,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 */
@@ -413,20 +420,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 */
@@ -448,12 +455,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;
@@ -482,14 +489,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;
@@ -501,7 +508,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;
@@ -511,37 +518,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;
+ struct printf_spec spec;
- if ((unsigned long)s < PAGE_SIZE)
- s = "(null)";
-
- len = strnlen(s, spec.precision);
-
- 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 */
@@ -559,6 +549,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)
@@ -600,20 +640,7 @@ 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--) {
- if (buf < end)
- *buf = ' ';
- ++buf;
- }
- }
- return buf;
+ return widen_string(buf, n, end, spec);
}
#ifdef CONFIG_BLOCK
@@ -659,11 +686,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
}
@@ -1324,40 +1347,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;
+
+ 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 number(buf, end, *(const netdev_features_t *)addr, spec);
+ 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
@@ -1376,14 +1404,77 @@ 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
}
}
+static
+char *format_flags(char *buf, char *end, unsigned long flags,
+ const struct trace_print_flags *names)
+{
+ unsigned long mask;
+ const struct printf_spec strspec = {
+ .field_width = -1,
+ .precision = -1,
+ };
+ const struct printf_spec numspec = {
+ .flags = SPECIAL|SMALL,
+ .field_width = -1,
+ .precision = -1,
+ .base = 16,
+ };
+
+ for ( ; flags && names->name; names++) {
+ mask = names->mask;
+ if ((flags & mask) != mask)
+ continue;
+
+ buf = string(buf, end, names->name, strspec);
+
+ flags &= ~mask;
+ if (flags) {
+ if (buf < end)
+ *buf = '|';
+ buf++;
+ }
+ }
+
+ if (flags)
+ buf = number(buf, end, flags, numspec);
+
+ return buf;
+}
+
+static noinline_for_stack
+char *flags_string(char *buf, char *end, void *flags_ptr, const char *fmt)
+{
+ unsigned long flags;
+ const struct trace_print_flags *names;
+
+ switch (fmt[1]) {
+ case 'p':
+ flags = *(unsigned long *)flags_ptr;
+ /* Remove zone id */
+ flags &= (1UL << NR_PAGEFLAGS) - 1;
+ names = pageflag_names;
+ break;
+ case 'v':
+ flags = *(unsigned long *)flags_ptr;
+ names = vmaflag_names;
+ break;
+ case 'g':
+ flags = *(gfp_t *)flags_ptr;
+ names = gfpflag_names;
+ break;
+ default:
+ WARN_ONCE(1, "Unsupported flags modifier: %c\n", fmt[1]);
+ return buf;
+ }
+
+ return format_flags(buf, end, flags, names);
+}
+
int kptr_restrict __read_mostly;
/*
@@ -1472,6 +1563,11 @@ int kptr_restrict __read_mostly;
* - 'Cn' For a clock, it prints the name (Common Clock Framework) or address
* (legacy clock framework) of the clock
* - 'Cr' For a clock, it prints the current rate of the clock
+ * - 'G' For flags to be printed as a collection of symbolic strings that would
+ * construct the specific value. Supported flags given by option:
+ * p page flags (see struct page) given as pointer to unsigned long
+ * g gfp flags (GFP_* and __GFP_*) given as pointer to gfp_t
+ * v vma flags (VM_*) given as pointer to unsigned long
*
* ** Please update also Documentation/printk-formats.txt when making changes **
*
@@ -1567,22 +1663,23 @@ char *pointer(const char *fmt, char *buf, char *end, void *ptr,
return buf;
}
case 'K':
- /*
- * %pK cannot be used in IRQ context because its test
- * for CAP_SYSLOG would be meaningless.
- */
- if (kptr_restrict && (in_irq() || in_serving_softirq() ||
- in_nmi())) {
- if (spec.field_width == -1)
- spec.field_width = default_width;
- return string(buf, end, "pK-error", spec);
- }
-
switch (kptr_restrict) {
case 0:
/* Always print %pK values */
break;
case 1: {
+ const struct cred *cred;
+
+ /*
+ * kptr_restrict==1 cannot be used in IRQ context
+ * because its test for CAP_SYSLOG would be meaningless.
+ */
+ if (in_irq() || in_serving_softirq() || in_nmi()) {
+ if (spec.field_width == -1)
+ spec.field_width = default_width;
+ return string(buf, end, "pK-error", spec);
+ }
+
/*
* Only print the real pointer value if the current
* process has CAP_SYSLOG and is running with the
@@ -1592,8 +1689,7 @@ char *pointer(const char *fmt, char *buf, char *end, void *ptr,
* leak pointer values if a binary opens a file using
* %pK and then elevates privileges before reading it.
*/
- const struct cred *cred = current_cred();
-
+ cred = current_cred();
if (!has_capability_noaudit(current, CAP_SYSLOG) ||
!uid_eq(cred->euid, cred->uid) ||
!gid_eq(cred->egid, cred->gid))
@@ -1609,13 +1705,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':
@@ -1629,6 +1721,8 @@ char *pointer(const char *fmt, char *buf, char *end, void *ptr,
return bdev_name(buf, end, ptr, spec, fmt);
#endif
+ case 'G':
+ return flags_string(buf, end, ptr, fmt);
}
spec.flags |= SMALL;
if (spec.field_width == -1) {
@@ -1664,6 +1758,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) {
@@ -1746,16 +1841,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;
}
}
@@ -1812,19 +1907,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 {
@@ -1835,6 +1930,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
@@ -1902,11 +2015,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: {
@@ -2346,11 +2459,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: {
OpenPOWER on IntegriCloud