root/sys/dev/bhnd/nvram/bhnd_nvram_value_prf.c
/*-
 * Copyright (c) 2015-2016 Landon Fuller <landonf@FreeBSD.org>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer,
 *    without modification.
 * 2. Redistributions in binary form must reproduce at minimum a disclaimer
 *    similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any
 *    redistribution must be conditioned upon including a substantially
 *    similar Disclaimer requirement for further binary redistribution.
 *
 * NO WARRANTY
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY,
 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGES.
 */

#include <sys/param.h>
#include <sys/limits.h>
#include <sys/sbuf.h>

#ifdef _KERNEL

#include <sys/ctype.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/systm.h>

#include <machine/_inttypes.h>

#else /* !_KERNEL */

#include <ctype.h>
#include <inttypes.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>

#endif /* _KERNEL */

#include "bhnd_nvram_private.h"
#include "bhnd_nvram_valuevar.h"

#ifdef _KERNEL
#define bhnd_nv_hex2ascii(hex)  hex2ascii(hex)
#else /* !_KERNEL */
static char const bhnd_nv_hex2ascii[] = "0123456789abcdefghijklmnopqrstuvwxyz";
#define bhnd_nv_hex2ascii(hex)          (bhnd_nv_hex2ascii[hex])
#endif /* _KERNEL */

/**
 * Maximum size, in bytes, of a string-encoded NVRAM integer value, not
 * including any prefix (0x, 0, etc).
 * 
 * We assume the largest possible encoding is the base-2 representation
 * of a 64-bit integer.
 */
#define NV_NUMSTR_MAX   ((sizeof(uint64_t) * CHAR_BIT) + 1)

/**
 * Format a string representation of @p value using @p fmt, with, writing the
 * result to @p outp.
 *
 * @param               value   The value to be formatted.
 * @param               fmt     The format string.
 * @param[out]          outp    On success, the string will be written to this 
 *                              buffer. This argment may be NULL if the value is
 *                              not desired.
 * @param[in,out]       olen    The capacity of @p outp. On success, will be set
 *                              to the actual number of bytes required for the
 *                              requested string encoding (including a trailing
 *                              NUL).
 * 
 * Refer to bhnd_nvram_val_vprintf() for full format string documentation.
 *
 * @retval 0            success
 * @retval EINVAL       If @p fmt contains unrecognized format string
 *                      specifiers.
 * @retval ENOMEM       If the @p outp is non-NULL, and the provided @p olen
 *                      is too small to hold the encoded value.
 * @retval EFTYPE       If value coercion from @p value to a single string
 *                      value via @p fmt is unsupported.
 * @retval ERANGE       If value coercion of @p value would overflow (or
 *                      underflow) the representation defined by @p fmt.
 */
int
bhnd_nvram_val_printf(bhnd_nvram_val *value, const char *fmt, char *outp,
    size_t *olen, ...)
{
        va_list ap;
        int     error;

        va_start(ap, olen);
        error = bhnd_nvram_val_vprintf(value, fmt, outp, olen, ap);
        va_end(ap);

        return (error);
}

