/** * Parse a 'name=value' string. * * @param env The string to be parsed. * @param env_len The length of @p envp. * @param delim The delimiter used in @p envp. This will generally be '='. * @param[out] name If not NULL, a pointer to the name string. This argument * may be NULL. * @param[out] name_len On success, the length of the name substring. This * argument may be NULL. * @param[out] value On success, a pointer to the value substring. This argument * may be NULL. * @param[out] value_len On success, the length of the value substring. This * argument may be NULL. * * @retval 0 success * @retval EINVAL if parsing @p envp fails. */ int bhnd_nvram_parse_env(const char *env, size_t env_len, char delim, const char **name, size_t *name_len, const char **value, size_t *value_len) { const char *p; /* Name */ if ((p = memchr(env, delim, env_len)) == NULL) { BHND_NV_LOG("delimiter '%c' not found in '%.*s'\n", delim, BHND_NV_PRINT_WIDTH(env_len), env); return (EINVAL); } /* Name */ if (name != NULL) *name = env; if (name_len != NULL) *name_len = p - env; /* Skip delim */ p++; /* Value */ if (value != NULL) *value = p; if (value_len != NULL) *value_len = env_len - (p - env); return (0); }
/** * Parses the string in the optionally NUL-terminated @p str to as an integer * value of @p otype, accepting any integer format supported by the standard * strtoul(). * * - Any leading whitespace in @p str -- as defined by the equivalent of * calling isspace_l() with an ASCII locale -- will be ignored. * - A @p str may be prefixed with a single optional '+' or '-' sign denoting * signedness. * - A hexadecimal @p str may include an '0x' or '0X' prefix, denoting that a * base 16 integer follows. * - An octal @p str may include a '0' prefix, denoting that an octal integer * follows. * * If a @p base of 0 is specified, the base will be determined according * to the string's initial prefix, as per strtoul()'s documented behavior. * * When parsing a base 16 integer to a signed representation, if no explicit * sign prefix is given, the string will be parsed as the raw two's complement * representation of the signed integer value. * * @param str The string to be parsed. * @param maxlen The maximum number of bytes to be read in * @p str. * @param base The input string's base (2-36), or 0. * @param[out] nbytes On success or failure, will be set to the total * number of parsed bytes. If the total number of * bytes is not desired, a NULL pointer may be * provided. * @param[out] outp On success, the parsed integer value will be * written to @p outp. 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 size of the requested value. * @param otype The integer type to be parsed. * * @retval 0 success * @retval EINVAL if an invalid @p base is specified. * @retval EINVAL if an unsupported (or non-integer) @p otype is * specified. * @retval ENOMEM If @p outp is non-NULL and a buffer of @p olen is too * small to hold the requested value. * @retval EFTYPE if @p str cannot be parsed as an integer of @p base. * @retval ERANGE If the integer parsed from @p str is too large to be * represented as a value of @p otype. */ int bhnd_nvram_parse_int(const char *str, size_t maxlen, u_int base, size_t *nbytes, void *outp, size_t *olen, bhnd_nvram_type otype) { uint64_t value; uint64_t carry_max, value_max; uint64_t type_max; size_t limit, local_nbytes; size_t ndigits; bool negative, sign, twos_compl; /* Must be an integer type */ if (!bhnd_nvram_is_int_type(otype)) return (EINVAL); /* Determine output byte limit */ if (outp != NULL) limit = *olen; else limit = 0; /* We always need a byte count. If the caller provides a NULL nbytes, * track our position in a stack variable */ if (nbytes == NULL) nbytes = &local_nbytes; value = 0; ndigits = 0; *nbytes = 0; negative = false; sign = false; /* Validate the specified base */ if (base != 0 && !(base >= 2 && base <= 36)) return (EINVAL); /* Skip any leading whitespace */ for (; *nbytes < maxlen; (*nbytes)++) { if (!bhnd_nv_isspace(str[*nbytes])) break; } /* Empty string? */ if (*nbytes == maxlen) return (EFTYPE); /* Parse and skip sign */ if (str[*nbytes] == '-') { negative = true; sign = true; (*nbytes)++; } else if (str[*nbytes] == '+') { sign = true; (*nbytes)++; } /* Truncated after sign character? */ if (*nbytes == maxlen) return (EFTYPE); /* Identify (or validate) hex base, skipping 0x/0X prefix */ if (base == 16 || base == 0) { /* Check for (and skip) 0x/0X prefix */ if (maxlen - *nbytes >= 2 && str[*nbytes] == '0' && (str[*nbytes+1] == 'x' || str[*nbytes+1] == 'X')) { base = 16; (*nbytes) += 2; } } /* Truncated after hex prefix? */ if (*nbytes == maxlen) return (EFTYPE); /* Differentiate decimal/octal by looking for a leading 0 */ if (base == 0) { if (str[*nbytes] == '0') { base = 8; } else { base = 10; } } /* Only enable twos-compliment signed integer parsing enabled if the * input is base 16, and no explicit sign prefix was provided */ if (!sign && base == 16) twos_compl = true; else twos_compl = false; /* Determine the maximum value representable by the requested type */ switch (otype) { case BHND_NVRAM_TYPE_CHAR: case BHND_NVRAM_TYPE_UINT8: type_max = (uint64_t)UINT8_MAX; break; case BHND_NVRAM_TYPE_UINT16: type_max = (uint64_t)UINT16_MAX; break; case BHND_NVRAM_TYPE_UINT32: type_max = (uint64_t)UINT32_MAX; break; case BHND_NVRAM_TYPE_UINT64: type_max = (uint64_t)UINT64_MAX; break; case BHND_NVRAM_TYPE_INT8: if (twos_compl) type_max = (uint64_t)UINT8_MAX; else if (negative) type_max = -(uint64_t)INT8_MIN; else type_max = (uint64_t)INT8_MAX; break; case BHND_NVRAM_TYPE_INT16: if (twos_compl) type_max = (uint64_t)UINT16_MAX; else if (negative) type_max = -(uint64_t)INT16_MIN; else type_max = (uint64_t)INT16_MAX; break; case BHND_NVRAM_TYPE_INT32: if (twos_compl) type_max = (uint64_t)UINT32_MAX; else if (negative) type_max = -(uint64_t)INT32_MIN; else type_max = (uint64_t)INT32_MAX; break; case BHND_NVRAM_TYPE_INT64: if (twos_compl) type_max = (uint64_t)UINT64_MAX; else if (negative) type_max = -(uint64_t)INT64_MIN; else type_max = (uint64_t)INT64_MAX; break; default: BHND_NV_LOG("unsupported integer type: %d\n", otype); return (EINVAL); } /* The maximum value after which an additional carry would overflow */ value_max = type_max / (uint64_t)base; /* The maximum carry value given a value equal to value_max */ carry_max = type_max % (uint64_t)base; /* Consume input until we hit maxlen or a non-digit character */ for (; *nbytes < maxlen; (*nbytes)++) { u_long carry; char c; /* Parse carry value */ c = str[*nbytes]; if (bhnd_nv_isdigit(c)) { carry = c - '0'; } else if (bhnd_nv_isxdigit(c)) { if (bhnd_nv_isupper(c)) carry = (c - 'A') + 10; else carry = (c - 'a') + 10; } else { /* Hit first non-digit character */ break; } /* If carry is outside the base, it's not a valid digit * in the current parse context; consider it a non-digit * character */ if (carry >= (uint64_t)base) break; /* Increment count of parsed digits */ ndigits++; if (value > value_max) { /* -Any- carry value would overflow */ return (ERANGE); } else if (value == value_max && carry > carry_max) { /* -This- carry value would overflow */ return (ERANGE); } value *= (uint64_t)base; value += carry; } /* If we hit a non-digit character before parsing the first digit, * we hit an empty integer string. */ if (ndigits == 0) return (EFTYPE); if (negative) value = -value; /* Provide (and verify) required length */ *olen = bhnd_nvram_value_size(otype, NULL, 0, 1); if (outp == NULL) return (0); else if (limit < *olen) return (ENOMEM); /* Provide result */ switch (otype) { case BHND_NVRAM_TYPE_CHAR: case BHND_NVRAM_TYPE_UINT8: *(uint8_t *)outp = (uint8_t)value; break; case BHND_NVRAM_TYPE_UINT16: *(uint16_t *)outp = (uint16_t)value; break; case BHND_NVRAM_TYPE_UINT32: *(uint32_t *)outp = (uint32_t)value; break; case BHND_NVRAM_TYPE_UINT64: *(uint64_t *)outp = (uint64_t)value; break; case BHND_NVRAM_TYPE_INT8: *(int8_t *)outp = (int8_t)(int64_t)value; break; case BHND_NVRAM_TYPE_INT16: *(int16_t *)outp = (int16_t)(int64_t)value; break; case BHND_NVRAM_TYPE_INT32: *(int32_t *)outp = (int32_t)(int64_t)value; break; case BHND_NVRAM_TYPE_INT64: *(int64_t *)outp = (int64_t)value; break; default: /* unreachable */ BHND_NV_PANIC("unhandled type %d\n", otype); } return (0); }
/** * Return the size, in bytes, of a value of @p type with @p nelem elements. * * @param type The value type. * @param data The actual data to be queried, or NULL if unknown. If * NULL and the base type is not a fixed width type * (e.g. BHND_NVRAM_TYPE_STRING), 0 will be returned. * @param nbytes The size of @p data, in bytes, or 0 if @p data is NULL. * @param nelem The number of elements. If @p type is not an array type, * this value must be 1. * * @retval 0 If @p type has a variable width, and @p data is NULL. * @retval 0 If a @p nelem value greater than 1 is provided for a * non-array @p type. * @retval 0 If a @p nelem value of 0 is provided. * @retval 0 If the result would exceed the maximum value * representable by size_t. * @retval non-zero The size, in bytes, of @p type with @p nelem elements. */ size_t bhnd_nvram_value_size(bhnd_nvram_type type, const void *data, size_t nbytes, size_t nelem) { /* If nelem 0, nothing to do */ if (nelem == 0) return (0); /* Non-array types must have an nelem value of 1 */ if (!bhnd_nvram_is_array_type(type) && nelem != 1) return (0); switch (type) { case BHND_NVRAM_TYPE_UINT8_ARRAY: case BHND_NVRAM_TYPE_UINT16_ARRAY: case BHND_NVRAM_TYPE_UINT32_ARRAY: case BHND_NVRAM_TYPE_UINT64_ARRAY: case BHND_NVRAM_TYPE_INT8_ARRAY: case BHND_NVRAM_TYPE_INT16_ARRAY: case BHND_NVRAM_TYPE_INT32_ARRAY: case BHND_NVRAM_TYPE_INT64_ARRAY: case BHND_NVRAM_TYPE_CHAR_ARRAY: { bhnd_nvram_type base_type; size_t base_size; base_type = bhnd_nvram_base_type(type); base_size = bhnd_nvram_value_size(base_type, NULL, 0, 1); /* Would nelem * base_size overflow? */ if (SIZE_MAX / nelem < base_size) { BHND_NV_LOG("cannot represent size %s * %zu\n", bhnd_nvram_type_name(base_type), nelem); return (0); } return (nelem * base_size); } case BHND_NVRAM_TYPE_STRING_ARRAY: { const char *p; size_t total_size; if (data == NULL) return (0); /* Iterate over the NUL-terminated strings to calculate * total byte length */ p = data; total_size = 0; for (size_t i = 0; i < nelem; i++) { size_t elem_size; elem_size = strnlen(p, nbytes - total_size); p += elem_size; /* Check for (and skip) terminating NUL */ if (total_size < nbytes && *p == '\0') { elem_size++; p++; } /* Would total_size + elem_size overflow? * * A memory range larger than SIZE_MAX shouldn't be, * possible, but include the check for completeness */ if (SIZE_MAX - total_size < elem_size) return (0); total_size += elem_size; } return (total_size); } case BHND_NVRAM_TYPE_STRING: { size_t size; if (data == NULL) return (0); /* Find length */ size = strnlen(data, nbytes); /* Is there a terminating NUL, or did we just hit the * end of the string input */ if (size < nbytes) size++; return (size); } case BHND_NVRAM_TYPE_INT8: case BHND_NVRAM_TYPE_UINT8: case BHND_NVRAM_TYPE_CHAR: return (sizeof(uint8_t)); case BHND_NVRAM_TYPE_INT16: case BHND_NVRAM_TYPE_UINT16: return (sizeof(uint16_t)); case BHND_NVRAM_TYPE_INT32: case BHND_NVRAM_TYPE_UINT32: return (sizeof(uint32_t)); case BHND_NVRAM_TYPE_UINT64: case BHND_NVRAM_TYPE_INT64: return (sizeof(uint64_t)); } /* Quiesce gcc4.2 */ BHND_NV_PANIC("bhnd nvram type %u unknown", type); }
/** * 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)) {