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