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 */
62static 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 */
101int
102bhnd_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 */
211int
212bhnd_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