diff options
Diffstat (limited to 'sys/dev/bhnd/nvram/bhnd_nvram_value_prf.c')
| -rw-r--r-- | sys/dev/bhnd/nvram/bhnd_nvram_value_prf.c | 883 |
1 files changed, 883 insertions, 0 deletions
diff --git a/sys/dev/bhnd/nvram/bhnd_nvram_value_prf.c b/sys/dev/bhnd/nvram/bhnd_nvram_value_prf.c new file mode 100644 index 000000000000..ad57fd609050 --- /dev/null +++ b/sys/dev/bhnd/nvram/bhnd_nvram_value_prf.c @@ -0,0 +1,883 @@ +/*- + * Copyright (c) 2015-2016 Landon Fuller <landonf@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any + * redistribution must be conditioned upon including a substantially + * similar Disclaimer requirement for further binary redistribution. + * + * NO WARRANTY + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGES. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/sbuf.h> + +#ifdef _KERNEL + +#include <sys/ctype.h> +#include <sys/kernel.h> +#include <sys/malloc.h> +#include <sys/systm.h> + +#include <machine/_inttypes.h> + +#else /* !_KERNEL */ + +#include <ctype.h> +#include <inttypes.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> + +#endif /* _KERNEL */ + +#include "bhnd_nvram_private.h" +#include "bhnd_nvram_valuevar.h" + +#ifdef _KERNEL +#define bhnd_nv_hex2ascii(hex) hex2ascii(hex) +#else /* !_KERNEL */ +static char const bhnd_nv_hex2ascii[] = "0123456789abcdefghijklmnopqrstuvwxyz"; +#define bhnd_nv_hex2ascii(hex) (bhnd_nv_hex2ascii[hex]) +#endif /* _KERNEL */ + +/** + * Maximum size, in bytes, of a string-encoded NVRAM integer value, not + * including any prefix (0x, 0, etc). + * + * We assume the largest possible encoding is the base-2 representation + * of a 64-bit integer. + */ +#define NV_NUMSTR_MAX ((sizeof(uint64_t) * CHAR_BIT) + 1) + +/** + * Format a string representation of @p value using @p fmt, with, writing the + * result to @p outp. + * + * @param value The value to be formatted. + * @param fmt The format string. + * @param[out] outp On success, the string will be written to this + * buffer. This argment may be NULL if the value is + * not desired. + * @param[in,out] olen The capacity of @p outp. On success, will be set + * to the actual number of bytes required for the + * requested string encoding (including a trailing + * NUL). + * + * Refer to bhnd_nvram_val_vprintf() for full format string documentation. + * + * @retval 0 success + * @retval EINVAL If @p fmt contains unrecognized format string + * specifiers. + * @retval ENOMEM If the @p outp is non-NULL, and the provided @p olen + * is too small to hold the encoded value. + * @retval EFTYPE If value coercion from @p value to a single string + * value via @p fmt is unsupported. + * @retval ERANGE If value coercion of @p value would overflow (or + * underflow) the representation defined by @p fmt. + */ +int +bhnd_nvram_val_printf(bhnd_nvram_val_t *value, const char *fmt, char *outp, + size_t *olen, ...) +{ + va_list ap; + int error; + + va_start(ap, olen); + error = bhnd_nvram_val_vprintf(value, fmt, outp, olen, ap); + va_end(ap); + + return (error); +} + + +/** + * Format a string representation of the elements of @p value using @p fmt, + * writing the result to @p outp. + * + * @param value The value to be formatted. + * @param fmt The format string. + * @param[out] outp On success, the string will be written to this + * buffer. This argment may be NULL if the value is + * not desired. + * @param[in,out] olen The capacity of @p outp. On success, will be set + * to the actual number of bytes required for the + * requested string encoding (including a trailing + * NUL). + * @param ap Argument list. + * + * @par Format Strings + * + * Value format strings are similar, but not identical to, those used + * by printf(3). + * + * Format specifier format: + * %[repeat][flags][width][.precision][length modifier][specifier] + * + * The format specifier is interpreted as an encoding directive for an + * individual value element; each format specifier will fetch the next element + * from the value, encode the element as the appropriate type based on the + * length modifiers and specifier, and then format the result as a string. + * + * For example, given a string value of '0x000F', and a format specifier of + * '%#hhx', the value will be asked to encode its first element as + * BHND_NVRAM_TYPE_UINT8. String formatting will then be applied to the 8-bit + * unsigned integer representation, producing a string value of "0xF". + * + * Repeat: + * - [digits] Repeatedly apply the format specifier to the input + * value's elements up to `digits` times. The delimiter + * must be passed as a string in the next variadic + * argument. + * - [] Repeatedly apply the format specifier to the input + * value's elements until all elements have been. The + * processed. The delimiter must be passed as a string in + * the next variadic argument. + * - [*] Repeatedly apply the format specifier to the input + * value's elements. The repeat count is read from the + * next variadic argument as a size_t value + * + * Flags: + * - '#' use alternative form (e.g. 0x/0X prefixing of hex + * strings). + * - '0' zero padding + * - '-' left adjust padding + * - '+' include a sign character + * - ' ' include a space in place of a sign character for + * positive numbers. + * + * Width/Precision: + * - digits minimum field width. + * - * read the minimum field width from the next variadic + * argument as a ssize_t value. A negative value enables + * left adjustment. + * - .digits field precision. + * - .* read the field precision from the next variadic argument + * as a ssize_t value. A negative value enables left + * adjustment. + * + * Length Modifiers: + * - 'hh', 'I8' Convert the value to an 8-bit signed or unsigned + * integer. + * - 'h', 'I16' Convert the value to an 16-bit signed or unsigned + * integer. + * - 'l', 'I32' Convert the value to an 32-bit signed or unsigned + * integer. + * - 'll', 'j', 'I64' Convert the value to an 64-bit signed or unsigned + * integer. + * + * Data Specifiers: + * - 'd', 'i' Convert and format as a signed decimal integer. + * - 'u' Convert and format as an unsigned decimal integer. + * - 'o' Convert and format as an unsigned octal integer. + * - 'x' Convert and format as an unsigned hexadecimal integer, + * using lowercase hex digits. + * - 'X' Convert and format as an unsigned hexadecimal integer, + * using uppercase hex digits. + * - 's' Convert and format as a string. + * - '%' Print a literal '%' character. + * + * @retval 0 success + * @retval EINVAL If @p fmt contains unrecognized format string + * specifiers. + * @retval ENOMEM If the @p outp is non-NULL, and the provided @p olen + * is too small to hold the encoded value. + * @retval EFTYPE If value coercion from @p value to a single string + * value via @p fmt is unsupported. + * @retval ERANGE If value coercion of @p value would overflow (or + * underflow) the representation defined by @p fmt. + */ +int +bhnd_nvram_val_vprintf(bhnd_nvram_val_t *value, const char *fmt, char *outp, + size_t *olen, va_list ap) +{ + const void *elem; + size_t elen; + size_t limit, nbytes; + int error; + + elem = NULL; + + /* Determine output byte limit */ + nbytes = 0; + if (outp != NULL) + limit = *olen; + else + limit = 0; + +#define WRITE_CHAR(_c) do { \ + if (limit > nbytes) \ + *(outp + nbytes) = _c; \ + \ + if (nbytes == SIZE_MAX) \ + return (EFTYPE); \ + nbytes++; \ +} while (0) + + /* Encode string value as per the format string */ + for (const char *p = fmt; *p != '\0'; p++) { + const char *delim; + size_t precision, width, delim_len; + u_long repeat, bits; + bool alt_form, ladjust, have_precision; + char padc, signc, lenc; + + padc = ' '; + signc = '\0'; + lenc = '\0'; + delim = ""; + delim_len = 0; + + ladjust = false; + alt_form = false; + + have_precision = false; + precision = 1; + bits = 32; + width = 0; + repeat = 1; + + /* Copy all input to output until we hit a format specifier */ + if (*p != '%') { + WRITE_CHAR(*p); + continue; + } + + /* Hit '%' -- is this followed by an escaped '%' literal? */ + p++; + if (*p == '%') { + WRITE_CHAR('%'); + p++; + continue; + } + + /* Parse repeat specifier */ + if (*p == '[') { + p++; + + /* Determine repeat count */ + if (*p == ']') { + /* Repeat consumes all input */ + repeat = bhnd_nvram_val_nelem(value); + } else if (*p == '*') { + /* Repeat is supplied as an argument */ + repeat = va_arg(ap, size_t); + p++; + } else { + char *endp; + + /* Repeat specified as argument */ + repeat = strtoul(p, &endp, 10); + if (p == endp) { + BHND_NV_LOG("error parsing repeat " + "count at '%s'", p); + return (EINVAL); + } + + /* Advance past repeat count */ + p = endp; + } + + /* Advance past terminating ']' */ + if (*p != ']') { + BHND_NV_LOG("error parsing repeat count at " + "'%s'", p); + return (EINVAL); + } + p++; + + delim = va_arg(ap, const char *); + delim_len = strlen(delim); + } + + /* Parse flags */ + while (*p != '\0') { + const char *np; + bool stop; + + stop = false; + np = p+1; + + switch (*p) { + case '#': + alt_form = true; + break; + case '0': + padc = '0'; + break; + case '-': + ladjust = true; + break; + case ' ': + /* Must not override '+' */ + if (signc != '+') + signc = ' '; + break; + case '+': + signc = '+'; + break; + default: + /* Non-flag character */ + stop = true; + break; + } + + if (stop) + break; + else + p = np; + } + + /* Parse minimum width */ + if (*p == '*') { + ssize_t arg; + + /* Width is supplied as an argument */ + arg = va_arg(ap, int); + + /* Negative width argument is interpreted as + * '-' flag followed by positive width */ + if (arg < 0) { + ladjust = true; + arg = -arg; + } + + width = arg; + p++; + } else if (bhnd_nv_isdigit(*p)) { + uint32_t v; + size_t len, parsed; + + /* Parse width value */ + len = sizeof(v); + error = bhnd_nvram_parse_int(p, strlen(p), 10, &parsed, + &v, &len, BHND_NVRAM_TYPE_UINT32); + if (error) { + BHND_NV_LOG("error parsing width %s: %d\n", p, + error); + return (EINVAL); + } + + /* Save width and advance input */ + width = v; + p += parsed; + } + + /* Parse precision */ + if (*p == '.') { + uint32_t v; + size_t len, parsed; + + p++; + have_precision = true; + + if (*p == '*') { + ssize_t arg; + + /* Precision is specified as an argument */ + arg = va_arg(ap, int); + + /* Negative precision argument is interpreted + * as '-' flag followed by positive + * precision */ + if (arg < 0) { + ladjust = true; + arg = -arg; + } + + precision = arg; + } else if (!bhnd_nv_isdigit(*p)) { + /* Implicit precision of 0 */ + precision = 0; + } else { + /* Parse precision value */ + len = sizeof(v); + error = bhnd_nvram_parse_int(p, strlen(p), 10, + &parsed, &v, &len, + BHND_NVRAM_TYPE_UINT32); + if (error) { + BHND_NV_LOG("error parsing width %s: " + "%d\n", p, error); + return (EINVAL); + } + + /* Save precision and advance input */ + precision = v; + p += parsed; + } + } + + /* Parse length modifiers */ + while (*p != '\0') { + const char *np; + bool stop; + + stop = false; + np = p+1; + + switch (*p) { + case 'h': + if (lenc == '\0') { + /* Set initial length value */ + lenc = *p; + bits = 16; + } else if (lenc == *p && bits == 16) { + /* Modify previous length value */ + bits = 8; + } else { + BHND_NV_LOG("invalid length modifier " + "%c\n", *p); + return (EINVAL); + } + break; + + case 'l': + if (lenc == '\0') { + /* Set initial length value */ + lenc = *p; + bits = 32; + } else if (lenc == *p && bits == 32) { + /* Modify previous length value */ + bits = 64; + } else { + BHND_NV_LOG("invalid length modifier " + "%c\n", *p); + return (EINVAL); + } + break; + + case 'j': + /* Conflicts with all other length + * specifications, and may only occur once */ + if (lenc != '\0') { + BHND_NV_LOG("invalid length modifier " + "%c\n", *p); + return (EINVAL); + } + + lenc = *p; + bits = 64; + break; + + case 'I': { + char *endp; + + /* Conflicts with all other length + * specifications, and may only occur once */ + if (lenc != '\0') { + BHND_NV_LOG("invalid length modifier " + "%c\n", *p); + return (EINVAL); + } + + lenc = *p; + + /* Parse the length specifier value */ + p++; + bits = strtoul(p, &endp, 10); + if (p == endp) { + BHND_NV_LOG("invalid size specifier: " + "%s\n", p); + return (EINVAL); + } + + /* Advance input past the parsed integer */ + np = endp; + break; + } + default: + /* Non-length modifier character */ + stop = true; + break; + } + + if (stop) + break; + else + p = np; + } + + /* Parse conversion specifier and format the value(s) */ + for (u_long n = 0; n < repeat; n++) { + bhnd_nvram_type arg_type; + size_t arg_size; + size_t i; + u_long base; + bool is_signed, is_upper; + + is_signed = false; + is_upper = false; + base = 0; + + /* Fetch next element */ + elem = bhnd_nvram_val_next(value, elem, &elen); + if (elem == NULL) { + BHND_NV_LOG("format string references more " + "than %zu available value elements\n", + bhnd_nvram_val_nelem(value)); + return (EINVAL); + } + + /* + * If this is not the first value, append the delimiter. + */ + if (n > 0) { + size_t nremain = 0; + if (limit > nbytes) + nremain = limit - nbytes; + + if (nremain >= delim_len) + memcpy(outp + nbytes, delim, delim_len); + + /* Add delimiter length to the total byte count */ + if (SIZE_MAX - nbytes < delim_len) + return (EFTYPE); /* overflows size_t */ + + nbytes += delim_len; + } + + /* Parse integer conversion specifiers */ + switch (*p) { + case 'd': + case 'i': + base = 10; + is_signed = true; + break; + + case 'u': + base = 10; + break; + + case 'o': + base = 8; + break; + + case 'x': + base = 16; + break; + + case 'X': + base = 16; + is_upper = true; + break; + } + + /* Format argument */ + switch (*p) { +#define NV_ENCODE_INT(_width) do { \ + arg_type = (is_signed) ? BHND_NVRAM_TYPE_INT ## _width : \ + BHND_NVRAM_TYPE_UINT ## _width; \ + arg_size = sizeof(v.u ## _width); \ + error = bhnd_nvram_val_encode_elem(value, elem, elen, \ + &v.u ## _width, &arg_size, arg_type); \ + if (error) { \ + BHND_NV_LOG("error encoding argument as %s: %d\n", \ + bhnd_nvram_type_name(arg_type), error); \ + return (error); \ + } \ + \ + if (is_signed) { \ + if (v.i ## _width < 0) { \ + add_neg = true; \ + numval = (int64_t)-(v.i ## _width); \ + } else { \ + numval = (int64_t) (v.i ## _width); \ + } \ + } else { \ + numval = v.u ## _width; \ + } \ +} while(0) + case 'd': + case 'i': + case 'u': + case 'o': + case 'x': + case 'X': { + char numbuf[NV_NUMSTR_MAX]; + char *sptr; + uint64_t numval; + size_t slen; + bool add_neg; + union { + uint8_t u8; + uint16_t u16; + uint32_t u32; + uint64_t u64; + int8_t i8; + int16_t i16; + int32_t i32; + int64_t i64; + } v; + + add_neg = false; + + /* If precision is specified, it overrides + * (and behaves identically) to a zero-prefixed + * minimum width */ + if (have_precision) { + padc = '0'; + width = precision; + ladjust = false; + } + + /* If zero-padding is used, value must be right + * adjusted */ + if (padc == '0') + ladjust = false; + + /* Request encode to the appropriate integer + * type, and then promote to common 64-bit + * representation */ + switch (bits) { + case 8: + NV_ENCODE_INT(8); + break; + case 16: + NV_ENCODE_INT(16); + break; + case 32: + NV_ENCODE_INT(32); + break; + case 64: + NV_ENCODE_INT(64); + break; + default: + BHND_NV_LOG("invalid length specifier: " + "%lu\n", bits); + return (EINVAL); + } +#undef NV_ENCODE_INT + + /* If a precision of 0 is specified and the + * value is also zero, no characters should + * be produced */ + if (have_precision && precision == 0 && + numval == 0) + { + break; + } + + /* Emit string representation to local buffer */ + BHND_NV_ASSERT(base <= 16, ("invalid base")); + sptr = numbuf + nitems(numbuf) - 1; + for (slen = 0; slen < sizeof(numbuf); slen++) { + char c; + uint64_t n; + + n = numval % base; + c = bhnd_nv_hex2ascii(n); + if (is_upper) + c = bhnd_nv_toupper(c); + + sptr--; + *sptr = c; + + numval /= (uint64_t)base; + if (numval == 0) { + slen++; + break; + } + } + + arg_size = slen; + + /* Reserve space for 0/0x prefix? */ + if (alt_form) { + if (numval == 0) { + /* If 0, no prefix */ + alt_form = false; + } else if (base == 8) { + arg_size += 1; /* 0 */ + } else if (base == 16) { + arg_size += 2; /* 0x/0X */ + } + } + + /* Reserve space for ' ', '+', or '-' prefix? */ + if (add_neg || signc != '\0') { + if (add_neg) + signc = '-'; + + arg_size++; + } + + /* Right adjust (if using spaces) */ + if (!ladjust && padc != '0') { + for (i = arg_size; i < width; i++) + WRITE_CHAR(padc); + } + + if (signc != '\0') + WRITE_CHAR(signc); + + if (alt_form) { + if (base == 8) { + WRITE_CHAR('0'); + } else if (base == 16) { + WRITE_CHAR('0'); + if (is_upper) + WRITE_CHAR('X'); + else + WRITE_CHAR('x'); + } + } + + /* Right adjust (if using zeros) */ + if (!ladjust && padc == '0') { + for (i = slen; i < width; i++) + WRITE_CHAR(padc); + } + + /* Write the string to our output buffer */ + if (limit > nbytes && limit - nbytes >= slen) + memcpy(outp + nbytes, sptr, slen); + + /* Update the total byte count */ + if (SIZE_MAX - nbytes < arg_size) + return (EFTYPE); /* overflows size_t */ + + nbytes += arg_size; + + /* Left adjust */ + for (i = arg_size; ladjust && i < width; i++) + WRITE_CHAR(padc); + + break; + } + + case 's': { + char *s; + size_t slen; + + /* Query the total length of the element when + * converted to a string */ + arg_type = BHND_NVRAM_TYPE_STRING; + error = bhnd_nvram_val_encode_elem(value, elem, + elen, NULL, &arg_size, arg_type); + if (error) { + BHND_NV_LOG("error encoding argument " + "as %s: %d\n", + bhnd_nvram_type_name(arg_type), + error); + return (error); + } + + /* Do not include trailing NUL in the string + * length */ + if (arg_size > 0) + arg_size--; + + /* Right adjust */ + for (i = arg_size; !ladjust && i < width; i++) + WRITE_CHAR(padc); + + /* Determine output positition and remaining + * buffer space */ + if (limit > nbytes) { + s = outp + nbytes; + slen = limit - nbytes; + } else { + s = NULL; + slen = 0; + } + + /* Encode the string to our output buffer */ + error = bhnd_nvram_val_encode_elem(value, elem, + elen, s, &slen, arg_type); + if (error && error != ENOMEM) { + BHND_NV_LOG("error encoding argument " + "as %s: %d\n", + bhnd_nvram_type_name(arg_type), + error); + return (error); + } + + /* Update the total byte count */ + if (SIZE_MAX - nbytes < arg_size) + return (EFTYPE); /* overflows size_t */ + + nbytes += arg_size; + + /* Left adjust */ + for (i = arg_size; ladjust && i < width; i++) + WRITE_CHAR(padc); + + break; + } + + case 'c': { + char c; + + arg_type = BHND_NVRAM_TYPE_CHAR; + arg_size = bhnd_nvram_value_size(arg_type, NULL, + 0, 1); + + /* Encode as single character */ + error = bhnd_nvram_val_encode_elem(value, elem, + elen, &c, &arg_size, arg_type); + if (error) { + BHND_NV_LOG("error encoding argument " + "as %s: %d\n", + bhnd_nvram_type_name(arg_type), + error); + return (error); + } + + BHND_NV_ASSERT(arg_size == sizeof(c), + ("invalid encoded size")); + + /* Right adjust */ + for (i = arg_size; !ladjust && i < width; i++) + WRITE_CHAR(padc); + + WRITE_CHAR(padc); + + /* Left adjust */ + for (i = arg_size; ladjust && i < width; i++) + WRITE_CHAR(padc); + + break; + } + } + } + } + + /* Append terminating NUL */ + if (limit > nbytes) + *(outp + nbytes) = '\0'; + + if (nbytes < SIZE_MAX) + nbytes++; + else + return (EFTYPE); + + /* Report required space */ + *olen = nbytes; + if (limit < nbytes) { + if (outp != NULL) + return (ENOMEM); + } + + return (0); +} |
