884 lines
21 KiB
C
884 lines
21 KiB
C
|
/*-
|
||
|
* 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);
|
||
|
}
|