/*- * Copyright (c) 2015-2016 Landon Fuller * 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 __FBSDID("$FreeBSD$"); #include #include #ifdef _KERNEL #include #include #include #include #include #else /* !_KERNEL */ #include #include #include #include #include #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 *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 *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_type_width(arg_type); /* 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); }