/* * linux/lib/vsprintf.c * * Copyright (C) 1991, 1992 Linus Torvalds */ /* vsprintf.c -- Lars Wirzenius & Linus Torvalds. */ /* * Wirzenius wrote this portably, Torvalds fucked it up :-) */ #include #include #include #include #include #include #include #include /* we use this so that we can do without the ctype library */ #define is_digit(c) ((c) >= '0' && (c) <= '9') #define is_alpha(c) (((c) >= 'A' && (c) <= 'Z') || ((c) >= 'a' && (c) <= 'z')) #define is_alnum(c) (is_digit(c) || is_alpha(c)) static int skip_atoi(const char **s) { int i=0; while (is_digit(**s)) i = i*10 + *((*s)++) - '0'; return i; } #define ZEROPAD 1 /* pad with zero */ #define SIGN 2 /* unsigned/signed long */ #define PLUS 4 /* show plus */ #define SPACE 8 /* space if plus */ #define LEFT 16 /* left justified */ #define SMALL 32 /* Must be 32 == 0x20 */ #define SPECIAL 64 /* 0x */ static char *number(char *buf, char *end, unsigned long long num, int base, int size, int precision, int type) { /* we are called with base 8, 10 or 16, only, thus don't need "G..." */ static const char digits[16] = "0123456789ABCDEF"; /* "GHIJKLMNOPQRSTUVWXYZ"; */ char tmp[66]; char sign; char locase; int need_pfx = ((type & SPECIAL) && base != 10); int i; /* locase = 0 or 0x20. ORing digits or letters with 'locase' * produces same digits or (maybe lowercased) letters */ locase = (type & SMALL); if (type & LEFT) type &= ~ZEROPAD; sign = 0; if (type & SIGN) { if ((signed long long) num < 0) { sign = '-'; num = - (signed long long) num; size--; } else if (type & PLUS) { sign = '+'; size--; } else if (type & SPACE) { sign = ' '; size--; } } if (need_pfx) { size--; if (base == 16) size--; } /* generate full string in tmp[], in reverse order */ i = 0; if (num == 0) tmp[i++] = '0'; else do { tmp[i++] = (digits[do_div(num,base)] | locase); } while (num != 0); /* printing 100 using %2d gives "100", not "00" */ if (i > precision) precision = i; size -= precision; if (!(type & (ZEROPAD+LEFT))) { while(--size >= 0) { if (buf < end) *buf = ' '; ++buf; } } /* sign */ if (sign) { if (buf < end) *buf = sign; ++buf; } /* "0x" / "0" prefix */ if (need_pfx) { if (buf < end) *buf = '0'; ++buf; if (base == 16) { if (buf < end) *buf = ('X' | locase); ++buf; } } /* zero or space padding */ if (!(type & LEFT)) { char c = (type & ZEROPAD) ? '0' : ' '; while (--size >= 0) { if (buf < end) *buf = c; ++buf; } } /* hmm even more zero padding? */ while (i <= --precision) { if (buf < end) *buf = '0'; ++buf; } /* actual digits of result */ while (--i >= 0) { if (buf < end) *buf = tmp[i]; ++buf; } /* trailing space padding */ while (--size >= 0) { if (buf < end) *buf = ' '; ++buf; } return buf; } #ifndef PAGE_SIZE #define PAGE_SIZE 4096 #endif static char *string(char *buf, char *end, char *s, int field_width, int precision, int flags) { int len, i; if ((unsigned long)s < PAGE_SIZE) s = ""; len = strnlen(s, precision); if (!(flags & LEFT)) { while (len < field_width--) { if (buf < end) *buf = ' '; ++buf; } } for (i = 0; i < len; ++i) { if (buf < end) *buf = *s; ++buf; ++s; } while (len < field_width--) { if (buf < end) *buf = ' '; ++buf; } return buf; } #ifndef __PBL__ static char *symbol_string(char *buf, char *end, void *ptr, int field_width, int precision, int flags) { unsigned long value = (unsigned long) ptr; #ifdef CONFIG_KALLSYMS char sym[KSYM_SYMBOL_LEN]; sprint_symbol(sym, value); return string(buf, end, sym, field_width, precision, flags); #else field_width = 2*sizeof(void *); flags |= SPECIAL | SMALL | ZEROPAD; return number(buf, end, value, 16, field_width, precision, flags); #endif } static noinline_for_stack char *ip4_addr_string(char *buf, char *end, const u8 *addr, int field_width, int precision, int flags, const char *fmt) { char ip4_addr[sizeof("255.255.255.255")]; char *pos; int i; pos = ip4_addr; for (i = 0; i < 4; i++) { pos = number(pos, pos + 3, addr[i], 10, 0, 0, LEFT); if (i < 3) *pos++ = '.'; } *pos = 0; return string(buf, end, ip4_addr, field_width, precision, flags); } static noinline_for_stack char *uuid_string(char *buf, char *end, const u8 *addr, int field_width, int precision, int flags, const char *fmt) { char uuid[sizeof("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx")]; char *p = uuid; int i; static const u8 be[16] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}; static const u8 le[16] = {3,2,1,0,5,4,7,6,8,9,10,11,12,13,14,15}; const u8 *index = be; bool uc = false; switch (*(++fmt)) { case 'L': uc = true; /* fall-through */ case 'l': index = le; break; case 'B': uc = true; break; } for (i = 0; i < 16; i++) { p = hex_byte_pack(p, addr[index[i]]); switch (i) { case 3: case 5: case 7: case 9: *p++ = '-'; break; } } *p = 0; if (uc) { p = uuid; do { *p = toupper(*p); } while (*(++p)); } return string(buf, end, uuid, field_width, precision, flags); } static noinline_for_stack char *address_val(char *buf, char *end, const void *addr, int field_width, int precision, int flags, const char *fmt) { unsigned long long num; flags |= SPECIAL | SMALL | ZEROPAD; switch (fmt[1]) { case 'd': num = *(const dma_addr_t *)addr; field_width = sizeof(dma_addr_t) * 2 + 2; break; case 'p': default: num = *(const phys_addr_t *)addr; field_width = sizeof(phys_addr_t) * 2 + 2; break; } return number(buf, end, num, 16, field_width, precision, flags); } /* * Show a '%p' thing. A kernel extension is that the '%p' is followed * by an extra set of alphanumeric characters that are extended format * specifiers. * * Right now we handle: * * - 'I' [4] for IPv4 addresses printed in the usual way * IPv4 uses dot-separated decimal without leading 0's (1.2.3.4) * - 'S' For symbolic direct pointers * - 'U' For a 16 byte UUID/GUID, it prints the UUID/GUID in the form * "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" * Options for %pU are: * b big endian lower case hex (default) * B big endian UPPER case hex * l little endian lower case hex * L little endian UPPER case hex * big endian output byte order is: * [0][1][2][3]-[4][5]-[6][7]-[8][9]-[10][11][12][13][14][15] * little endian output byte order is: * [3][2][1][0]-[5][4]-[7][6]-[8][9]-[10][11][12][13][14][15] * - 'V' For a struct va_format which contains a format string * and va_list *, * call vsnprintf(->format, *->va_list). * Implements a "recursive vsnprintf". * Do not use this feature without some mechanism to verify the * correctness of the format string and va_list arguments. * - 'a[pd]' For address types [p] phys_addr_t, [d] dma_addr_t and derivatives * (default assumed to be phys_addr_t, passed by reference) * * Note: The difference between 'S' and 'F' is that on ia64 and ppc64 * function pointers are really function descriptors, which contain a * pointer to the real address. */ static char *pointer(const char *fmt, char *buf, char *end, void *ptr, int field_width, int precision, int flags) { switch (*fmt) { case 'S': return symbol_string(buf, end, ptr, field_width, precision, flags); case 'U': if (IS_ENABLED(CONFIG_PRINTF_UUID)) return uuid_string(buf, end, ptr, field_width, precision, flags, fmt); break; case 'V': { va_list va; va_copy(va, *((struct va_format *)ptr)->va); buf += vsnprintf(buf, end > buf ? end - buf : 0, ((struct va_format *)ptr)->fmt, va); va_end(va); return buf; } case 'a': return address_val(buf, end, ptr, field_width, precision, flags, fmt); case 'I': switch (fmt[1]) { case '4': return ip4_addr_string(buf, end, ptr, field_width, precision, flags, fmt); } break; } flags |= SMALL; if (field_width == -1) { field_width = 2*sizeof(void *); flags |= ZEROPAD; } return number(buf, end, (unsigned long) ptr, 16, field_width, precision, flags); } #else static char *pointer(const char *fmt, char *buf, char *end, void *ptr, int field_width, int precision, int flags) { flags |= SMALL; if (field_width == -1) { field_width = 2*sizeof(void *); flags |= ZEROPAD; } return number(buf, end, (unsigned long) ptr, 16, field_width, precision, flags); } #endif /** * vsnprintf - Format a string and place it in a buffer * @buf: The buffer to place the result into * @size: The size of the buffer, including the trailing null space * @fmt: The format string to use * @args: Arguments for the format string * * This function follows C99 vsnprintf, but has some extensions: * %pS output the name of a text symbol * %pF output the name of a function pointer * %pR output the address range in a struct resource * * The return value is the number of characters which would * be generated for the given input, excluding the trailing * '\0', as per ISO C99. If you want to have the exact * number of characters written into @buf as return value * (not including the trailing '\0'), use vscnprintf(). If the * return is greater than or equal to @size, the resulting * string is truncated. * * Call this function if you are already dealing with a va_list. * You probably want snprintf() instead. */ int vsnprintf(char *buf, size_t size, const char *fmt, va_list args) { unsigned long long num; int base; char *str, *end, c; int flags; /* flags to number() */ int field_width; /* width of output field */ int precision; /* min. # of digits for integers; max number of chars for from string */ int qualifier; /* 'h', 'l', or 'L' for integer fields */ /* 'z' support added 23/7/1999 S.H. */ /* 'z' changed to 'Z' --davidm 1/25/99 */ /* 't' added for ptrdiff_t */ /* Reject out-of-range values early. Large positive sizes are used for unknown buffer sizes. */ if (unlikely((int) size < 0)) return 0; str = buf; end = buf + size; /* Make sure end is always >= buf */ if (end < buf) { end = ((void *)-1); size = end - buf; } for (; *fmt ; ++fmt) { if (*fmt != '%') { if (str < end) *str = *fmt; ++str; continue; } /* process flags */ flags = 0; repeat: ++fmt; /* this also skips first '%' */ switch (*fmt) { case '-': flags |= LEFT; goto repeat; case '+': flags |= PLUS; goto repeat; case ' ': flags |= SPACE; goto repeat; case '#': flags |= SPECIAL; goto repeat; case '0': flags |= ZEROPAD; goto repeat; } /* get field width */ field_width = -1; if (is_digit(*fmt)) field_width = skip_atoi(&fmt); else if (*fmt == '*') { ++fmt; /* it's the next argument */ field_width = va_arg(args, int); if (field_width < 0) { field_width = -field_width; flags |= LEFT; } } /* get the precision */ precision = -1; if (*fmt == '.') { ++fmt; if (is_digit(*fmt)) precision = skip_atoi(&fmt); else if (*fmt == '*') { ++fmt; /* it's the next argument */ precision = va_arg(args, int); } if (precision < 0) precision = 0; } /* get the conversion qualifier */ qualifier = -1; if (*fmt == 'h' || *fmt == 'l' || *fmt == 'L' || *fmt =='Z' || *fmt == 'z' || *fmt == 't') { qualifier = *fmt; ++fmt; if (qualifier == 'l' && *fmt == 'l') { qualifier = 'L'; ++fmt; } } /* default base */ base = 10; switch (*fmt) { case 'c': if (!(flags & LEFT)) { while (--field_width > 0) { if (str < end) *str = ' '; ++str; } } c = (unsigned char) va_arg(args, int); if (str < end) *str = c; ++str; while (--field_width > 0) { if (str < end) *str = ' '; ++str; } continue; case 's': str = string(str, end, va_arg(args, char *), field_width, precision, flags); continue; case 'p': str = pointer(fmt+1, str, end, va_arg(args, void *), field_width, precision, flags); /* Skip all alphanumeric pointer suffixes */ while (is_alnum(fmt[1])) fmt++; continue; case 'n': /* FIXME: * What does C99 say about the overflow case here? */ if (qualifier == 'l') { long * ip = va_arg(args, long *); *ip = (str - buf); } else if (qualifier == 'Z' || qualifier == 'z') { size_t * ip = va_arg(args, size_t *); *ip = (str - buf); } else { int * ip = va_arg(args, int *); *ip = (str - buf); } continue; case '%': if (str < end) *str = '%'; ++str; continue; /* integer number formats - set up the flags and "break" */ case 'o': base = 8; break; case 'x': flags |= SMALL; case 'X': base = 16; break; case 'd': case 'i': flags |= SIGN; case 'u': break; default: if (str < end) *str = '%'; ++str; if (*fmt) { if (str < end) *str = *fmt; ++str; } else { --fmt; } continue; } if (qualifier == 'L') num = va_arg(args, long long); else if (qualifier == 'l') { num = va_arg(args, unsigned long); if (flags & SIGN) num = (signed long) num; } else if (qualifier == 'Z' || qualifier == 'z') { num = va_arg(args, size_t); } else if (qualifier == 't') { num = va_arg(args, ptrdiff_t); } else if (qualifier == 'h') { num = (unsigned short) va_arg(args, int); if (flags & SIGN) num = (signed short) num; } else { num = va_arg(args, unsigned int); if (flags & SIGN) num = (signed int) num; } str = number(str, end, num, base, field_width, precision, flags); } if (size > 0) { if (str < end) *str = '\0'; else end[-1] = '\0'; } /* the trailing null byte doesn't count towards the total */ return str-buf; } EXPORT_SYMBOL(vsnprintf); /** * vscnprintf - Format a string and place it in a buffer * @buf: The buffer to place the result into * @size: The size of the buffer, including the trailing null space * @fmt: The format string to use * @args: Arguments for the format string * * The return value is the number of characters which have been written into * the @buf not including the trailing '\0'. If @size is <= 0 the function * returns 0. * * Call this function if you are already dealing with a va_list. * You probably want scnprintf() instead. * * See the vsnprintf() documentation for format string extensions over C99. */ int vscnprintf(char *buf, size_t size, const char *fmt, va_list args) { int i; i = vsnprintf(buf, size, fmt, args); return (i >= size) ? (size - 1) : i; } EXPORT_SYMBOL(vscnprintf); /** * vsprintf - Format a string and place it in a buffer * @buf: The buffer to place the result into * @fmt: The format string to use * @args: Arguments for the format string * * The function returns the number of characters written * into @buf. Use vsnprintf() or vscnprintf() in order to avoid * buffer overflows. * * Call this function if you are already dealing with a va_list. * You probably want sprintf() instead. * * See the vsnprintf() documentation for format string extensions over C99. */ int vsprintf(char *buf, const char *fmt, va_list args) { return vsnprintf(buf, INT_MAX, fmt, args); } EXPORT_SYMBOL(vsprintf); int sprintf(char * buf, const char *fmt, ...) { va_list args; int i; va_start(args, fmt); i = vsprintf(buf, fmt, args); va_end(args); return i; } EXPORT_SYMBOL(sprintf); int snprintf(char *buf, size_t size, const char *fmt, ...) { va_list args; int i; va_start(args, fmt); i = vsnprintf(buf, size, fmt, args); va_end(args); return i; } EXPORT_SYMBOL(snprintf); /** * scnprintf - Format a string and place it in a buffer * @buf: The buffer to place the result into * @size: The size of the buffer, including the trailing null space * @fmt: The format string to use * @...: Arguments for the format string * * The return value is the number of characters written into @buf not including * the trailing '\0'. If @size is == 0 the function returns 0. */ int scnprintf(char *buf, size_t size, const char *fmt, ...) { va_list args; int i; va_start(args, fmt); i = vscnprintf(buf, size, fmt, args); va_end(args); return i; } EXPORT_SYMBOL(scnprintf); int vasprintf(char **strp, const char *fmt, va_list ap) { unsigned int len; va_list aq; char *p; va_copy(aq, ap); len = vsnprintf(NULL, 0, fmt, aq); va_end(aq); p = malloc(len + 1); if (!p) return -1; vsnprintf(p, len + 1, fmt, ap); *strp = p; return len; } EXPORT_SYMBOL(vasprintf); char *bvasprintf(const char *fmt, va_list ap) { char *p; int len; len = vasprintf(&p, fmt, ap); if (len < 0) return NULL; return p; } EXPORT_SYMBOL(bvasprintf); int asprintf(char **strp, const char *fmt, ...) { va_list ap; int len; va_start(ap, fmt); len = vasprintf(strp, fmt, ap); va_end(ap); return len; } EXPORT_SYMBOL(asprintf); char *basprintf(const char *fmt, ...) { va_list ap; char *p; int len; va_start(ap, fmt); len = vasprintf(&p, fmt, ap); va_end(ap); if (len < 0) return NULL; return p; } EXPORT_SYMBOL(basprintf);