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