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