Path: blob/main/sys/dev/bhnd/nvram/bhnd_nvram_value_prf.c
39536 views
/*-1* Copyright (c) 2015-2016 Landon Fuller <[email protected]>2* All rights reserved.3*4* Redistribution and use in source and binary forms, with or without5* modification, are permitted provided that the following conditions6* are met:7* 1. Redistributions of source code must retain the above copyright8* notice, this list of conditions and the following disclaimer,9* without modification.10* 2. Redistributions in binary form must reproduce at minimum a disclaimer11* similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any12* redistribution must be conditioned upon including a substantially13* similar Disclaimer requirement for further binary redistribution.14*15* NO WARRANTY16* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS17* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT18* LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY19* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL20* THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY,21* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF22* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS23* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER24* IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)25* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF26* THE POSSIBILITY OF SUCH DAMAGES.27*/2829#include <sys/param.h>30#include <sys/limits.h>31#include <sys/sbuf.h>3233#ifdef _KERNEL3435#include <sys/ctype.h>36#include <sys/kernel.h>37#include <sys/malloc.h>38#include <sys/systm.h>3940#include <machine/_inttypes.h>4142#else /* !_KERNEL */4344#include <ctype.h>45#include <inttypes.h>46#include <errno.h>47#include <stdlib.h>48#include <string.h>4950#endif /* _KERNEL */5152#include "bhnd_nvram_private.h"53#include "bhnd_nvram_valuevar.h"5455#ifdef _KERNEL56#define bhnd_nv_hex2ascii(hex) hex2ascii(hex)57#else /* !_KERNEL */58static char const bhnd_nv_hex2ascii[] = "0123456789abcdefghijklmnopqrstuvwxyz";59#define bhnd_nv_hex2ascii(hex) (bhnd_nv_hex2ascii[hex])60#endif /* _KERNEL */6162/**63* Maximum size, in bytes, of a string-encoded NVRAM integer value, not64* including any prefix (0x, 0, etc).65*66* We assume the largest possible encoding is the base-2 representation67* of a 64-bit integer.68*/69#define NV_NUMSTR_MAX ((sizeof(uint64_t) * CHAR_BIT) + 1)7071/**72* Format a string representation of @p value using @p fmt, with, writing the73* result to @p outp.74*75* @param value The value to be formatted.76* @param fmt The format string.77* @param[out] outp On success, the string will be written to this78* buffer. This argment may be NULL if the value is79* not desired.80* @param[in,out] olen The capacity of @p outp. On success, will be set81* to the actual number of bytes required for the82* requested string encoding (including a trailing83* NUL).84*85* Refer to bhnd_nvram_val_vprintf() for full format string documentation.86*87* @retval 0 success88* @retval EINVAL If @p fmt contains unrecognized format string89* specifiers.90* @retval ENOMEM If the @p outp is non-NULL, and the provided @p olen91* is too small to hold the encoded value.92* @retval EFTYPE If value coercion from @p value to a single string93* value via @p fmt is unsupported.94* @retval ERANGE If value coercion of @p value would overflow (or95* underflow) the representation defined by @p fmt.96*/97int98bhnd_nvram_val_printf(bhnd_nvram_val *value, const char *fmt, char *outp,99size_t *olen, ...)100{101va_list ap;102int error;103104va_start(ap, olen);105error = bhnd_nvram_val_vprintf(value, fmt, outp, olen, ap);106va_end(ap);107108return (error);109}110111/**112* Format a string representation of the elements of @p value using @p fmt,113* writing the result to @p outp.114*115* @param value The value to be formatted.116* @param fmt The format string.117* @param[out] outp On success, the string will be written to this118* buffer. This argment may be NULL if the value is119* not desired.120* @param[in,out] olen The capacity of @p outp. On success, will be set121* to the actual number of bytes required for the122* requested string encoding (including a trailing123* NUL).124* @param ap Argument list.125*126* @par Format Strings127*128* Value format strings are similar, but not identical to, those used129* by printf(3).130*131* Format specifier format:132* %[repeat][flags][width][.precision][length modifier][specifier]133*134* The format specifier is interpreted as an encoding directive for an135* individual value element; each format specifier will fetch the next element136* from the value, encode the element as the appropriate type based on the137* length modifiers and specifier, and then format the result as a string.138*139* For example, given a string value of '0x000F', and a format specifier of140* '%#hhx', the value will be asked to encode its first element as141* BHND_NVRAM_TYPE_UINT8. String formatting will then be applied to the 8-bit142* unsigned integer representation, producing a string value of "0xF".143*144* Repeat:145* - [digits] Repeatedly apply the format specifier to the input146* value's elements up to `digits` times. The delimiter147* must be passed as a string in the next variadic148* argument.149* - [] Repeatedly apply the format specifier to the input150* value's elements until all elements have been. The151* processed. The delimiter must be passed as a string in152* the next variadic argument.153* - [*] Repeatedly apply the format specifier to the input154* value's elements. The repeat count is read from the155* next variadic argument as a size_t value156*157* Flags:158* - '#' use alternative form (e.g. 0x/0X prefixing of hex159* strings).160* - '0' zero padding161* - '-' left adjust padding162* - '+' include a sign character163* - ' ' include a space in place of a sign character for164* positive numbers.165*166* Width/Precision:167* - digits minimum field width.168* - * read the minimum field width from the next variadic169* argument as a ssize_t value. A negative value enables170* left adjustment.171* - .digits field precision.172* - .* read the field precision from the next variadic argument173* as a ssize_t value. A negative value enables left174* adjustment.175*176* Length Modifiers:177* - 'hh', 'I8' Convert the value to an 8-bit signed or unsigned178* integer.179* - 'h', 'I16' Convert the value to an 16-bit signed or unsigned180* integer.181* - 'l', 'I32' Convert the value to an 32-bit signed or unsigned182* integer.183* - 'll', 'j', 'I64' Convert the value to an 64-bit signed or unsigned184* integer.185*186* Data Specifiers:187* - 'd', 'i' Convert and format as a signed decimal integer.188* - 'u' Convert and format as an unsigned decimal integer.189* - 'o' Convert and format as an unsigned octal integer.190* - 'x' Convert and format as an unsigned hexadecimal integer,191* using lowercase hex digits.192* - 'X' Convert and format as an unsigned hexadecimal integer,193* using uppercase hex digits.194* - 's' Convert and format as a string.195* - '%' Print a literal '%' character.196*197* @retval 0 success198* @retval EINVAL If @p fmt contains unrecognized format string199* specifiers.200* @retval ENOMEM If the @p outp is non-NULL, and the provided @p olen201* is too small to hold the encoded value.202* @retval EFTYPE If value coercion from @p value to a single string203* value via @p fmt is unsupported.204* @retval ERANGE If value coercion of @p value would overflow (or205* underflow) the representation defined by @p fmt.206*/207int208bhnd_nvram_val_vprintf(bhnd_nvram_val *value, const char *fmt, char *outp,209size_t *olen, va_list ap)210{211const void *elem;212size_t elen;213size_t limit, nbytes;214int error;215216elem = NULL;217218/* Determine output byte limit */219nbytes = 0;220if (outp != NULL)221limit = *olen;222else223limit = 0;224225#define WRITE_CHAR(_c) do { \226if (limit > nbytes) \227*(outp + nbytes) = _c; \228\229if (nbytes == SIZE_MAX) \230return (EFTYPE); \231nbytes++; \232} while (0)233234/* Encode string value as per the format string */235for (const char *p = fmt; *p != '\0'; p++) {236const char *delim;237size_t precision, width, delim_len;238u_long repeat, bits;239bool alt_form, ladjust, have_precision;240char padc, signc, lenc;241242padc = ' ';243signc = '\0';244lenc = '\0';245delim = "";246delim_len = 0;247248ladjust = false;249alt_form = false;250251have_precision = false;252precision = 1;253bits = 32;254width = 0;255repeat = 1;256257/* Copy all input to output until we hit a format specifier */258if (*p != '%') {259WRITE_CHAR(*p);260continue;261}262263/* Hit '%' -- is this followed by an escaped '%' literal? */264p++;265if (*p == '%') {266WRITE_CHAR('%');267p++;268continue;269}270271/* Parse repeat specifier */272if (*p == '[') {273p++;274275/* Determine repeat count */276if (*p == ']') {277/* Repeat consumes all input */278repeat = bhnd_nvram_val_nelem(value);279} else if (*p == '*') {280/* Repeat is supplied as an argument */281repeat = va_arg(ap, size_t);282p++;283} else {284char *endp;285286/* Repeat specified as argument */287repeat = strtoul(p, &endp, 10);288if (p == endp) {289BHND_NV_LOG("error parsing repeat "290"count at '%s'", p);291return (EINVAL);292}293294/* Advance past repeat count */295p = endp;296}297298/* Advance past terminating ']' */299if (*p != ']') {300BHND_NV_LOG("error parsing repeat count at "301"'%s'", p);302return (EINVAL);303}304p++;305306delim = va_arg(ap, const char *);307delim_len = strlen(delim);308}309310/* Parse flags */311while (*p != '\0') {312const char *np;313bool stop;314315stop = false;316np = p+1;317318switch (*p) {319case '#':320alt_form = true;321break;322case '0':323padc = '0';324break;325case '-':326ladjust = true;327break;328case ' ':329/* Must not override '+' */330if (signc != '+')331signc = ' ';332break;333case '+':334signc = '+';335break;336default:337/* Non-flag character */338stop = true;339break;340}341342if (stop)343break;344else345p = np;346}347348/* Parse minimum width */349if (*p == '*') {350ssize_t arg;351352/* Width is supplied as an argument */353arg = va_arg(ap, int);354355/* Negative width argument is interpreted as356* '-' flag followed by positive width */357if (arg < 0) {358ladjust = true;359arg = -arg;360}361362width = arg;363p++;364} else if (bhnd_nv_isdigit(*p)) {365uint32_t v;366size_t len, parsed;367368/* Parse width value */369len = sizeof(v);370error = bhnd_nvram_parse_int(p, strlen(p), 10, &parsed,371&v, &len, BHND_NVRAM_TYPE_UINT32);372if (error) {373BHND_NV_LOG("error parsing width %s: %d\n", p,374error);375return (EINVAL);376}377378/* Save width and advance input */379width = v;380p += parsed;381}382383/* Parse precision */384if (*p == '.') {385uint32_t v;386size_t len, parsed;387388p++;389have_precision = true;390391if (*p == '*') {392ssize_t arg;393394/* Precision is specified as an argument */395arg = va_arg(ap, int);396397/* Negative precision argument is interpreted398* as '-' flag followed by positive399* precision */400if (arg < 0) {401ladjust = true;402arg = -arg;403}404405precision = arg;406} else if (!bhnd_nv_isdigit(*p)) {407/* Implicit precision of 0 */408precision = 0;409} else {410/* Parse precision value */411len = sizeof(v);412error = bhnd_nvram_parse_int(p, strlen(p), 10,413&parsed, &v, &len,414BHND_NVRAM_TYPE_UINT32);415if (error) {416BHND_NV_LOG("error parsing width %s: "417"%d\n", p, error);418return (EINVAL);419}420421/* Save precision and advance input */422precision = v;423p += parsed;424}425}426427/* Parse length modifiers */428while (*p != '\0') {429const char *np;430bool stop;431432stop = false;433np = p+1;434435switch (*p) {436case 'h':437if (lenc == '\0') {438/* Set initial length value */439lenc = *p;440bits = 16;441} else if (lenc == *p && bits == 16) {442/* Modify previous length value */443bits = 8;444} else {445BHND_NV_LOG("invalid length modifier "446"%c\n", *p);447return (EINVAL);448}449break;450451case 'l':452if (lenc == '\0') {453/* Set initial length value */454lenc = *p;455bits = 32;456} else if (lenc == *p && bits == 32) {457/* Modify previous length value */458bits = 64;459} else {460BHND_NV_LOG("invalid length modifier "461"%c\n", *p);462return (EINVAL);463}464break;465466case 'j':467/* Conflicts with all other length468* specifications, and may only occur once */469if (lenc != '\0') {470BHND_NV_LOG("invalid length modifier "471"%c\n", *p);472return (EINVAL);473}474475lenc = *p;476bits = 64;477break;478479case 'I': {480char *endp;481482/* Conflicts with all other length483* specifications, and may only occur once */484if (lenc != '\0') {485BHND_NV_LOG("invalid length modifier "486"%c\n", *p);487return (EINVAL);488}489490lenc = *p;491492/* Parse the length specifier value */493p++;494bits = strtoul(p, &endp, 10);495if (p == endp) {496BHND_NV_LOG("invalid size specifier: "497"%s\n", p);498return (EINVAL);499}500501/* Advance input past the parsed integer */502np = endp;503break;504}505default:506/* Non-length modifier character */507stop = true;508break;509}510511if (stop)512break;513else514p = np;515}516517/* Parse conversion specifier and format the value(s) */518for (u_long n = 0; n < repeat; n++) {519bhnd_nvram_type arg_type;520size_t arg_size;521size_t i;522u_long base;523bool is_signed, is_upper;524525is_signed = false;526is_upper = false;527base = 0;528529/* Fetch next element */530elem = bhnd_nvram_val_next(value, elem, &elen);531if (elem == NULL) {532BHND_NV_LOG("format string references more "533"than %zu available value elements\n",534bhnd_nvram_val_nelem(value));535return (EINVAL);536}537538/*539* If this is not the first value, append the delimiter.540*/541if (n > 0) {542size_t nremain = 0;543if (limit > nbytes)544nremain = limit - nbytes;545546if (nremain >= delim_len)547memcpy(outp + nbytes, delim, delim_len);548549/* Add delimiter length to the total byte count */550if (SIZE_MAX - nbytes < delim_len)551return (EFTYPE); /* overflows size_t */552553nbytes += delim_len;554}555556/* Parse integer conversion specifiers */557switch (*p) {558case 'd':559case 'i':560base = 10;561is_signed = true;562break;563564case 'u':565base = 10;566break;567568case 'o':569base = 8;570break;571572case 'x':573base = 16;574break;575576case 'X':577base = 16;578is_upper = true;579break;580}581582/* Format argument */583switch (*p) {584#define NV_ENCODE_INT(_width) do { \585arg_type = (is_signed) ? BHND_NVRAM_TYPE_INT ## _width : \586BHND_NVRAM_TYPE_UINT ## _width; \587arg_size = sizeof(v.u ## _width); \588error = bhnd_nvram_val_encode_elem(value, elem, elen, \589&v.u ## _width, &arg_size, arg_type); \590if (error) { \591BHND_NV_LOG("error encoding argument as %s: %d\n", \592bhnd_nvram_type_name(arg_type), error); \593return (error); \594} \595\596if (is_signed) { \597if (v.i ## _width < 0) { \598add_neg = true; \599numval = (int64_t)-(v.i ## _width); \600} else { \601numval = (int64_t) (v.i ## _width); \602} \603} else { \604numval = v.u ## _width; \605} \606} while(0)607case 'd':608case 'i':609case 'u':610case 'o':611case 'x':612case 'X': {613char numbuf[NV_NUMSTR_MAX];614char *sptr;615uint64_t numval;616size_t slen;617bool add_neg;618union {619uint8_t u8;620uint16_t u16;621uint32_t u32;622uint64_t u64;623int8_t i8;624int16_t i16;625int32_t i32;626int64_t i64;627} v;628629add_neg = false;630631/* If precision is specified, it overrides632* (and behaves identically) to a zero-prefixed633* minimum width */634if (have_precision) {635padc = '0';636width = precision;637ladjust = false;638}639640/* If zero-padding is used, value must be right641* adjusted */642if (padc == '0')643ladjust = false;644645/* Request encode to the appropriate integer646* type, and then promote to common 64-bit647* representation */648switch (bits) {649case 8:650NV_ENCODE_INT(8);651break;652case 16:653NV_ENCODE_INT(16);654break;655case 32:656NV_ENCODE_INT(32);657break;658case 64:659NV_ENCODE_INT(64);660break;661default:662BHND_NV_LOG("invalid length specifier: "663"%lu\n", bits);664return (EINVAL);665}666#undef NV_ENCODE_INT667668/* If a precision of 0 is specified and the669* value is also zero, no characters should670* be produced */671if (have_precision && precision == 0 &&672numval == 0)673{674break;675}676677/* Emit string representation to local buffer */678BHND_NV_ASSERT(base <= 16, ("invalid base"));679sptr = numbuf + nitems(numbuf) - 1;680for (slen = 0; slen < sizeof(numbuf); slen++) {681char c;682uint64_t n;683684n = numval % base;685c = bhnd_nv_hex2ascii(n);686if (is_upper)687c = bhnd_nv_toupper(c);688689sptr--;690*sptr = c;691692numval /= (uint64_t)base;693if (numval == 0) {694slen++;695break;696}697}698699arg_size = slen;700701/* Reserve space for 0/0x prefix? */702if (alt_form) {703if (numval == 0) {704/* If 0, no prefix */705alt_form = false;706} else if (base == 8) {707arg_size += 1; /* 0 */708} else if (base == 16) {709arg_size += 2; /* 0x/0X */710}711}712713/* Reserve space for ' ', '+', or '-' prefix? */714if (add_neg || signc != '\0') {715if (add_neg)716signc = '-';717718arg_size++;719}720721/* Right adjust (if using spaces) */722if (!ladjust && padc != '0') {723for (i = arg_size; i < width; i++)724WRITE_CHAR(padc);725}726727if (signc != '\0')728WRITE_CHAR(signc);729730if (alt_form) {731if (base == 8) {732WRITE_CHAR('0');733} else if (base == 16) {734WRITE_CHAR('0');735if (is_upper)736WRITE_CHAR('X');737else738WRITE_CHAR('x');739}740}741742/* Right adjust (if using zeros) */743if (!ladjust && padc == '0') {744for (i = slen; i < width; i++)745WRITE_CHAR(padc);746}747748/* Write the string to our output buffer */749if (limit > nbytes && limit - nbytes >= slen)750memcpy(outp + nbytes, sptr, slen);751752/* Update the total byte count */753if (SIZE_MAX - nbytes < arg_size)754return (EFTYPE); /* overflows size_t */755756nbytes += arg_size;757758/* Left adjust */759for (i = arg_size; ladjust && i < width; i++)760WRITE_CHAR(padc);761762break;763}764765case 's': {766char *s;767size_t slen;768769/* Query the total length of the element when770* converted to a string */771arg_type = BHND_NVRAM_TYPE_STRING;772error = bhnd_nvram_val_encode_elem(value, elem,773elen, NULL, &arg_size, arg_type);774if (error) {775BHND_NV_LOG("error encoding argument "776"as %s: %d\n",777bhnd_nvram_type_name(arg_type),778error);779return (error);780}781782/* Do not include trailing NUL in the string783* length */784if (arg_size > 0)785arg_size--;786787/* Right adjust */788for (i = arg_size; !ladjust && i < width; i++)789WRITE_CHAR(padc);790791/* Determine output positition and remaining792* buffer space */793if (limit > nbytes) {794s = outp + nbytes;795slen = limit - nbytes;796} else {797s = NULL;798slen = 0;799}800801/* Encode the string to our output buffer */802error = bhnd_nvram_val_encode_elem(value, elem,803elen, s, &slen, arg_type);804if (error && error != ENOMEM) {805BHND_NV_LOG("error encoding argument "806"as %s: %d\n",807bhnd_nvram_type_name(arg_type),808error);809return (error);810}811812/* Update the total byte count */813if (SIZE_MAX - nbytes < arg_size)814return (EFTYPE); /* overflows size_t */815816nbytes += arg_size;817818/* Left adjust */819for (i = arg_size; ladjust && i < width; i++)820WRITE_CHAR(padc);821822break;823}824825case 'c': {826char c;827828arg_type = BHND_NVRAM_TYPE_CHAR;829arg_size = bhnd_nvram_type_width(arg_type);830831/* Encode as single character */832error = bhnd_nvram_val_encode_elem(value, elem,833elen, &c, &arg_size, arg_type);834if (error) {835BHND_NV_LOG("error encoding argument "836"as %s: %d\n",837bhnd_nvram_type_name(arg_type),838error);839return (error);840}841842BHND_NV_ASSERT(arg_size == sizeof(c),843("invalid encoded size"));844845/* Right adjust */846for (i = arg_size; !ladjust && i < width; i++)847WRITE_CHAR(padc);848849WRITE_CHAR(padc);850851/* Left adjust */852for (i = arg_size; ladjust && i < width; i++)853WRITE_CHAR(padc);854855break;856}857}858}859}860861/* Append terminating NUL */862if (limit > nbytes)863*(outp + nbytes) = '\0';864865if (nbytes < SIZE_MAX)866nbytes++;867else868return (EFTYPE);869870/* Report required space */871*olen = nbytes;872if (limit < nbytes) {873if (outp != NULL)874return (ENOMEM);875}876877return (0);878}879880881