/* * vsnprintf.c * * vsnprintf(), from which the rest of the printf() * family is built */ #include #include #include #include #include #include enum flags { FL_ZERO = 0x01, /* Zero modifier */ FL_MINUS = 0x02, /* Minus modifier */ FL_PLUS = 0x04, /* Plus modifier */ FL_TICK = 0x08, /* ' modifier */ FL_SPACE = 0x10, /* Space modifier */ FL_HASH = 0x20, /* # modifier */ FL_SIGNED = 0x40, /* Number is signed */ FL_UPPER = 0x80 /* Upper case digits */ }; /* These may have to be adjusted on certain implementations */ enum ranks { rank_char = -2, rank_short = -1, rank_int = 0, rank_long = 1, rank_longlong = 2 }; #define MIN_RANK rank_char #define MAX_RANK rank_longlong #define INTMAX_RANK rank_longlong #define SIZE_T_RANK rank_long #define PTRDIFF_T_RANK rank_long #define EMIT(x) ({ if (o nchars) { while (width > nchars) { EMIT(' '); width--; } } /* Emit nondigits */ if (minus) EMIT('-'); else if (flags & FL_PLUS) EMIT('+'); else if (flags & FL_SPACE) EMIT(' '); if ((flags & FL_HASH) && base == 16) { EMIT('0'); EMIT((flags & FL_UPPER) ? 'X' : 'x'); } /* Emit zero padding */ if ((flags & (FL_MINUS | FL_ZERO)) == FL_ZERO && width > ndigits) { while (width > nchars) { EMIT('0'); width--; } } /* Generate the number. This is done from right to left. */ q += ndigits; /* Advance the pointer to end of number */ o += ndigits; qq = q; oo = o; /* Temporary values */ b4tick = tickskip; while (ndigits > 0) { if (!b4tick--) { qq--; oo--; ndigits--; if (oo < n) *qq = '_'; b4tick = tickskip - 1; } qq--; oo--; ndigits--; if (oo < n) *qq = digits[val % base]; val /= base; } /* Emit late space padding */ while ((flags & FL_MINUS) && width > nchars) { EMIT(' '); width--; } return o; } int vsnprintf(char *buffer, size_t n, const char *format, va_list ap) { const char *p = format; char ch; char *q = buffer; size_t o = 0; /* Number of characters output */ uintmax_t val = 0; int rank = rank_int; /* Default rank */ int width = 0; int prec = -1; int base; size_t sz; enum flags flags = 0; enum { st_normal, /* Ground state */ st_flags, /* Special flags */ st_width, /* Field width */ st_prec, /* Field precision */ st_modifiers /* Length or conversion modifiers */ } state = st_normal; const char *sarg; /* %s string argument */ char carg; /* %c char argument */ int slen; /* String length */ while ((ch = *p++)) { switch (state) { case st_normal: if (ch == '%') { state = st_flags; flags = 0; rank = rank_int; width = 0; prec = -1; } else { EMIT(ch); } break; case st_flags: switch (ch) { case '-': flags |= FL_MINUS; break; case '+': flags |= FL_PLUS; break; case '\'': flags |= FL_TICK; break; case ' ': flags |= FL_SPACE; break; case '#': flags |= FL_HASH; break; case '0': flags |= FL_ZERO; break; default: state = st_width; p--; /* Process this character again */ break; } break; case st_width: if (ch >= '0' && ch <= '9') { width = width * 10 + (ch - '0'); } else if (ch == '*') { width = va_arg(ap, int); if (width < 0) { width = -width; flags |= FL_MINUS; } } else if (ch == '.') { prec = 0; /* Precision given */ state = st_prec; } else { state = st_modifiers; p--; /* Process this character again */ } break; case st_prec: if (ch >= '0' && ch <= '9') { prec = prec * 10 + (ch - '0'); } else if (ch == '*') { prec = va_arg(ap, int); if (prec < 0) prec = -1; } else { state = st_modifiers; p--; /* Process this character again */ } break; case st_modifiers: switch (ch) { /* Length modifiers - nonterminal sequences */ case 'h': rank--; /* Shorter rank */ break; case 'l': rank++; /* Longer rank */ break; case 'j': rank = INTMAX_RANK; break; case 'z': rank = SIZE_T_RANK; break; case 't': rank = PTRDIFF_T_RANK; break; case 'L': case 'q': rank += 2; break; default: /* Output modifiers - terminal sequences */ /* Next state will be normal */ state = st_normal; /* Canonicalize rank */ if (rank < MIN_RANK) rank = MIN_RANK; else if (rank > MAX_RANK) rank = MAX_RANK; switch (ch) { case 'P': /* Upper case pointer */ flags |= FL_UPPER; /* fall through */ case 'p': /* Pointer */ base = 16; prec = (CHAR_BIT*sizeof(void *)+3)/4; flags |= FL_HASH; val = (uintmax_t)(uintptr_t) va_arg(ap, void *); goto is_integer; case 'd': /* Signed decimal output */ case 'i': base = 10; flags |= FL_SIGNED; switch (rank) { case rank_char: /* Yes, all these casts are needed... */ val = (uintmax_t)(intmax_t) (signed char) va_arg(ap, signed int); break; case rank_short: val = (uintmax_t)(intmax_t) (signed short) va_arg(ap, signed int); break; case rank_int: val = (uintmax_t)(intmax_t) va_arg(ap, signed int); break; case rank_long: val = (uintmax_t)(intmax_t) va_arg(ap, signed long); break; case rank_longlong: val = (uintmax_t)(intmax_t) va_arg(ap, signed long long); break; } goto is_integer; case 'o': /* Octal */ base = 8; goto is_unsigned; case 'u': /* Unsigned decimal */ base = 10; goto is_unsigned; case 'X': /* Upper case hexadecimal */ flags |= FL_UPPER; /* fall through */ case 'x': /* Hexadecimal */ base = 16; goto is_unsigned; is_unsigned: switch (rank) { case rank_char: val = (uintmax_t) (unsigned char) va_arg(ap, unsigned int); break; case rank_short: val = (uintmax_t) (unsigned short) va_arg(ap, unsigned int); break; case rank_int: val = (uintmax_t) va_arg(ap, unsigned int); break; case rank_long: val = (uintmax_t) va_arg(ap, unsigned long); break; case rank_longlong: val = (uintmax_t) va_arg(ap, unsigned long long); break; } /* fall through */ is_integer: sz = format_int(q, (o < n) ? n - o : 0, val, flags, base, width, prec); q += sz; o += sz; break; case 'c': /* Character */ carg = (char)va_arg(ap, int); sarg = &carg; slen = 1; goto is_string; case 's': /* String */ sarg = va_arg(ap, const char *); sarg = sarg ? sarg : "(null)"; slen = strlen(sarg); goto is_string; is_string: { char sch; int i; if (prec != -1 && slen > prec) slen = prec; if (width > slen && !(flags & FL_MINUS)) { char pad = (flags & FL_ZERO) ? '0' : ' '; while (width > slen) { EMIT(pad); width--; } } for (i = slen; i; i--) { sch = *sarg++; EMIT(sch); } if (width > slen && (flags & FL_MINUS)) { while (width > slen) { EMIT(' '); width--; } } } break; case 'n': { /* Output the number of characters written */ switch (rank) { case rank_char: *va_arg(ap, signed char *) = o; break; case rank_short: *va_arg(ap, signed short *) = o; break; case rank_int: *va_arg(ap, signed int *) = o; break; case rank_long: *va_arg(ap, signed long *) = o; break; case rank_longlong: *va_arg(ap, signed long long *) = o; break; } } break; default: /* Anything else, including % */ EMIT(ch); break; } } } } /* Null-terminate the string */ if (o < n) *q = '\0'; /* No overflow */ else if (n > 0) buffer[n - 1] = '\0'; /* Overflow - terminate at end of buffer */ return o; }