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