[e16e8f2] | 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 | } |
---|