/**
 * Format a string representation of the elements of @p value using @p fmt,
 * writing the result to @p outp.
 *
 * @param               value   The value to be formatted.
 * @param               fmt     The format string.
 * @param[out]          outp    On success, the string will be written to this 
 *                              buffer. This argment may be NULL if the value is
 *                              not desired.
 * @param[in,out]       olen    The capacity of @p outp. On success, will be set
 *                              to the actual number of bytes required for the
 *                              requested string encoding (including a trailing
 *                              NUL).
 * @param               ap      Argument list.
 *
 * @par Format Strings
 * 
 * Value format strings are similar, but not identical to, those used
 * by printf(3).
 * 
 * Format specifier format:
 *     %[repeat][flags][width][.precision][length modifier][specifier]
 *
 * The format specifier is interpreted as an encoding directive for an
 * individual value element; each format specifier will fetch the next element
 * from the value, encode the element as the appropriate type based on the
 * length modifiers and specifier, and then format the result as a string.
 * 
 * For example, given a string value of '0x000F', and a format specifier of
 * '%#hhx', the value will be asked to encode its first element as
 * BHND_NVRAM_TYPE_UINT8. String formatting will then be applied to the 8-bit
 * unsigned integer representation, producing a string value of "0xF".
 * 
 * Repeat:
 * - [digits]           Repeatedly apply the format specifier to the input
 *                      value's elements up to `digits` times. The delimiter
 *                      must be passed as a string in the next variadic
 *                      argument.
 * - []                 Repeatedly apply the format specifier to the input
 *                      value's elements until all elements have been. The
 *                      processed. The delimiter must be passed as a string in
 *                      the next variadic argument.
 * - [*]                Repeatedly apply the format specifier to the input
 *                      value's elements. The repeat count is read from the
 *                      next variadic argument as a size_t value
 * 
 * Flags:
 * - '#'                use alternative form (e.g. 0x/0X prefixing of hex
 *                      strings).
 * - '0'                zero padding
 * - '-'                left adjust padding
 * - '+'                include a sign character
 * - ' '                include a space in place of a sign character for
 *                      positive numbers.
 * 
 * Width/Precision:
 * - digits             minimum field width.
 * - *                  read the minimum field width from the next variadic
 *                      argument as a ssize_t value. A negative value enables
 *                      left adjustment.
 * - .digits            field precision.
 * - .*                 read the field precision from the next variadic argument
 *                      as a ssize_t value. A negative value enables left
 *                      adjustment.
 *
 * Length Modifiers:
 * - 'hh', 'I8'         Convert the value to an 8-bit signed or unsigned
 *                      integer.
 * - 'h', 'I16'         Convert the value to an 16-bit signed or unsigned
 *                      integer.
 * - 'l', 'I32'         Convert the value to an 32-bit signed or unsigned
 *                      integer.
 * - 'll', 'j', 'I64'   Convert the value to an 64-bit signed or unsigned
 *                      integer.
 * 
 * Data Specifiers:
 * - 'd', 'i'           Convert and format as a signed decimal integer.
 * - 'u'                Convert and format as an unsigned decimal integer.
 * - 'o'                Convert and format as an unsigned octal integer.
 * - 'x'                Convert and format as an unsigned hexadecimal integer,
 *                      using lowercase hex digits.
 * - 'X'                Convert and format as an unsigned hexadecimal integer,
 *                      using uppercase hex digits.
 * - 's'                Convert and format as a string.
 * - '%'                Print a literal '%' character.
 *
 * @retval 0            success
 * @retval EINVAL       If @p fmt contains unrecognized format string
 *                      specifiers.
 * @retval ENOMEM       If the @p outp is non-NULL, and the provided @p olen
 *                      is too small to hold the encoded value.
 * @retval EFTYPE       If value coercion from @p value to a single string
 *                      value via @p fmt is unsupported.
 * @retval ERANGE       If value coercion of @p value would overflow (or
 *                      underflow) the representation defined by @p fmt.
 */
