Home | History | Annotate | Download | only in lib
      1 /*
      2  * vsnprintf.c
      3  *
      4  * vsnprintf(), from which the rest of the printf()
      5  * family is built
      6  */
      7 
      8 #include <stdarg.h>
      9 #include <stddef.h>
     10 #include <inttypes.h>
     11 #include <string.h>
     12 #include <limits.h>
     13 #include <stdio.h>
     14 
     15 enum flags {
     16     FL_ZERO = 0x01,		/* Zero modifier */
     17     FL_MINUS = 0x02,		/* Minus modifier */
     18     FL_PLUS = 0x04,		/* Plus modifier */
     19     FL_TICK = 0x08,		/* ' modifier */
     20     FL_SPACE = 0x10,		/* Space modifier */
     21     FL_HASH = 0x20,		/* # modifier */
     22     FL_SIGNED = 0x40,		/* Number is signed */
     23     FL_UPPER = 0x80		/* Upper case digits */
     24 };
     25 
     26 /* These may have to be adjusted on certain implementations */
     27 enum ranks {
     28     rank_char = -2,
     29     rank_short = -1,
     30     rank_int = 0,
     31     rank_long = 1,
     32     rank_longlong = 2
     33 };
     34 
     35 #define MIN_RANK	rank_char
     36 #define MAX_RANK	rank_longlong
     37 
     38 #define INTMAX_RANK	rank_longlong
     39 #define SIZE_T_RANK	rank_long
     40 #define PTRDIFF_T_RANK	rank_long
     41 
     42 #define EMIT(x) ({ if (o<n){*q++ = (x);} o++; })
     43 
     44 static size_t
     45 format_int(char *q, size_t n, uintmax_t val, enum flags flags,
     46 	   int base, int width, int prec)
     47 {
     48     char *qq;
     49     size_t o = 0, oo;
     50     static const char lcdigits[] = "0123456789abcdef";
     51     static const char ucdigits[] = "0123456789ABCDEF";
     52     const char *digits;
     53     uintmax_t tmpval;
     54     int minus = 0;
     55     int ndigits = 0, nchars;
     56     int tickskip, b4tick;
     57 
     58     /* Select type of digits */
     59     digits = (flags & FL_UPPER) ? ucdigits : lcdigits;
     60 
     61     /* If signed, separate out the minus */
     62     if (flags & FL_SIGNED && (intmax_t) val < 0) {
     63 	minus = 1;
     64 	val = (uintmax_t) (-(intmax_t) val);
     65     }
     66 
     67     /* Count the number of digits needed.  This returns zero for 0. */
     68     tmpval = val;
     69     while (tmpval) {
     70 	tmpval /= base;
     71 	ndigits++;
     72     }
     73 
     74     /* Adjust ndigits for size of output */
     75 
     76     if (flags & FL_HASH && base == 8) {
     77 	if (prec < ndigits + 1)
     78 	    prec = ndigits + 1;
     79     }
     80 
     81     if (ndigits < prec) {
     82 	ndigits = prec;		/* Mandatory number padding */
     83     } else if (val == 0) {
     84 	ndigits = 1;		/* Zero still requires space */
     85     }
     86 
     87     /* For ', figure out what the skip should be */
     88     if (flags & FL_TICK) {
     89 	tickskip = (base == 16) ? 4 : 3;
     90     } else {
     91 	tickskip = ndigits;	/* No tick marks */
     92     }
     93 
     94     /* Tick marks aren't digits, but generated by the number converter */
     95     ndigits += (ndigits - 1) / tickskip;
     96 
     97     /* Now compute the number of nondigits */
     98     nchars = ndigits;
     99 
    100     if (minus || (flags & (FL_PLUS | FL_SPACE)))
    101 	nchars++;		/* Need space for sign */
    102     if ((flags & FL_HASH) && base == 16) {
    103 	nchars += 2;		/* Add 0x for hex */
    104     }
    105 
    106     /* Emit early space padding */
    107     if (!(flags & (FL_MINUS | FL_ZERO)) && width > nchars) {
    108 	while (width > nchars) {
    109 	    EMIT(' ');
    110 	    width--;
    111 	}
    112     }
    113 
    114     /* Emit nondigits */
    115     if (minus)
    116 	EMIT('-');
    117     else if (flags & FL_PLUS)
    118 	EMIT('+');
    119     else if (flags & FL_SPACE)
    120 	EMIT(' ');
    121 
    122     if ((flags & FL_HASH) && base == 16) {
    123 	EMIT('0');
    124 	EMIT((flags & FL_UPPER) ? 'X' : 'x');
    125     }
    126 
    127     /* Emit zero padding */
    128     if ((flags & (FL_MINUS | FL_ZERO)) == FL_ZERO && width > ndigits) {
    129 	while (width > nchars) {
    130 	    EMIT('0');
    131 	    width--;
    132 	}
    133     }
    134 
    135     /* Generate the number.  This is done from right to left. */
    136     q += ndigits;		/* Advance the pointer to end of number */
    137     o += ndigits;
    138     qq = q;
    139     oo = o;			/* Temporary values */
    140 
    141     b4tick = tickskip;
    142     while (ndigits > 0) {
    143 	if (!b4tick--) {
    144 	    qq--;
    145 	    oo--;
    146 	    ndigits--;
    147 	    if (oo < n)
    148 		*qq = '_';
    149 	    b4tick = tickskip - 1;
    150 	}
    151 	qq--;
    152 	oo--;
    153 	ndigits--;
    154 	if (oo < n)
    155 	    *qq = digits[val % base];
    156 	val /= base;
    157     }
    158 
    159     /* Emit late space padding */
    160     while ((flags & FL_MINUS) && width > nchars) {
    161 	EMIT(' ');
    162 	width--;
    163     }
    164 
    165     return o;
    166 }
    167 
    168 int vsnprintf(char *buffer, size_t n, const char *format, va_list ap)
    169 {
    170     const char *p = format;
    171     char ch;
    172     char *q = buffer;
    173     size_t o = 0;		/* Number of characters output */
    174     uintmax_t val = 0;
    175     int rank = rank_int;	/* Default rank */
    176     int width = 0;
    177     int prec = -1;
    178     int base;
    179     size_t sz;
    180     enum flags flags = 0;
    181     enum {
    182 	st_normal,		/* Ground state */
    183 	st_flags,		/* Special flags */
    184 	st_width,		/* Field width */
    185 	st_prec,		/* Field precision */
    186 	st_modifiers		/* Length or conversion modifiers */
    187     } state = st_normal;
    188     const char *sarg;		/* %s string argument */
    189     char carg;			/* %c char argument */
    190     int slen;			/* String length */
    191 
    192     while ((ch = *p++)) {
    193 	switch (state) {
    194 	case st_normal:
    195 	    if (ch == '%') {
    196 		state = st_flags;
    197 		flags = 0;
    198 		rank = rank_int;
    199 		width = 0;
    200 		prec = -1;
    201 	    } else {
    202 		EMIT(ch);
    203 	    }
    204 	    break;
    205 
    206 	case st_flags:
    207 	    switch (ch) {
    208 	    case '-':
    209 		flags |= FL_MINUS;
    210 		break;
    211 	    case '+':
    212 		flags |= FL_PLUS;
    213 		break;
    214 	    case '\'':
    215 		flags |= FL_TICK;
    216 		break;
    217 	    case ' ':
    218 		flags |= FL_SPACE;
    219 		break;
    220 	    case '#':
    221 		flags |= FL_HASH;
    222 		break;
    223 	    case '0':
    224 		flags |= FL_ZERO;
    225 		break;
    226 	    default:
    227 		state = st_width;
    228 		p--;		/* Process this character again */
    229 		break;
    230 	    }
    231 	    break;
    232 
    233 	case st_width:
    234 	    if (ch >= '0' && ch <= '9') {
    235 		width = width * 10 + (ch - '0');
    236 	    } else if (ch == '*') {
    237 		width = va_arg(ap, int);
    238 		if (width < 0) {
    239 		    width = -width;
    240 		    flags |= FL_MINUS;
    241 		}
    242 	    } else if (ch == '.') {
    243 		prec = 0;	/* Precision given */
    244 		state = st_prec;
    245 	    } else {
    246 		state = st_modifiers;
    247 		p--;		/* Process this character again */
    248 	    }
    249 	    break;
    250 
    251 	case st_prec:
    252 	    if (ch >= '0' && ch <= '9') {
    253 		prec = prec * 10 + (ch - '0');
    254 	    } else if (ch == '*') {
    255 		prec = va_arg(ap, int);
    256 		if (prec < 0)
    257 		    prec = -1;
    258 	    } else {
    259 		state = st_modifiers;
    260 		p--;		/* Process this character again */
    261 	    }
    262 	    break;
    263 
    264 	case st_modifiers:
    265 	    switch (ch) {
    266 		/* Length modifiers - nonterminal sequences */
    267 	    case 'h':
    268 		rank--;		/* Shorter rank */
    269 		break;
    270 	    case 'l':
    271 		rank++;		/* Longer rank */
    272 		break;
    273 	    case 'j':
    274 		rank = INTMAX_RANK;
    275 		break;
    276 	    case 'z':
    277 		rank = SIZE_T_RANK;
    278 		break;
    279 	    case 't':
    280 		rank = PTRDIFF_T_RANK;
    281 		break;
    282 	    case 'L':
    283 	    case 'q':
    284 		rank += 2;
    285 		break;
    286 	    default:
    287 		/* Output modifiers - terminal sequences */
    288 		state = st_normal;	/* Next state will be normal */
    289 		if (rank < MIN_RANK)	/* Canonicalize rank */
    290 		    rank = MIN_RANK;
    291 		else if (rank > MAX_RANK)
    292 		    rank = MAX_RANK;
    293 
    294 		switch (ch) {
    295 		case 'P':	/* Upper case pointer */
    296 		    flags |= FL_UPPER;
    297 		    /* fall through */
    298 		case 'p':	/* Pointer */
    299 		    base = 16;
    300 		    prec = (CHAR_BIT * sizeof(void *) + 3) / 4;
    301 		    flags |= FL_HASH;
    302 		    val = (uintmax_t) (uintptr_t) va_arg(ap, void *);
    303 		    goto is_integer;
    304 
    305 		case 'd':	/* Signed decimal output */
    306 		case 'i':
    307 		    base = 10;
    308 		    flags |= FL_SIGNED;
    309 		    switch (rank) {
    310 		    case rank_char:
    311 			/* Yes, all these casts are needed... */
    312 			val =
    313 			    (uintmax_t) (intmax_t) (signed char)va_arg(ap,
    314 								       signed
    315 								       int);
    316 			break;
    317 		    case rank_short:
    318 			val =
    319 			    (uintmax_t) (intmax_t) (signed short)va_arg(ap,
    320 									signed
    321 									int);
    322 			break;
    323 		    case rank_int:
    324 			val = (uintmax_t) (intmax_t) va_arg(ap, signed int);
    325 			break;
    326 		    case rank_long:
    327 			val = (uintmax_t) (intmax_t) va_arg(ap, signed long);
    328 			break;
    329 		    case rank_longlong:
    330 			val =
    331 			    (uintmax_t) (intmax_t) va_arg(ap, signed long long);
    332 			break;
    333 		    }
    334 		    goto is_integer;
    335 		case 'o':	/* Octal */
    336 		    base = 8;
    337 		    goto is_unsigned;
    338 		case 'u':	/* Unsigned decimal */
    339 		    base = 10;
    340 		    goto is_unsigned;
    341 		case 'X':	/* Upper case hexadecimal */
    342 		    flags |= FL_UPPER;
    343 		    /* fall through */
    344 		case 'x':	/* Hexadecimal */
    345 		    base = 16;
    346 		    goto is_unsigned;
    347 
    348 is_unsigned:
    349 		    switch (rank) {
    350 		    case rank_char:
    351 			val =
    352 			    (uintmax_t) (unsigned char)va_arg(ap, unsigned int);
    353 			break;
    354 		    case rank_short:
    355 			val =
    356 			    (uintmax_t) (unsigned short)va_arg(ap,
    357 							       unsigned int);
    358 			break;
    359 		    case rank_int:
    360 			val = (uintmax_t) va_arg(ap, unsigned int);
    361 			break;
    362 		    case rank_long:
    363 			val = (uintmax_t) va_arg(ap, unsigned long);
    364 			break;
    365 		    case rank_longlong:
    366 			val = (uintmax_t) va_arg(ap, unsigned long long);
    367 			break;
    368 		    }
    369 		    /* fall through */
    370 
    371 is_integer:
    372 		    sz = format_int(q, (o < n) ? n - o : 0, val, flags, base,
    373 				    width, prec);
    374 		    q += sz;
    375 		    o += sz;
    376 		    break;
    377 
    378 		case 'c':	/* Character */
    379 		    carg = (char)va_arg(ap, int);
    380 		    sarg = &carg;
    381 		    slen = 1;
    382 		    goto is_string;
    383 		case 's':	/* String */
    384 		    sarg = va_arg(ap, const char *);
    385 		    sarg = sarg ? sarg : "(null)";
    386 		    slen = strlen(sarg);
    387 		    goto is_string;
    388 
    389 is_string:
    390 		    {
    391 			char sch;
    392 			int i;
    393 
    394 			if (prec != -1 && slen > prec)
    395 			    slen = prec;
    396 
    397 			if (width > slen && !(flags & FL_MINUS)) {
    398 			    char pad = (flags & FL_ZERO) ? '0' : ' ';
    399 			    while (width > slen) {
    400 				EMIT(pad);
    401 				width--;
    402 			    }
    403 			}
    404 			for (i = slen; i; i--) {
    405 			    sch = *sarg++;
    406 			    EMIT(sch);
    407 			}
    408 			if (width > slen && (flags & FL_MINUS)) {
    409 			    while (width > slen) {
    410 				EMIT(' ');
    411 				width--;
    412 			    }
    413 			}
    414 		    }
    415 		    break;
    416 
    417 		case 'n':	/* Output the number of characters written */
    418 		    {
    419 			switch (rank) {
    420 			case rank_char:
    421 			    *va_arg(ap, signed char *) = o;
    422 			    break;
    423 			case rank_short:
    424 			    *va_arg(ap, signed short *) = o;
    425 			    break;
    426 			case rank_int:
    427 			    *va_arg(ap, signed int *) = o;
    428 			    break;
    429 			case rank_long:
    430 			    *va_arg(ap, signed long *) = o;
    431 			    break;
    432 			case rank_longlong:
    433 			    *va_arg(ap, signed long long *) = o;
    434 			    break;
    435 			}
    436 		    }
    437 		    break;
    438 
    439 		default:	/* Anything else, including % */
    440 		    EMIT(ch);
    441 		    break;
    442 		}
    443 	    }
    444 	}
    445     }
    446 
    447     /* Null-terminate the string */
    448     if (o < n)
    449 	*q = '\0';		/* No overflow */
    450     else if (n > 0)
    451 	buffer[n - 1] = '\0';	/* Overflow - terminate at end of buffer */
    452 
    453     return o;
    454 }
    455