]>
Commit | Line | Data |
---|---|---|
b57f37fb WP |
1 | |
2 | /* | |
3 | * Modified and hacked into libratbox by Aaron Sethman <androsyn@ratbox.org> | |
4 | * The original headers are below.. | |
5 | * Note that this implementation does not process floating point numbers so | |
6 | * you will likely need to fall back to using sprintf yourself to do those... | |
7 | * $Id: snprintf.c 25038 2008-01-23 16:03:08Z androsyn $ | |
8 | */ | |
9 | ||
10 | /* | |
11 | * linux/lib/vsprintf.c | |
12 | * | |
13 | * Copyright (C) 1991, 1992 Linus Torvalds | |
14 | */ | |
15 | ||
16 | /* vsprintf.c -- Lars Wirzenius & Linus Torvalds. */ | |
17 | /* | |
18 | * Wirzenius wrote this portably, Torvalds fucked it up :-) | |
19 | */ | |
20 | ||
21 | /* | |
22 | * Fri Jul 13 2001 Crutcher Dunnavant <crutcher+kernel@datastacks.com> | |
23 | * - changed to provide snprintf and vsnprintf functions | |
24 | * So Feb 1 16:51:32 CET 2004 Juergen Quade <quade@hsnr.de> | |
25 | * - scnprintf and vscnprintf | |
26 | */ | |
27 | #include <libratbox_config.h> | |
28 | #include <ratbox_lib.h> | |
29 | ||
30 | static int skip_atoi(const char **s) | |
31 | { | |
32 | int i=0; | |
33 | ||
34 | while (isdigit(**s)) | |
35 | i = i*10 + *((*s)++) - '0'; | |
36 | return i; | |
37 | } | |
38 | ||
39 | /* Decimal conversion is by far the most typical, and is used | |
40 | * for /proc and /sys data. This directly impacts e.g. top performance | |
41 | * with many processes running. We optimize it for speed | |
42 | * using code from | |
43 | * http://www.cs.uiowa.edu/~jones/bcd/decimal.html | |
44 | * (with permission from the author, Douglas W. Jones). */ | |
45 | ||
46 | /* Formats correctly any integer in [0,99999]. | |
47 | * Outputs from one to five digits depending on input. | |
48 | * On i386 gcc 4.1.2 -O2: ~250 bytes of code. */ | |
49 | static char* put_dec_trunc(char *buf, unsigned q) | |
50 | { | |
51 | unsigned d3, d2, d1, d0; | |
52 | d1 = (q>>4) & 0xf; | |
53 | d2 = (q>>8) & 0xf; | |
54 | d3 = (q>>12); | |
55 | ||
56 | d0 = 6*(d3 + d2 + d1) + (q & 0xf); | |
57 | q = (d0 * 0xcd) >> 11; | |
58 | d0 = d0 - 10*q; | |
59 | *buf++ = d0 + '0'; /* least significant digit */ | |
60 | d1 = q + 9*d3 + 5*d2 + d1; | |
61 | if (d1 != 0) { | |
62 | q = (d1 * 0xcd) >> 11; | |
63 | d1 = d1 - 10*q; | |
64 | *buf++ = d1 + '0'; /* next digit */ | |
65 | ||
66 | d2 = q + 2*d2; | |
67 | if ((d2 != 0) || (d3 != 0)) { | |
68 | q = (d2 * 0xd) >> 7; | |
69 | d2 = d2 - 10*q; | |
70 | *buf++ = d2 + '0'; /* next digit */ | |
71 | ||
72 | d3 = q + 4*d3; | |
73 | if (d3 != 0) { | |
74 | q = (d3 * 0xcd) >> 11; | |
75 | d3 = d3 - 10*q; | |
76 | *buf++ = d3 + '0'; /* next digit */ | |
77 | if (q != 0) | |
78 | *buf++ = q + '0'; /* most sign. digit */ | |
79 | } | |
80 | } | |
81 | } | |
82 | return buf; | |
83 | } | |
84 | /* Same with if's removed. Always emits five digits */ | |
85 | static char* put_dec_full(char *buf, unsigned q) | |
86 | { | |
87 | /* BTW, if q is in [0,9999], 8-bit ints will be enough, */ | |
88 | /* but anyway, gcc produces better code with full-sized ints */ | |
89 | unsigned d3, d2, d1, d0; | |
90 | d1 = (q>>4) & 0xf; | |
91 | d2 = (q>>8) & 0xf; | |
92 | d3 = (q>>12); | |
93 | ||
94 | /* Possible ways to approx. divide by 10 */ | |
95 | /* gcc -O2 replaces multiply with shifts and adds */ | |
96 | // (x * 0xcd) >> 11: 11001101 - shorter code than * 0x67 (on i386) | |
97 | // (x * 0x67) >> 10: 1100111 | |
98 | // (x * 0x34) >> 9: 110100 - same | |
99 | // (x * 0x1a) >> 8: 11010 - same | |
100 | // (x * 0x0d) >> 7: 1101 - same, shortest code (on i386) | |
101 | ||
102 | d0 = 6*(d3 + d2 + d1) + (q & 0xf); | |
103 | q = (d0 * 0xcd) >> 11; | |
104 | d0 = d0 - 10*q; | |
105 | *buf++ = d0 + '0'; | |
106 | d1 = q + 9*d3 + 5*d2 + d1; | |
107 | q = (d1 * 0xcd) >> 11; | |
108 | d1 = d1 - 10*q; | |
109 | *buf++ = d1 + '0'; | |
110 | ||
111 | d2 = q + 2*d2; | |
112 | q = (d2 * 0xd) >> 7; | |
113 | d2 = d2 - 10*q; | |
114 | *buf++ = d2 + '0'; | |
115 | ||
116 | d3 = q + 4*d3; | |
117 | q = (d3 * 0xcd) >> 11; /* - shorter code */ | |
118 | /* q = (d3 * 0x67) >> 10; - would also work */ | |
119 | d3 = d3 - 10*q; | |
120 | *buf++ = d3 + '0'; | |
121 | *buf++ = q + '0'; | |
122 | return buf; | |
123 | } | |
124 | ||
125 | static char* put_dec(char *buf, unsigned long long int num) | |
126 | { | |
127 | while (1) { | |
128 | unsigned rem; | |
129 | if (num < 100000) | |
130 | return put_dec_trunc(buf, num); | |
131 | rem = num % 100000; | |
132 | num = num / 100000; | |
133 | buf = put_dec_full(buf, rem); | |
134 | } | |
135 | } | |
136 | ||
137 | #define ZEROPAD 1 /* pad with zero */ | |
138 | #define SIGN 2 /* unsigned/signed long */ | |
139 | #define PLUS 4 /* show plus */ | |
140 | #define SPACE 8 /* space if plus */ | |
141 | #define LEFT 16 /* left justified */ | |
142 | #define SPECIAL 32 /* 0x */ | |
143 | #define LARGE 64 /* use 'ABCDEF' instead of 'abcdef' */ | |
144 | ||
145 | static char *number(char *buf, char *end, unsigned long long int num, int base, int size, int precision, int type) | |
146 | { | |
147 | char sign,tmp[66]; | |
148 | const char *digits; | |
149 | /* we are called with base 8, 10 or 16, only, thus don't need "g..." */ | |
150 | static const char small_digits[] = "0123456789abcdefx"; /* "ghijklmnopqrstuvwxyz"; */ | |
151 | static const char large_digits[] = "0123456789ABCDEFX"; /* "GHIJKLMNOPQRSTUVWXYZ"; */ | |
152 | int need_pfx = ((type & SPECIAL) && base != 10); | |
153 | int i; | |
154 | ||
155 | digits = (type & LARGE) ? large_digits : small_digits; | |
156 | if (type & LEFT) | |
157 | type &= ~ZEROPAD; | |
158 | if (base < 2 || base > 36) | |
159 | return NULL; | |
160 | sign = 0; | |
161 | if (type & SIGN) { | |
162 | if ((signed long long int) num < 0) { | |
163 | sign = '-'; | |
164 | num = - (signed long long int) num; | |
165 | size--; | |
166 | } else if (type & PLUS) { | |
167 | sign = '+'; | |
168 | size--; | |
169 | } else if (type & SPACE) { | |
170 | sign = ' '; | |
171 | size--; | |
172 | } | |
173 | } | |
174 | if (need_pfx) { | |
175 | size--; | |
176 | if (base == 16) | |
177 | size--; | |
178 | } | |
179 | ||
180 | /* generate full string in tmp[], in reverse order */ | |
181 | i = 0; | |
182 | if (num == 0) | |
183 | tmp[i++] = '0'; | |
184 | /* Generic code, for any base: | |
185 | else do { | |
186 | tmp[i++] = digits[do_div(num,base)]; | |
187 | } while (num != 0); | |
188 | */ | |
189 | else if (base != 10) { /* 8 or 16 */ | |
190 | int mask = base - 1; | |
191 | int shift = 3; | |
192 | if (base == 16) shift = 4; | |
193 | do { | |
194 | tmp[i++] = digits[((unsigned char)num) & mask]; | |
195 | num >>= shift; | |
196 | } while (num); | |
197 | } else { /* base 10 */ | |
198 | i = put_dec(tmp, num) - tmp; | |
199 | } | |
200 | ||
201 | /* printing 100 using %2d gives "100", not "00" */ | |
202 | if (i > precision) | |
203 | precision = i; | |
204 | /* leading space padding */ | |
205 | size -= precision; | |
206 | if (!(type & (ZEROPAD+LEFT))) { | |
207 | while(--size >= 0) { | |
208 | if (buf < end) | |
209 | *buf = ' '; | |
210 | ++buf; | |
211 | } | |
212 | } | |
213 | /* sign */ | |
214 | if (sign) { | |
215 | if (buf < end) | |
216 | *buf = sign; | |
217 | ++buf; | |
218 | } | |
219 | /* "0x" / "0" prefix */ | |
220 | if (need_pfx) { | |
221 | if (buf < end) | |
222 | *buf = '0'; | |
223 | ++buf; | |
224 | if (base == 16) { | |
225 | if (buf < end) | |
226 | *buf = digits[16]; /* for arbitrary base: digits[33]; */ | |
227 | ++buf; | |
228 | } | |
229 | } | |
230 | /* zero or space padding */ | |
231 | if (!(type & LEFT)) { | |
232 | char c = (type & ZEROPAD) ? '0' : ' '; | |
233 | while (--size >= 0) { | |
234 | if (buf < end) | |
235 | *buf = c; | |
236 | ++buf; | |
237 | } | |
238 | } | |
239 | /* hmm even more zero padding? */ | |
240 | while (i <= --precision) { | |
241 | if (buf < end) | |
242 | *buf = '0'; | |
243 | ++buf; | |
244 | } | |
245 | /* actual digits of result */ | |
246 | while (--i >= 0) { | |
247 | if (buf < end) | |
248 | *buf = tmp[i]; | |
249 | ++buf; | |
250 | } | |
251 | /* trailing space padding */ | |
252 | while (--size >= 0) { | |
253 | if (buf < end) | |
254 | *buf = ' '; | |
255 | ++buf; | |
256 | } | |
257 | return buf; | |
258 | } | |
259 | ||
260 | /** | |
261 | * vsnprintf - Format a string and place it in a buffer | |
262 | * @buf: The buffer to place the result into | |
263 | * @size: The size of the buffer, including the trailing null space | |
264 | * @fmt: The format string to use | |
265 | * @args: Arguments for the format string | |
266 | * | |
267 | * The return value is the number of characters which would | |
268 | * be generated for the given input, excluding the trailing | |
269 | * '\0', as per ISO C99. If you want to have the exact | |
270 | * number of characters written into @buf as return value | |
271 | * (not including the trailing '\0'), use vscnprintf(). If the | |
272 | * return is greater than or equal to @size, the resulting | |
273 | * string is truncated. | |
274 | * | |
275 | * Call this function if you are already dealing with a va_list. | |
276 | * You probably want snprintf() instead. | |
277 | */ | |
278 | int rb_vsnprintf(char *buf, size_t size, const char *fmt, va_list args) | |
279 | { | |
280 | int len; | |
281 | unsigned long long int num; | |
282 | int i, base; | |
283 | char *str, *end, c; | |
284 | const char *s; | |
285 | ||
286 | int flags; /* flags to number() */ | |
287 | ||
288 | int field_width; /* width of output field */ | |
289 | int precision; /* min. # of digits for integers; max | |
290 | number of chars for from string */ | |
291 | int qualifier; /* 'h', 'l', or 'L' for integer fields */ | |
292 | /* 'z' support added 23/7/1999 S.H. */ | |
293 | /* 'z' changed to 'Z' --davidm 1/25/99 */ | |
294 | /* 't' added for ptrdiff_t */ | |
295 | ||
296 | /* Reject out-of-range values early. Large positive sizes are | |
297 | used for unknown buffer sizes. */ | |
298 | if (unlikely((int) size < 0)) { | |
299 | return 0; | |
300 | } | |
301 | ||
302 | str = buf; | |
303 | end = buf + size; | |
304 | ||
305 | /* Make sure end is always >= buf */ | |
306 | if (end < buf) { | |
307 | end = ((void *)-1); | |
308 | size = end - buf; | |
309 | } | |
310 | ||
311 | for (; *fmt ; ++fmt) { | |
312 | if (*fmt != '%') { | |
313 | if (str < end) | |
314 | *str = *fmt; | |
315 | ++str; | |
316 | continue; | |
317 | } | |
318 | ||
319 | /* process flags */ | |
320 | flags = 0; | |
321 | repeat: | |
322 | ++fmt; /* this also skips first '%' */ | |
323 | switch (*fmt) { | |
324 | case '-': flags |= LEFT; goto repeat; | |
325 | case '+': flags |= PLUS; goto repeat; | |
326 | case ' ': flags |= SPACE; goto repeat; | |
327 | case '#': flags |= SPECIAL; goto repeat; | |
328 | case '0': flags |= ZEROPAD; goto repeat; | |
329 | } | |
330 | ||
331 | /* get field width */ | |
332 | field_width = -1; | |
333 | if (isdigit(*fmt)) | |
334 | field_width = skip_atoi(&fmt); | |
335 | else if (*fmt == '*') { | |
336 | ++fmt; | |
337 | /* it's the next argument */ | |
338 | field_width = va_arg(args, int); | |
339 | if (field_width < 0) { | |
340 | field_width = -field_width; | |
341 | flags |= LEFT; | |
342 | } | |
343 | } | |
344 | ||
345 | /* get the precision */ | |
346 | precision = -1; | |
347 | if (*fmt == '.') { | |
348 | ++fmt; | |
349 | if (isdigit(*fmt)) | |
350 | precision = skip_atoi(&fmt); | |
351 | else if (*fmt == '*') { | |
352 | ++fmt; | |
353 | /* it's the next argument */ | |
354 | precision = va_arg(args, int); | |
355 | } | |
356 | if (precision < 0) | |
357 | precision = 0; | |
358 | } | |
359 | ||
360 | /* get the conversion qualifier */ | |
361 | qualifier = -1; | |
362 | if (*fmt == 'h' || *fmt == 'l' || *fmt == 'L' || | |
363 | *fmt =='Z' || *fmt == 'z' || *fmt == 't') { | |
364 | qualifier = *fmt; | |
365 | ++fmt; | |
366 | if (qualifier == 'l' && *fmt == 'l') { | |
367 | qualifier = 'L'; | |
368 | ++fmt; | |
369 | } | |
370 | } | |
371 | ||
372 | /* default base */ | |
373 | base = 10; | |
374 | ||
375 | switch (*fmt) { | |
376 | case 'c': | |
377 | if (!(flags & LEFT)) { | |
378 | while (--field_width > 0) { | |
379 | if (str < end) | |
380 | *str = ' '; | |
381 | ++str; | |
382 | } | |
383 | } | |
384 | c = (unsigned char) va_arg(args, int); | |
385 | if (str < end) | |
386 | *str = c; | |
387 | ++str; | |
388 | while (--field_width > 0) { | |
389 | if (str < end) | |
390 | *str = ' '; | |
391 | ++str; | |
392 | } | |
393 | continue; | |
394 | ||
395 | case 's': | |
396 | s = va_arg(args, char *); | |
397 | if (s == NULL) { | |
398 | abort(); /* prefer blowing up vs corrupt data */ | |
399 | } | |
400 | len = rb_strnlen(s, precision); | |
401 | ||
402 | if (!(flags & LEFT)) { | |
403 | while (len < field_width--) { | |
404 | if (str < end) | |
405 | *str = ' '; | |
406 | ++str; | |
407 | } | |
408 | } | |
409 | for (i = 0; i < len; ++i) { | |
410 | if (str < end) | |
411 | *str = *s; | |
412 | ++str; ++s; | |
413 | } | |
414 | while (len < field_width--) { | |
415 | if (str < end) | |
416 | *str = ' '; | |
417 | ++str; | |
418 | } | |
419 | continue; | |
420 | ||
421 | case 'p': | |
422 | if (field_width == -1) { | |
423 | field_width = 2*sizeof(void *); | |
424 | flags |= ZEROPAD; | |
425 | } | |
426 | str = number(str, end, | |
427 | (unsigned long) va_arg(args, void *), | |
428 | 16, field_width, precision, flags); | |
429 | continue; | |
430 | ||
431 | ||
432 | case 'n': | |
433 | /* FIXME: | |
434 | * What does C99 say about the overflow case here? */ | |
435 | if (qualifier == 'l') { | |
436 | long * ip = va_arg(args, long *); | |
437 | *ip = (str - buf); | |
438 | } else if (qualifier == 'Z' || qualifier == 'z') { | |
439 | size_t * ip = va_arg(args, size_t *); | |
440 | *ip = (str - buf); | |
441 | } else { | |
442 | int * ip = va_arg(args, int *); | |
443 | *ip = (str - buf); | |
444 | } | |
445 | continue; | |
446 | ||
447 | case '%': | |
448 | if (str < end) | |
449 | *str = '%'; | |
450 | ++str; | |
451 | continue; | |
452 | ||
453 | /* integer number formats - set up the flags and "break" */ | |
454 | case 'o': | |
455 | base = 8; | |
456 | break; | |
457 | ||
458 | case 'X': | |
459 | flags |= LARGE; | |
460 | case 'x': | |
461 | base = 16; | |
462 | break; | |
463 | ||
464 | case 'd': | |
465 | case 'i': | |
466 | flags |= SIGN; | |
467 | case 'u': | |
468 | break; | |
469 | ||
470 | default: | |
471 | if (str < end) | |
472 | *str = '%'; | |
473 | ++str; | |
474 | if (*fmt) { | |
475 | if (str < end) | |
476 | *str = *fmt; | |
477 | ++str; | |
478 | } else { | |
479 | --fmt; | |
480 | } | |
481 | continue; | |
482 | } | |
483 | if (qualifier == 'L') | |
484 | num = va_arg(args, long long int); | |
485 | else if (qualifier == 'l') { | |
486 | num = va_arg(args, unsigned long); | |
487 | if (flags & SIGN) | |
488 | num = (signed long) num; | |
489 | } else if (qualifier == 'Z' || qualifier == 'z') { | |
490 | num = va_arg(args, size_t); | |
491 | } else if (qualifier == 't') { | |
492 | num = va_arg(args, ptrdiff_t); | |
493 | } else if (qualifier == 'h') { | |
494 | num = (unsigned short) va_arg(args, int); | |
495 | if (flags & SIGN) | |
496 | num = (signed short) num; | |
497 | } else { | |
498 | num = va_arg(args, unsigned int); | |
499 | if (flags & SIGN) | |
500 | num = (signed int) num; | |
501 | } | |
502 | str = number(str, end, num, base, | |
503 | field_width, precision, flags); | |
504 | } | |
505 | if (size > 0) { | |
506 | if (str < end) | |
507 | *str = '\0'; | |
508 | else | |
509 | end[-1] = '\0'; | |
510 | } | |
511 | /* the trailing null byte doesn't count towards the total */ | |
512 | return str-buf; | |
513 | } | |
514 | ||
515 | /** | |
516 | * snprintf - Format a string and place it in a buffer | |
517 | * @buf: The buffer to place the result into | |
518 | * @size: The size of the buffer, including the trailing null space | |
519 | * @fmt: The format string to use | |
520 | * @...: Arguments for the format string | |
521 | * | |
522 | * The return value is the number of characters which would be | |
523 | * generated for the given input, excluding the trailing null, | |
524 | * as per ISO C99. If the return is greater than or equal to | |
525 | * @size, the resulting string is truncated. | |
526 | */ | |
527 | int rb_snprintf(char * buf, size_t size, const char *fmt, ...) | |
528 | { | |
529 | va_list args; | |
530 | int i; | |
531 | ||
532 | va_start(args, fmt); | |
533 | i=rb_vsnprintf(buf,size,fmt,args); | |
534 | va_end(args); | |
535 | return i; | |
536 | } | |
537 | ||
538 | /** | |
539 | * vsprintf - Format a string and place it in a buffer | |
540 | * @buf: The buffer to place the result into | |
541 | * @fmt: The format string to use | |
542 | * @args: Arguments for the format string | |
543 | * | |
544 | * The function returns the number of characters written | |
545 | * into @buf. Use vsnprintf() or vscnprintf() in order to avoid | |
546 | * buffer overflows. | |
547 | * | |
548 | * Call this function if you are already dealing with a va_list. | |
549 | * You probably want sprintf() instead. | |
550 | */ | |
551 | int rb_vsprintf(char *buf, const char *fmt, va_list args) | |
552 | { | |
553 | return rb_vsnprintf(buf, INT_MAX, fmt, args); | |
554 | } | |
555 | ||
556 | /** | |
557 | * sprintf - Format a string and place it in a buffer | |
558 | * @buf: The buffer to place the result into | |
559 | * @fmt: The format string to use | |
560 | * @...: Arguments for the format string | |
561 | * | |
562 | * The function returns the number of characters written | |
563 | * into @buf. Use snprintf() or scnprintf() in order to avoid | |
564 | * buffer overflows. | |
565 | */ | |
566 | int rb_sprintf(char * buf, const char *fmt, ...) | |
567 | { | |
568 | va_list args; | |
569 | int i; | |
570 | ||
571 | va_start(args, fmt); | |
572 | i=rb_vsnprintf(buf, INT_MAX, fmt, args); | |
573 | va_end(args); | |
574 | return i; | |
575 | } | |
576 | ||
577 | /* | |
578 | * rb_vsprintf_append() | |
579 | * appends sprintf formatted string to the end of the buffer | |
580 | */ | |
581 | ||
582 | int | |
583 | rb_vsprintf_append(char *str, const char *format, va_list ap) | |
584 | { | |
585 | size_t x = strlen(str); | |
586 | return(rb_vsprintf(str+x, format, ap) + x); | |
587 | } | |
588 | ||
589 | /* | |
590 | * rb_sprintf_append() | |
591 | * appends sprintf formatted string to the end of the buffer | |
592 | */ | |
593 | int | |
594 | rb_sprintf_append(char *str, const char *format, ...) | |
595 | { | |
596 | int x; | |
597 | va_list ap; | |
598 | va_start(ap, format); | |
599 | x = rb_vsprintf_append(str, format, ap); | |
600 | va_end(ap); | |
601 | return(x); | |
602 | } | |
603 | ||
604 | /* | |
605 | * rb_vsnprintf_append() | |
606 | * appends sprintf formatted string to the end of the buffer but not | |
607 | * exceeding len | |
608 | */ | |
609 | ||
610 | int | |
611 | rb_vsnprintf_append(char *str, size_t len, const char *format, va_list ap) | |
612 | { | |
613 | size_t x = strlen(str); | |
614 | return(rb_vsnprintf(str+x, len - x, format, ap) + x); | |
615 | } | |
616 | ||
617 | /* | |
618 | * rb_snprintf_append() | |
619 | * appends snprintf formatted string to the end of the buffer but not | |
620 | * exceeding len | |
621 | */ | |
622 | ||
623 | int | |
624 | rb_snprintf_append(char *str, size_t len, const char *format, ...) | |
625 | { | |
626 | int x; | |
627 | va_list ap; | |
628 | va_start(ap, format); | |
629 | x = rb_vsnprintf_append(str, len, format, ap); | |
630 | va_end(ap); | |
631 | return(x); | |
632 | } | |
633 |