int
bhnd_nvram_val_vprintf(bhnd_nvram_val *value, const char *fmt, char *outp,
    size_t *olen, va_list ap)
{
        const void      *elem;
        size_t           elen;
        size_t           limit, nbytes;
        int              error;

        elem = NULL;

        /* Determine output byte limit */
        nbytes = 0;
        if (outp != NULL)
                limit = *olen;
        else
                limit = 0;

#define WRITE_CHAR(_c)  do {                    \
        if (limit > nbytes)                     \
                *(outp + nbytes) = _c;          \
                                                \
        if (nbytes == SIZE_MAX)                 \
                return (EFTYPE);                \
        nbytes++;                               \
} while (0)

        /* Encode string value as per the format string */
        for (const char *p = fmt; *p != '\0'; p++) {
                const char      *delim;
                size_t           precision, width, delim_len;
                u_long           repeat, bits;
                bool             alt_form, ladjust, have_precision;
                char             padc, signc, lenc;

                padc = ' ';
                signc = '\0';
                lenc = '\0';
                delim = "";
                delim_len = 0;

                ladjust = false;
                alt_form = false;

                have_precision = false;
                precision = 1;
                bits = 32;
                width = 0;
                repeat = 1;

                /* Copy all input to output until we hit a format specifier */
                if (*p != '%') {
                        WRITE_CHAR(*p);
                        continue;
                }

                /* Hit '%' -- is this followed by an escaped '%' literal? */
                p++;
                if (*p == '%') {
                        WRITE_CHAR('%');
                        p++;
                        continue;
                }

                /* Parse repeat specifier */
                if (*p == '[') {
                        p++;
                        
                        /* Determine repeat count */
                        if (*p == ']') {
                                /* Repeat consumes all input */
                                repeat = bhnd_nvram_val_nelem(value);
                        } else if (*p == '*') {
                                /* Repeat is supplied as an argument */
                                repeat = va_arg(ap, size_t);
                                p++;
                        } else {
                                char *endp;

                                /* Repeat specified as argument */
                                repeat = strtoul(p, &endp, 10);
                                if (p == endp) {
                                        BHND_NV_LOG("error parsing repeat "
                                                    "count at '%s'", p);
                                        return (EINVAL);
                                }
                                
                                /* Advance past repeat count */
                                p = endp;
                        }

                        /* Advance past terminating ']' */
                        if (*p != ']') {
                                BHND_NV_LOG("error parsing repeat count at "
                                    "'%s'", p);
                                return (EINVAL);
                        }
                        p++;

                        delim = va_arg(ap, const char *);
                        delim_len = strlen(delim);
                }

                /* Parse flags */
                while (*p != '\0') {
                        const char      *np;
                        bool             stop;

                        stop = false;
                        np = p+1;

                        switch (*p) {
                        case '#':
                                alt_form = true;
                                break;
                        case '0':
                                padc = '0';
                                break;
                        case '-':
                                ladjust = true;
                                break;
                        case ' ':
                                /* Must not override '+' */
                                if (signc != '+')
                                        signc = ' ';
                                break;
                        case '+':
                                signc = '+';
                                break;
                        default:
                                /* Non-flag character */
                                stop = true;
                                break;
                        }

                        if (stop)
                                break;
                        else
                                p = np;
                }

                /* Parse minimum width */
                if (*p == '*') {
                        ssize_t arg;

                        /* Width is supplied as an argument */
                        arg = va_arg(ap, int);

                        /* Negative width argument is interpreted as
                         * '-' flag followed by positive width */
                        if (arg < 0) {
                                ladjust = true;
                                arg = -arg;
                        }

                        width = arg;
                        p++;
                } else if (bhnd_nv_isdigit(*p)) {
                        uint32_t        v;
                        size_t          len, parsed;

                        /* Parse width value */
                        len = sizeof(v);
                        error = bhnd_nvram_parse_int(p, strlen(p), 10, &parsed,
                            &v, &len, BHND_NVRAM_TYPE_UINT32);
                        if (error) {
                                BHND_NV_LOG("error parsing width %s: %d\n", p,
                                    error);
                                return (EINVAL);
                        }

                        /* Save width and advance input */
                        width = v;
                        p += parsed;
                }

                /* Parse precision */
                if (*p == '.') {
                        uint32_t        v;
                        size_t          len, parsed;

                        p++;
                        have_precision = true;

                        if (*p == '*') {
                                ssize_t arg;

                                /* Precision is specified as an argument */
                                arg = va_arg(ap, int);

                                /* Negative precision argument is interpreted
                                 * as '-' flag followed by positive
                                 * precision */
                                if (arg < 0) {
                                        ladjust = true;
                                        arg = -arg;
                                }

                                precision = arg;
                        } else if (!bhnd_nv_isdigit(*p)) {
                                /* Implicit precision of 0 */
                                precision = 0;
                        } else {
                                /* Parse precision value */
                                len = sizeof(v);
                                error = bhnd_nvram_parse_int(p, strlen(p), 10,
                                    &parsed, &v, &len,
                                    BHND_NVRAM_TYPE_UINT32);
                                if (error) {
                                        BHND_NV_LOG("error parsing width %s: "
                                            "%d\n", p, error);
                                        return (EINVAL);
                                }

                                /* Save precision and advance input */
                                precision = v;
                                p += parsed;
                        }
                }

                /* Parse length modifiers */
                while (*p != '\0') {
                        const char      *np;
                        bool             stop;
                        
                        stop = false;
                        np = p+1;

                        switch (*p) {
                        case 'h':
                                if (lenc == '\0') {
                                        /* Set initial length value */
                                        lenc = *p;
                                        bits = 16;
                                } else if (lenc == *p && bits == 16) {
                                        /* Modify previous length value */
                                        bits = 8;
                                } else {
                                        BHND_NV_LOG("invalid length modifier "
                                            "%c\n", *p);
                                        return (EINVAL);
                                }
                                break;

                        case 'l':
                                if (lenc == '\0') {
                                        /* Set initial length value */
                                        lenc = *p;
                                        bits = 32;
                                } else if (lenc == *p && bits == 32) {
                                        /* Modify previous length value */
                                        bits = 64;
                                } else {
                                        BHND_NV_LOG("invalid length modifier "
                                            "%c\n", *p);
                                        return (EINVAL);
                                }
                                break;

                        case 'j':
                                /* Conflicts with all other length
                                 * specifications, and may only occur once */
                                if (lenc != '\0') {
                                        BHND_NV_LOG("invalid length modifier "
                                            "%c\n", *p);
                                        return (EINVAL);
                                }

                                lenc = *p;
                                bits = 64;
                                break;

                        case 'I': {
                                char    *endp;

                                /* Conflicts with all other length
                                 * specifications, and may only occur once */
                                if (lenc != '\0') {
                                        BHND_NV_LOG("invalid length modifier "
                                            "%c\n", *p);
                                        return (EINVAL);
                                }

                                lenc = *p;

                                /* Parse the length specifier value */
                                p++;
                                bits = strtoul(p, &endp, 10);
                                if (p == endp) {
                                        BHND_NV_LOG("invalid size specifier: "
                                            "%s\n", p);
                                        return (EINVAL);
                                }

                                /* Advance input past the parsed integer */
                                np = endp;
                                break;
                        }
                        default:
                                /* Non-length modifier character */
                                stop = true;
                                break;
                        }

                        if (stop)
                                break;
                        else
                                p = np;
                }

                /* Parse conversion specifier and format the value(s) */
                for (u_long n = 0; n < repeat; n++) {
                        bhnd_nvram_type arg_type;
                        size_t          arg_size;
                        size_t          i;
                        u_long          base;
                        bool            is_signed, is_upper;

                        is_signed = false;
                        is_upper = false;
                        base = 0;

                        /* Fetch next element */
                        elem = bhnd_nvram_val_next(value, elem, &elen);
                        if (elem == NULL) {
                                BHND_NV_LOG("format string references more "
                                    "than %zu available value elements\n",
                                    bhnd_nvram_val_nelem(value));
                                return (EINVAL);
                        }

                        /*
                         * If this is not the first value, append the delimiter.
                         */
                        if (n > 0) {
                                size_t nremain = 0;
                                if (limit > nbytes)
                                        nremain = limit - nbytes;

                                if (nremain >= delim_len)
                                        memcpy(outp + nbytes, delim, delim_len);

                                /* Add delimiter length to the total byte count */
                                if (SIZE_MAX - nbytes < delim_len)
                                        return (EFTYPE); /* overflows size_t */

                                nbytes += delim_len;
                        }

                        /* Parse integer conversion specifiers */
                        switch (*p) {
                        case 'd':
                        case 'i':
                                base = 10;
                                is_signed = true;
                                break;

                        case 'u':
                                base = 10;
                                break;

                        case 'o':
                                base = 8;
                                break;

                        case 'x':
                                base = 16;
                                break;

                        case 'X':
                                base = 16;
                                is_upper = true;
                                break;
                        }

                        /* Format argument */
                        switch (*p) {
#define NV_ENCODE_INT(_width) do {                                      \
        arg_type = (is_signed) ? BHND_NVRAM_TYPE_INT ## _width :        \
            BHND_NVRAM_TYPE_UINT ## _width;                             \
        arg_size = sizeof(v.u ## _width);                               \
        error = bhnd_nvram_val_encode_elem(value, elem, elen,           \
            &v.u ## _width, &arg_size, arg_type);                       \
        if (error) {                                                    \
                BHND_NV_LOG("error encoding argument as %s: %d\n",      \
                     bhnd_nvram_type_name(arg_type), error);            \
                return (error);                                         \
        }                                                               \
                                                                        \
        if (is_signed) {                                                \
                if (v.i ## _width < 0) {                                \
                        add_neg = true;                                 \
                        numval = (int64_t)-(v.i ## _width);             \
                } else {                                                \
                        numval = (int64_t) (v.i ## _width);             \
                }                                                       \
        } else {                                                        \
                numval = v.u ## _width;                                 \
        }                                                               \
} while(0)
                        case 'd':
                        case 'i':
                        case 'u':
                        case 'o':
                        case 'x':
                        case 'X': {
                                char             numbuf[NV_NUMSTR_MAX];
                                char            *sptr;
                                uint64_t         numval;
                                size_t           slen;
                                bool             add_neg;
                                union {
                                        uint8_t         u8;
                                        uint16_t        u16;
                                        uint32_t        u32;
                                        uint64_t        u64;
                                        int8_t          i8;
                                        int16_t         i16;
                                        int32_t         i32;
                                        int64_t         i64;
                                } v;

                                add_neg = false;

                                /* If precision is specified, it overrides
                                 * (and behaves identically) to a zero-prefixed
                                 * minimum width */
                                if (have_precision) {
                                        padc = '0';
                                        width = precision;
                                        ladjust = false;
                                }

                                /* If zero-padding is used, value must be right
                                 * adjusted */
                                if (padc == '0')
                                        ladjust = false;

                                /* Request encode to the appropriate integer
                                 * type, and then promote to common 64-bit
                                 * representation */
                                switch (bits) {
                                case 8:
                                        NV_ENCODE_INT(8);
                                        break;
                                case 16:
                                        NV_ENCODE_INT(16);
                                        break;
                                case 32:
                                        NV_ENCODE_INT(32);
                                        break;
                                case 64:
                                        NV_ENCODE_INT(64);
                                        break;
                                default:
                                        BHND_NV_LOG("invalid length specifier: "
                                            "%lu\n", bits);
                                        return (EINVAL);
                                }
#undef  NV_ENCODE_INT

                                /* If a precision of 0 is specified and the
                                 * value is also zero, no characters should
                                 * be produced */
                                if (have_precision && precision == 0 &&
                                    numval == 0)
                                {
                                        break;
                                }

                                /* Emit string representation to local buffer */
                                BHND_NV_ASSERT(base <= 16, ("invalid base"));
                                sptr = numbuf + nitems(numbuf) - 1;
                                for (slen = 0; slen < sizeof(numbuf); slen++) {
                                        char            c;
                                        uint64_t        n;

                                        n = numval % base;
                                        c = bhnd_nv_hex2ascii(n);
                                        if (is_upper)
                                                c = bhnd_nv_toupper(c);

                                        sptr--;
                                        *sptr = c;

                                        numval /= (uint64_t)base;
                                        if (numval == 0) {
                                                slen++;
                                                break;
                                        }
                                }

                                arg_size = slen;

                                /* Reserve space for 0/0x prefix? */
                                if (alt_form) {
                                        if (numval == 0) {
                                                /* If 0, no prefix */
                                                alt_form = false;
                                        } else if (base == 8) {
                                                arg_size += 1; /* 0 */
                                        } else if (base == 16) {
                                                arg_size += 2; /* 0x/0X */
                                        }
                                }

                                /* Reserve space for ' ', '+', or '-' prefix? */
                                if (add_neg || signc != '\0') {
                                        if (add_neg)
                                                signc = '-';

                                        arg_size++;
                                }

                                /* Right adjust (if using spaces) */
                                if (!ladjust && padc != '0') {
                                        for (i = arg_size;  i < width; i++)
                                                WRITE_CHAR(padc);
                                }

                                if (signc != '\0')
                                        WRITE_CHAR(signc);

                                if (alt_form) {
                                        if (base == 8) {
                                                WRITE_CHAR('0');
                                        } else if (base == 16) {
                                                WRITE_CHAR('0');
                                                if (is_upper)
                                                        WRITE_CHAR('X');
                                                else
                                                        WRITE_CHAR('x');
                                        }
                                }

                                /* Right adjust (if using zeros) */
                                if (!ladjust && padc == '0') {
                                        for (i = slen;  i < width; i++)
                                                WRITE_CHAR(padc);
                                }

                                /* Write the string to our output buffer */
                                if (limit > nbytes && limit - nbytes >= slen)
                                        memcpy(outp + nbytes, sptr, slen);

                                /* Update the total byte count */
                                if (SIZE_MAX - nbytes < arg_size)
                                        return (EFTYPE); /* overflows size_t */

                                nbytes += arg_size;

                                /* Left adjust */
                                for (i = arg_size; ladjust && i < width; i++)
                                        WRITE_CHAR(padc);

                                break;
                        }

                        case 's': {
                                char    *s;
                                size_t   slen;

                                /* Query the total length of the element when
                                 * converted to a string */
                                arg_type = BHND_NVRAM_TYPE_STRING;
                                error = bhnd_nvram_val_encode_elem(value, elem,
                                    elen, NULL, &arg_size, arg_type);
                                if (error) {
                                        BHND_NV_LOG("error encoding argument "
                                            "as %s: %d\n",
                                            bhnd_nvram_type_name(arg_type),
                                            error);
                                        return (error);
                                }

                                /* Do not include trailing NUL in the string
                                 * length */
                                if (arg_size > 0)
                                        arg_size--;

                                /* Right adjust */
                                for (i = arg_size; !ladjust && i < width; i++)
                                        WRITE_CHAR(padc);

                                /* Determine output positition and remaining
                                 * buffer space */
                                if (limit > nbytes) {
                                        s = outp + nbytes;
                                        slen = limit - nbytes;
                                } else {
                                        s = NULL;
                                        slen = 0;
                                }

                                /* Encode the string to our output buffer */
                                error = bhnd_nvram_val_encode_elem(value, elem,
                                    elen, s, &slen, arg_type);
                                if (error && error != ENOMEM) {
                                        BHND_NV_LOG("error encoding argument "
                                            "as %s: %d\n",
                                            bhnd_nvram_type_name(arg_type),
                                            error);
                                        return (error);
                                }

                                /* Update the total byte count */
                                if (SIZE_MAX - nbytes < arg_size)
                                        return (EFTYPE); /* overflows size_t */

                                nbytes += arg_size;

                                /* Left adjust */
                                for (i = arg_size; ladjust && i < width; i++)
                                        WRITE_CHAR(padc);

                                break;
                        }

                        case 'c': {
                                char c;

                                arg_type = BHND_NVRAM_TYPE_CHAR;
                                arg_size = bhnd_nvram_type_width(arg_type);

                                /* Encode as single character */
                                error = bhnd_nvram_val_encode_elem(value, elem,
                                    elen, &c, &arg_size, arg_type);
                                if (error) {
                                        BHND_NV_LOG("error encoding argument "
                                            "as %s: %d\n",
                                            bhnd_nvram_type_name(arg_type),
                                            error);
                                        return (error);
                                }

                                BHND_NV_ASSERT(arg_size == sizeof(c),
                                    ("invalid encoded size"));

                                /* Right adjust */
                                for (i = arg_size; !ladjust && i < width; i++)
                                        WRITE_CHAR(padc);

                                WRITE_CHAR(padc);

                                /* Left adjust */
                                for (i = arg_size; ladjust && i < width; i++)
                                        WRITE_CHAR(padc);

                                break;
                        }
                        }
                }
        }

        /* Append terminating NUL */
        if (limit > nbytes)
                *(outp + nbytes) = '\0';

        if (nbytes < SIZE_MAX)
                nbytes++;
        else
                return (EFTYPE);

        /* Report required space */
        *olen = nbytes;
        if (limit < nbytes) {
                if (outp != NULL)
                        return (ENOMEM);
        }

        return (0);
}