1 /*-
2  * Copyright (c) 2015-2016 Landon Fuller <landonf@FreeBSD.org>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer,
10  *    without modification.
11  * 2. Redistributions in binary form must reproduce at minimum a disclaimer
12  *    similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any
13  *    redistribution must be conditioned upon including a substantially
14  *    similar Disclaimer requirement for further binary redistribution.
15  *
16  * NO WARRANTY
17  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19  * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY
20  * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
21  * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY,
22  * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
25  * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
27  * THE POSSIBILITY OF SUCH DAMAGES.
28  */
29 
30 #include <sys/cdefs.h>
31 __FBSDID("$FreeBSD$");
32 
33 #include <sys/param.h>
34 #include <sys/limits.h>
35 #include <sys/sbuf.h>
36 
37 #ifdef _KERNEL
38 
39 #include <sys/ctype.h>
40 #include <sys/kernel.h>
41 #include <sys/malloc.h>
42 #include <sys/systm.h>
43 
44 #include <machine/_inttypes.h>
45 
46 #else /* !_KERNEL */
47 
48 #include <ctype.h>
49 #include <inttypes.h>
50 #include <errno.h>
51 #include <stdlib.h>
52 #include <string.h>
53 
54 #endif /* _KERNEL */
55 
56 #include "bhnd_nvram_private.h"
57 #include "bhnd_nvram_valuevar.h"
58 
59 #ifdef _KERNEL
60 #define	bhnd_nv_hex2ascii(hex)	hex2ascii(hex)
61 #else /* !_KERNEL */
62 static char const bhnd_nv_hex2ascii[] = "0123456789abcdefghijklmnopqrstuvwxyz";
63 #define	bhnd_nv_hex2ascii(hex)		(bhnd_nv_hex2ascii[hex])
64 #endif /* _KERNEL */
65 
66 /**
67  * Maximum size, in bytes, of a string-encoded NVRAM integer value, not
68  * including any prefix (0x, 0, etc).
69  *
70  * We assume the largest possible encoding is the base-2 representation
71  * of a 64-bit integer.
72  */
73 #define NV_NUMSTR_MAX	((sizeof(uint64_t) * CHAR_BIT) + 1)
74 
75 /**
76  * Format a string representation of @p value using @p fmt, with, writing the
77  * result to @p outp.
78  *
79  * @param		value	The value to be formatted.
80  * @param		fmt	The format string.
81  * @param[out]		outp	On success, the string will be written to this
82  *				buffer. This argment may be NULL if the value is
83  *				not desired.
84  * @param[in,out]	olen	The capacity of @p outp. On success, will be set
85  *				to the actual number of bytes required for the
86  *				requested string encoding (including a trailing
87  *				NUL).
88  *
89  * Refer to bhnd_nvram_val_vprintf() for full format string documentation.
90  *
91  * @retval 0		success
92  * @retval EINVAL	If @p fmt contains unrecognized format string
93  *			specifiers.
94  * @retval ENOMEM	If the @p outp is non-NULL, and the provided @p olen
95  *			is too small to hold the encoded value.
96  * @retval EFTYPE	If value coercion from @p value to a single string
97  *			value via @p fmt is unsupported.
98  * @retval ERANGE	If value coercion of @p value would overflow (or
99  *			underflow) the representation defined by @p fmt.
100  */
101 int
102 bhnd_nvram_val_printf(bhnd_nvram_val *value, const char *fmt, char *outp,
103     size_t *olen, ...)
104 {
105 	va_list	ap;
106 	int	error;
107 
108 	va_start(ap, olen);
109 	error = bhnd_nvram_val_vprintf(value, fmt, outp, olen, ap);
110 	va_end(ap);
111 
112 	return (error);
113 }
114 
115 /**
116  * Format a string representation of the elements of @p value using @p fmt,
117  * writing the result to @p outp.
118  *
119  * @param		value	The value to be formatted.
120  * @param		fmt	The format string.
121  * @param[out]		outp	On success, the string will be written to this
122  *				buffer. This argment may be NULL if the value is
123  *				not desired.
124  * @param[in,out]	olen	The capacity of @p outp. On success, will be set
125  *				to the actual number of bytes required for the
126  *				requested string encoding (including a trailing
127  *				NUL).
128  * @param		ap	Argument list.
129  *
130  * @par Format Strings
131  *
132  * Value format strings are similar, but not identical to, those used
133  * by printf(3).
134  *
135  * Format specifier format:
136  *     %[repeat][flags][width][.precision][length modifier][specifier]
137  *
138  * The format specifier is interpreted as an encoding directive for an
139  * individual value element; each format specifier will fetch the next element
140  * from the value, encode the element as the appropriate type based on the
141  * length modifiers and specifier, and then format the result as a string.
142  *
143  * For example, given a string value of '0x000F', and a format specifier of
144  * '%#hhx', the value will be asked to encode its first element as
145  * BHND_NVRAM_TYPE_UINT8. String formatting will then be applied to the 8-bit
146  * unsigned integer representation, producing a string value of "0xF".
147  *
148  * Repeat:
149  * - [digits]		Repeatedly apply the format specifier to the input
150  *			value's elements up to `digits` times. The delimiter
151  *			must be passed as a string in the next variadic
152  *			argument.
153  * - []			Repeatedly apply the format specifier to the input
154  *			value's elements until all elements have been. The
155  *			processed. The delimiter must be passed as a string in
156  *			the next variadic argument.
157  * - [*]		Repeatedly apply the format specifier to the input
158  *			value's elements. The repeat count is read from the
159  *			next variadic argument as a size_t value
160  *
161  * Flags:
162  * - '#'		use alternative form (e.g. 0x/0X prefixing of hex
163  *			strings).
164  * - '0'		zero padding
165  * - '-'		left adjust padding
166  * - '+'		include a sign character
167  * - ' '		include a space in place of a sign character for
168  *			positive numbers.
169  *
170  * Width/Precision:
171  * - digits		minimum field width.
172  * - *			read the minimum field width from the next variadic
173  *			argument as a ssize_t value. A negative value enables
174  *			left adjustment.
175  * - .digits		field precision.
176  * - .*			read the field precision from the next variadic argument
177  *			as a ssize_t value. A negative value enables left
178  *			adjustment.
179  *
180  * Length Modifiers:
181  * - 'hh', 'I8'		Convert the value to an 8-bit signed or unsigned
182  *			integer.
183  * - 'h', 'I16'		Convert the value to an 16-bit signed or unsigned
184  *			integer.
185  * - 'l', 'I32'		Convert the value to an 32-bit signed or unsigned
186  *			integer.
187  * - 'll', 'j', 'I64'	Convert the value to an 64-bit signed or unsigned
188  *			integer.
189  *
190  * Data Specifiers:
191  * - 'd', 'i'		Convert and format as a signed decimal integer.
192  * - 'u'		Convert and format as an unsigned decimal integer.
193  * - 'o'		Convert and format as an unsigned octal integer.
194  * - 'x'		Convert and format as an unsigned hexadecimal integer,
195  *			using lowercase hex digits.
196  * - 'X'		Convert and format as an unsigned hexadecimal integer,
197  *			using uppercase hex digits.
198  * - 's'		Convert and format as a string.
199  * - '%'		Print a literal '%' character.
200  *
201  * @retval 0		success
202  * @retval EINVAL	If @p fmt contains unrecognized format string
203  *			specifiers.
204  * @retval ENOMEM	If the @p outp is non-NULL, and the provided @p olen
205  *			is too small to hold the encoded value.
206  * @retval EFTYPE	If value coercion from @p value to a single string
207  *			value via @p fmt is unsupported.
208  * @retval ERANGE	If value coercion of @p value would overflow (or
209  *			underflow) the representation defined by @p fmt.
210  */
211 int
212 bhnd_nvram_val_vprintf(bhnd_nvram_val *value, const char *fmt, char *outp,
213     size_t *olen, va_list ap)
214 {
215 	const void	*elem;
216 	size_t		 elen;
217 	size_t		 limit, nbytes;
218 	int		 error;
219 
220 	elem = NULL;
221 
222 	/* Determine output byte limit */
223 	nbytes = 0;
224 	if (outp != NULL)
225 		limit = *olen;
226 	else
227 		limit = 0;
228 
229 #define	WRITE_CHAR(_c)	do {			\
230 	if (limit > nbytes)			\
231 		*(outp + nbytes) = _c;		\
232 						\
233 	if (nbytes == SIZE_MAX)			\
234 		return (EFTYPE);		\
235 	nbytes++;				\
236 } while (0)
237 
238 	/* Encode string value as per the format string */
239 	for (const char *p = fmt; *p != '\0'; p++) {
240 		const char	*delim;
241 		size_t		 precision, width, delim_len;
242 		u_long		 repeat, bits;
243 		bool		 alt_form, ladjust, have_precision;
244 		char		 padc, signc, lenc;
245 
246 		padc = ' ';
247 		signc = '\0';
248 		lenc = '\0';
249 		delim = "";
250 		delim_len = 0;
251 
252 		ladjust = false;
253 		alt_form = false;
254 
255 		have_precision = false;
256 		precision = 1;
257 		bits = 32;
258 		width = 0;
259 		repeat = 1;
260 
261 		/* Copy all input to output until we hit a format specifier */
262 		if (*p != '%') {
263 			WRITE_CHAR(*p);
264 			continue;
265 		}
266 
267 		/* Hit '%' -- is this followed by an escaped '%' literal? */
268 		p++;
269 		if (*p == '%') {
270 			WRITE_CHAR('%');
271 			p++;
272 			continue;
273 		}
274 
275 		/* Parse repeat specifier */
276 		if (*p == '[') {
277 			p++;
278 
279 			/* Determine repeat count */
280 			if (*p == ']') {
281 				/* Repeat consumes all input */
282 				repeat = bhnd_nvram_val_nelem(value);
283 			} else if (*p == '*') {
284 				/* Repeat is supplied as an argument */
285 				repeat = va_arg(ap, size_t);
286 				p++;
287 			} else {
288 				char *endp;
289 
290 				/* Repeat specified as argument */
291 				repeat = strtoul(p, &endp, 10);
292 				if (p == endp) {
293 					BHND_NV_LOG("error parsing repeat "
294 						    "count at '%s'", p);
295 					return (EINVAL);
296 				}
297 
298 				/* Advance past repeat count */
299 				p = endp;
300 			}
301 
302 			/* Advance past terminating ']' */
303 			if (*p != ']') {
304 				BHND_NV_LOG("error parsing repeat count at "
305 				    "'%s'", p);
306 				return (EINVAL);
307 			}
308 			p++;
309 
310 			delim = va_arg(ap, const char *);
311 			delim_len = strlen(delim);
312 		}
313 
314 		/* Parse flags */
315 		while (*p != '\0') {
316 			const char	*np;
317 			bool		 stop;
318 
319 			stop = false;
320 			np = p+1;
321 
322 			switch (*p) {
323 			case '#':
324 				alt_form = true;
325 				break;
326 			case '0':
327 				padc = '0';
328 				break;
329 			case '-':
330 				ladjust = true;
331 				break;
332 			case ' ':
333 				/* Must not override '+' */
334 				if (signc != '+')
335 					signc = ' ';
336 				break;
337 			case '+':
338 				signc = '+';
339 				break;
340 			default:
341 				/* Non-flag character */
342 				stop = true;
343 				break;
344 			}
345 
346 			if (stop)
347 				break;
348 			else
349 				p = np;
350 		}
351 
352 		/* Parse minimum width */
353 		if (*p == '*') {
354 			ssize_t arg;
355 
356 			/* Width is supplied as an argument */
357 			arg = va_arg(ap, int);
358 
359 			/* Negative width argument is interpreted as
360 			 * '-' flag followed by positive width */
361 			if (arg < 0) {
362 				ladjust = true;
363 				arg = -arg;
364 			}
365 
366 			width = arg;
367 			p++;
368 		} else if (bhnd_nv_isdigit(*p)) {
369 			uint32_t	v;
370 			size_t		len, parsed;
371 
372 			/* Parse width value */
373 			len = sizeof(v);
374 			error = bhnd_nvram_parse_int(p, strlen(p), 10, &parsed,
375 			    &v, &len, BHND_NVRAM_TYPE_UINT32);
376 			if (error) {
377 				BHND_NV_LOG("error parsing width %s: %d\n", p,
378 				    error);
379 				return (EINVAL);
380 			}
381 
382 			/* Save width and advance input */
383 			width = v;
384 			p += parsed;
385 		}
386 
387 		/* Parse precision */
388 		if (*p == '.') {
389 			uint32_t	v;
390 			size_t		len, parsed;
391 
392 			p++;
393 			have_precision = true;
394 
395 			if (*p == '*') {
396 				ssize_t arg;
397 
398 				/* Precision is specified as an argument */
399 				arg = va_arg(ap, int);
400 
401 				/* Negative precision argument is interpreted
402 				 * as '-' flag followed by positive
403 				 * precision */
404 				if (arg < 0) {
405 					ladjust = true;
406 					arg = -arg;
407 				}
408 
409 				precision = arg;
410 			} else if (!bhnd_nv_isdigit(*p)) {
411 				/* Implicit precision of 0 */
412 				precision = 0;
413 			} else {
414 				/* Parse precision value */
415 				len = sizeof(v);
416 				error = bhnd_nvram_parse_int(p, strlen(p), 10,
417 				    &parsed, &v, &len,
418 				    BHND_NVRAM_TYPE_UINT32);
419 				if (error) {
420 					BHND_NV_LOG("error parsing width %s: "
421 					    "%d\n", p, error);
422 					return (EINVAL);
423 				}
424 
425 				/* Save precision and advance input */
426 				precision = v;
427 				p += parsed;
428 			}
429 		}
430 
431 		/* Parse length modifiers */
432 		while (*p != '\0') {
433 			const char	*np;
434 			bool		 stop;
435 
436 			stop = false;
437 			np = p+1;
438 
439 			switch (*p) {
440 			case 'h':
441 				if (lenc == '\0') {
442 					/* Set initial length value */
443 					lenc = *p;
444 					bits = 16;
445 				} else if (lenc == *p && bits == 16) {
446 					/* Modify previous length value */
447 					bits = 8;
448 				} else {
449 					BHND_NV_LOG("invalid length modifier "
450 					    "%c\n", *p);
451 					return (EINVAL);
452 				}
453 				break;
454 
455 			case 'l':
456 				if (lenc == '\0') {
457 					/* Set initial length value */
458 					lenc = *p;
459 					bits = 32;
460 				} else if (lenc == *p && bits == 32) {
461 					/* Modify previous length value */
462 					bits = 64;
463 				} else {
464 					BHND_NV_LOG("invalid length modifier "
465 					    "%c\n", *p);
466 					return (EINVAL);
467 				}
468 				break;
469 
470 			case 'j':
471 				/* Conflicts with all other length
472 				 * specifications, and may only occur once */
473 				if (lenc != '\0') {
474 					BHND_NV_LOG("invalid length modifier "
475 					    "%c\n", *p);
476 					return (EINVAL);
477 				}
478 
479 				lenc = *p;
480 				bits = 64;
481 				break;
482 
483 			case 'I': {
484 				char	*endp;
485 
486 				/* Conflicts with all other length
487 				 * specifications, and may only occur once */
488 				if (lenc != '\0') {
489 					BHND_NV_LOG("invalid length modifier "
490 					    "%c\n", *p);
491 					return (EINVAL);
492 				}
493 
494 				lenc = *p;
495 
496 				/* Parse the length specifier value */
497 				p++;
498 				bits = strtoul(p, &endp, 10);
499 				if (p == endp) {
500 					BHND_NV_LOG("invalid size specifier: "
501 					    "%s\n", p);
502 					return (EINVAL);
503 				}
504 
505 				/* Advance input past the parsed integer */
506 				np = endp;
507 				break;
508 			}
509 			default:
510 				/* Non-length modifier character */
511 				stop = true;
512 				break;
513 			}
514 
515 			if (stop)
516 				break;
517 			else
518 				p = np;
519 		}
520 
521 		/* Parse conversion specifier and format the value(s) */
522 		for (u_long n = 0; n < repeat; n++) {
523 			bhnd_nvram_type	arg_type;
524 			size_t		arg_size;
525 			size_t		i;
526 			u_long		base;
527 			bool		is_signed, is_upper;
528 
529 			is_signed = false;
530 			is_upper = false;
531 			base = 0;
532 
533 			/* Fetch next element */
534 			elem = bhnd_nvram_val_next(value, elem, &elen);
535 			if (elem == NULL) {
536 				BHND_NV_LOG("format string references more "
537 				    "than %zu available value elements\n",
538 				    bhnd_nvram_val_nelem(value));
539 				return (EINVAL);
540 			}
541 
542 			/*
543 			 * If this is not the first value, append the delimiter.
544 			 */
545 			if (n > 0) {
546 				size_t nremain = 0;
547 				if (limit > nbytes)
548 					nremain = limit - nbytes;
549 
550 				if (nremain >= delim_len)
551 					memcpy(outp + nbytes, delim, delim_len);
552 
553 				/* Add delimiter length to the total byte count */
554 				if (SIZE_MAX - nbytes < delim_len)
555 					return (EFTYPE); /* overflows size_t */
556 
557 				nbytes += delim_len;
558 			}
559 
560 			/* Parse integer conversion specifiers */
561 			switch (*p) {
562 			case 'd':
563 			case 'i':
564 				base = 10;
565 				is_signed = true;
566 				break;
567 
568 			case 'u':
569 				base = 10;
570 				break;
571 
572 			case 'o':
573 				base = 8;
574 				break;
575 
576 			case 'x':
577 				base = 16;
578 				break;
579 
580 			case 'X':
581 				base = 16;
582 				is_upper = true;
583 				break;
584 			}
585 
586 			/* Format argument */
587 			switch (*p) {
588 #define	NV_ENCODE_INT(_width) do { 					\
589 	arg_type = (is_signed) ? BHND_NVRAM_TYPE_INT ## _width :	\
590 	    BHND_NVRAM_TYPE_UINT ## _width;				\
591 	arg_size = sizeof(v.u ## _width);				\
592 	error = bhnd_nvram_val_encode_elem(value, elem, elen,		\
593 	    &v.u ## _width, &arg_size, arg_type);			\
594 	if (error) {							\
595 		BHND_NV_LOG("error encoding argument as %s: %d\n",	\
596 		     bhnd_nvram_type_name(arg_type), error);		\
597 		return (error);						\
598 	}								\
599 									\
600 	if (is_signed) {						\
601 		if (v.i ## _width < 0) {				\
602 			add_neg = true;					\
603 			numval = (int64_t)-(v.i ## _width);		\
604 		} else {						\
605 			numval = (int64_t) (v.i ## _width);		\
606 		}							\
607 	} else {							\
608 		numval = v.u ## _width;					\
609 	}								\
610 } while(0)
611 			case 'd':
612 			case 'i':
613 			case 'u':
614 			case 'o':
615 			case 'x':
616 			case 'X': {
617 				char		 numbuf[NV_NUMSTR_MAX];
618 				char		*sptr;
619 				uint64_t	 numval;
620 				size_t		 slen;
621 				bool		 add_neg;
622 				union {
623 					uint8_t		u8;
624 					uint16_t	u16;
625 					uint32_t	u32;
626 					uint64_t	u64;
627 					int8_t		i8;
628 					int16_t		i16;
629 					int32_t		i32;
630 					int64_t		i64;
631 				} v;
632 
633 				add_neg = false;
634 
635 				/* If precision is specified, it overrides
636 				 * (and behaves identically) to a zero-prefixed
637 				 * minimum width */
638 				if (have_precision) {
639 					padc = '0';
640 					width = precision;
641 					ladjust = false;
642 				}
643 
644 				/* If zero-padding is used, value must be right
645 				 * adjusted */
646 				if (padc == '0')
647 					ladjust = false;
648 
649 				/* Request encode to the appropriate integer
650 				 * type, and then promote to common 64-bit
651 				 * representation */
652 				switch (bits) {
653 				case 8:
654 					NV_ENCODE_INT(8);
655 					break;
656 				case 16:
657 					NV_ENCODE_INT(16);
658 					break;
659 				case 32:
660 					NV_ENCODE_INT(32);
661 					break;
662 				case 64:
663 					NV_ENCODE_INT(64);
664 					break;
665 				default:
666 					BHND_NV_LOG("invalid length specifier: "
667 					    "%lu\n", bits);
668 					return (EINVAL);
669 				}
670 #undef	NV_ENCODE_INT
671 
672 				/* If a precision of 0 is specified and the
673 				 * value is also zero, no characters should
674 				 * be produced */
675 				if (have_precision && precision == 0 &&
676 				    numval == 0)
677 				{
678 					break;
679 				}
680 
681 				/* Emit string representation to local buffer */
682 				BHND_NV_ASSERT(base <= 16, ("invalid base"));
683 				sptr = numbuf + nitems(numbuf) - 1;
684 				for (slen = 0; slen < sizeof(numbuf); slen++) {
685 					char		c;
686 					uint64_t	n;
687 
688 					n = numval % base;
689 					c = bhnd_nv_hex2ascii(n);
690 					if (is_upper)
691 						c = bhnd_nv_toupper(c);
692 
693 					sptr--;
694 					*sptr = c;
695 
696 					numval /= (uint64_t)base;
697 					if (numval == 0) {
698 						slen++;
699 						break;
700 					}
701 				}
702 
703 				arg_size = slen;
704 
705 				/* Reserve space for 0/0x prefix? */
706 				if (alt_form) {
707 					if (numval == 0) {
708 						/* If 0, no prefix */
709 						alt_form = false;
710 					} else if (base == 8) {
711 						arg_size += 1; /* 0 */
712 					} else if (base == 16) {
713 						arg_size += 2; /* 0x/0X */
714 					}
715 				}
716 
717 				/* Reserve space for ' ', '+', or '-' prefix? */
718 				if (add_neg || signc != '\0') {
719 					if (add_neg)
720 						signc = '-';
721 
722 					arg_size++;
723 				}
724 
725 				/* Right adjust (if using spaces) */
726 				if (!ladjust && padc != '0') {
727 					for (i = arg_size;  i < width; i++)
728 						WRITE_CHAR(padc);
729 				}
730 
731 				if (signc != '\0')
732 					WRITE_CHAR(signc);
733 
734 				if (alt_form) {
735 					if (base == 8) {
736 						WRITE_CHAR('0');
737 					} else if (base == 16) {
738 						WRITE_CHAR('0');
739 						if (is_upper)
740 							WRITE_CHAR('X');
741 						else
742 							WRITE_CHAR('x');
743 					}
744 				}
745 
746 				/* Right adjust (if using zeros) */
747 				if (!ladjust && padc == '0') {
748 					for (i = slen;  i < width; i++)
749 						WRITE_CHAR(padc);
750 				}
751 
752 				/* Write the string to our output buffer */
753 				if (limit > nbytes && limit - nbytes >= slen)
754 					memcpy(outp + nbytes, sptr, slen);
755 
756 				/* Update the total byte count */
757 				if (SIZE_MAX - nbytes < arg_size)
758 					return (EFTYPE); /* overflows size_t */
759 
760 				nbytes += arg_size;
761 
762 				/* Left adjust */
763 				for (i = arg_size; ladjust && i < width; i++)
764 					WRITE_CHAR(padc);
765 
766 				break;
767 			}
768 
769 			case 's': {
770 				char	*s;
771 				size_t	 slen;
772 
773 				/* Query the total length of the element when
774 				 * converted to a string */
775 				arg_type = BHND_NVRAM_TYPE_STRING;
776 				error = bhnd_nvram_val_encode_elem(value, elem,
777 				    elen, NULL, &arg_size, arg_type);
778 				if (error) {
779 					BHND_NV_LOG("error encoding argument "
780 					    "as %s: %d\n",
781 					    bhnd_nvram_type_name(arg_type),
782 					    error);
783 					return (error);
784 				}
785 
786 				/* Do not include trailing NUL in the string
787 				 * length */
788 				if (arg_size > 0)
789 					arg_size--;
790 
791 				/* Right adjust */
792 				for (i = arg_size; !ladjust && i < width; i++)
793 					WRITE_CHAR(padc);
794 
795 				/* Determine output positition and remaining
796 				 * buffer space */
797 				if (limit > nbytes) {
798 					s = outp + nbytes;
799 					slen = limit - nbytes;
800 				} else {
801 					s = NULL;
802 					slen = 0;
803 				}
804 
805 				/* Encode the string to our output buffer */
806 				error = bhnd_nvram_val_encode_elem(value, elem,
807 				    elen, s, &slen, arg_type);
808 				if (error && error != ENOMEM) {
809 					BHND_NV_LOG("error encoding argument "
810 					    "as %s: %d\n",
811 					    bhnd_nvram_type_name(arg_type),
812 					    error);
813 					return (error);
814 				}
815 
816 				/* Update the total byte count */
817 				if (SIZE_MAX - nbytes < arg_size)
818 					return (EFTYPE); /* overflows size_t */
819 
820 				nbytes += arg_size;
821 
822 				/* Left adjust */
823 				for (i = arg_size; ladjust && i < width; i++)
824 					WRITE_CHAR(padc);
825 
826 				break;
827 			}
828 
829 			case 'c': {
830 				char c;
831 
832 				arg_type = BHND_NVRAM_TYPE_CHAR;
833 				arg_size = bhnd_nvram_type_width(arg_type);
834 
835 				/* Encode as single character */
836 				error = bhnd_nvram_val_encode_elem(value, elem,
837 				    elen, &c, &arg_size, arg_type);
838 				if (error) {
839 					BHND_NV_LOG("error encoding argument "
840 					    "as %s: %d\n",
841 					    bhnd_nvram_type_name(arg_type),
842 					    error);
843 					return (error);
844 				}
845 
846 				BHND_NV_ASSERT(arg_size == sizeof(c),
847 				    ("invalid encoded size"));
848 
849 				/* Right adjust */
850 				for (i = arg_size; !ladjust && i < width; i++)
851 					WRITE_CHAR(padc);
852 
853 				WRITE_CHAR(padc);
854 
855 				/* Left adjust */
856 				for (i = arg_size; ladjust && i < width; i++)
857 					WRITE_CHAR(padc);
858 
859 				break;
860 			}
861 			}
862 		}
863 	}
864 
865 	/* Append terminating NUL */
866 	if (limit > nbytes)
867 		*(outp + nbytes) = '\0';
868 
869 	if (nbytes < SIZE_MAX)
870 		nbytes++;
871 	else
872 		return (EFTYPE);
873 
874 	/* Report required space */
875 	*olen = nbytes;
876 	if (limit < nbytes) {
877 		if (outp != NULL)
878 			return (ENOMEM);
879 	}
880 
881 	return (0);
882 }
883