]>
Commit | Line | Data |
---|---|---|
89c4ed63 A |
1 | /* snprintf - compatibility implementation of snprintf, vsnprintf |
2 | * | |
3 | * Copyright (c) 2013, NLnet Labs. All rights reserved. | |
4 | * | |
5 | * This software is open source. | |
6 | * | |
7 | * Redistribution and use in source and binary forms, with or without | |
8 | * modification, are permitted provided that the following conditions | |
9 | * are met: | |
10 | * | |
11 | * Redistributions of source code must retain the above copyright notice, | |
12 | * this list of conditions and the following disclaimer. | |
13 | * | |
14 | * Redistributions in binary form must reproduce the above copyright notice, | |
15 | * this list of conditions and the following disclaimer in the documentation | |
16 | * and/or other materials provided with the distribution. | |
17 | * | |
18 | * Neither the name of the NLNET LABS nor the names of its contributors may | |
19 | * be used to endorse or promote products derived from this software without | |
20 | * specific prior written permission. | |
21 | * | |
22 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
23 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
24 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
25 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
26 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
27 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED | |
28 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | |
29 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | |
30 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | |
31 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |
32 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
33 | */ | |
34 | ||
35 | #include "config.h" | |
36 | #include <stdio.h> | |
37 | #include <ctype.h> | |
38 | #include <string.h> | |
39 | #include <stdarg.h> | |
40 | #include <stdlib.h> | |
41 | #include <errno.h> | |
42 | #ifdef HAVE_STDINT_H | |
43 | #include <stdint.h> | |
44 | #endif | |
45 | ||
46 | /* for test */ | |
47 | /* #define SNPRINTF_TEST 1 */ | |
48 | #ifdef SNPRINTF_TEST | |
49 | #define snprintf my_snprintf | |
50 | #define vsnprintf my_vsnprintf | |
51 | #endif /* SNPRINTF_TEST */ | |
52 | ||
53 | int snprintf(char* str, size_t size, const char* format, ...); | |
54 | int vsnprintf(char* str, size_t size, const char* format, va_list arg); | |
55 | ||
56 | /** | |
57 | * Very portable snprintf implementation, limited in functionality, | |
58 | * esp. for %[capital] %[nonportable] and so on. Reduced float functionality, | |
59 | * mostly in formatting and range (e+-16), for %f and %g. | |
60 | * | |
61 | * %s, %d, %u, %i, %x, %c, %n and %% are fully supported. | |
62 | * This includes width, precision, flags 0- +, and *(arg for wid,prec). | |
63 | * %f, %g, %m, %p have reduced support, support for wid,prec,flags,*, but | |
64 | * less floating point range, no %e formatting for %g. | |
65 | */ | |
66 | int snprintf(char* str, size_t size, const char* format, ...) | |
67 | { | |
68 | int r; | |
69 | va_list args; | |
70 | va_start(args, format); | |
71 | r = vsnprintf(str, size, format, args); | |
72 | va_end(args); | |
73 | return r; | |
74 | } | |
75 | ||
76 | /** add padding to string */ | |
77 | static void | |
78 | print_pad(char** at, size_t* left, int* ret, char p, int num) | |
79 | { | |
80 | while(num--) { | |
81 | if(*left > 1) { | |
82 | *(*at)++ = p; | |
83 | (*left)--; | |
84 | } | |
85 | (*ret)++; | |
86 | } | |
87 | } | |
88 | ||
89 | /** get negative symbol, 0 if none */ | |
90 | static char | |
91 | get_negsign(int negative, int plus, int space) | |
92 | { | |
93 | if(negative) | |
94 | return '-'; | |
95 | if(plus) | |
96 | return '+'; | |
97 | if(space) | |
98 | return ' '; | |
99 | return 0; | |
100 | } | |
101 | ||
102 | #define PRINT_DEC_BUFSZ 32 /* 20 is enough for 64 bit decimals */ | |
103 | /** print decimal into buffer, returns length */ | |
104 | static int | |
105 | print_dec(char* buf, int max, unsigned int value) | |
106 | { | |
107 | int i = 0; | |
108 | if(value == 0) { | |
109 | if(max > 0) { | |
110 | buf[0] = '0'; | |
111 | i = 1; | |
112 | } | |
113 | } else while(value && i < max) { | |
114 | buf[i++] = '0' + value % 10; | |
115 | value /= 10; | |
116 | } | |
117 | return i; | |
118 | } | |
119 | ||
120 | /** print long decimal into buffer, returns length */ | |
121 | static int | |
122 | print_dec_l(char* buf, int max, unsigned long value) | |
123 | { | |
124 | int i = 0; | |
125 | if(value == 0) { | |
126 | if(max > 0) { | |
127 | buf[0] = '0'; | |
128 | i = 1; | |
129 | } | |
130 | } else while(value && i < max) { | |
131 | buf[i++] = '0' + value % 10; | |
132 | value /= 10; | |
133 | } | |
134 | return i; | |
135 | } | |
136 | ||
137 | /** print long decimal into buffer, returns length */ | |
138 | static int | |
139 | print_dec_ll(char* buf, int max, unsigned long long value) | |
140 | { | |
141 | int i = 0; | |
142 | if(value == 0) { | |
143 | if(max > 0) { | |
144 | buf[0] = '0'; | |
145 | i = 1; | |
146 | } | |
147 | } else while(value && i < max) { | |
148 | buf[i++] = '0' + value % 10; | |
149 | value /= 10; | |
150 | } | |
151 | return i; | |
152 | } | |
153 | ||
154 | /** print hex into buffer, returns length */ | |
155 | static int | |
156 | print_hex(char* buf, int max, unsigned int value) | |
157 | { | |
158 | const char* h = "0123456789abcdef"; | |
159 | int i = 0; | |
160 | if(value == 0) { | |
161 | if(max > 0) { | |
162 | buf[0] = '0'; | |
163 | i = 1; | |
164 | } | |
165 | } else while(value && i < max) { | |
166 | buf[i++] = h[value & 0x0f]; | |
167 | value >>= 4; | |
168 | } | |
169 | return i; | |
170 | } | |
171 | ||
172 | /** print long hex into buffer, returns length */ | |
173 | static int | |
174 | print_hex_l(char* buf, int max, unsigned long value) | |
175 | { | |
176 | const char* h = "0123456789abcdef"; | |
177 | int i = 0; | |
178 | if(value == 0) { | |
179 | if(max > 0) { | |
180 | buf[0] = '0'; | |
181 | i = 1; | |
182 | } | |
183 | } else while(value && i < max) { | |
184 | buf[i++] = h[value & 0x0f]; | |
185 | value >>= 4; | |
186 | } | |
187 | return i; | |
188 | } | |
189 | ||
190 | /** print long long hex into buffer, returns length */ | |
191 | static int | |
192 | print_hex_ll(char* buf, int max, unsigned long long value) | |
193 | { | |
194 | const char* h = "0123456789abcdef"; | |
195 | int i = 0; | |
196 | if(value == 0) { | |
197 | if(max > 0) { | |
198 | buf[0] = '0'; | |
199 | i = 1; | |
200 | } | |
201 | } else while(value && i < max) { | |
202 | buf[i++] = h[value & 0x0f]; | |
203 | value >>= 4; | |
204 | } | |
205 | return i; | |
206 | } | |
207 | ||
208 | /** copy string into result, reversed */ | |
209 | static void | |
210 | spool_str_rev(char** at, size_t* left, int* ret, const char* buf, int len) | |
211 | { | |
212 | int i = len; | |
213 | while(i) { | |
214 | if(*left > 1) { | |
215 | *(*at)++ = buf[--i]; | |
216 | (*left)--; | |
217 | } else --i; | |
218 | (*ret)++; | |
219 | } | |
220 | } | |
221 | ||
222 | /** copy string into result */ | |
223 | static void | |
224 | spool_str(char** at, size_t* left, int* ret, const char* buf, int len) | |
225 | { | |
226 | int i; | |
227 | for(i=0; i<len; i++) { | |
228 | if(*left > 1) { | |
229 | *(*at)++ = buf[i]; | |
230 | (*left)--; | |
231 | } | |
232 | (*ret)++; | |
233 | } | |
234 | } | |
235 | ||
236 | /** print number formatted */ | |
237 | static void | |
238 | print_num(char** at, size_t* left, int* ret, int minw, int precision, | |
239 | int prgiven, int zeropad, int minus, int plus, int space, | |
240 | int zero, int negative, char* buf, int len) | |
241 | { | |
242 | int w = len; /* excludes minus sign */ | |
243 | char s = get_negsign(negative, plus, space); | |
244 | if(minus) { | |
245 | /* left adjust the number into the field, space padding */ | |
246 | /* calc numw = [sign][zeroes][number] */ | |
247 | int numw = w; | |
248 | if(precision == 0 && zero) numw = 0; | |
249 | if(numw < precision) numw = precision; | |
250 | if(s) numw++; | |
251 | ||
252 | /* sign */ | |
253 | if(s) print_pad(at, left, ret, s, 1); | |
254 | ||
255 | /* number */ | |
256 | if(precision == 0 && zero) { | |
257 | /* "" for the number */ | |
258 | } else { | |
259 | if(w < precision) | |
260 | print_pad(at, left, ret, '0', precision - w); | |
261 | spool_str_rev(at, left, ret, buf, len); | |
262 | } | |
263 | /* spaces */ | |
264 | if(numw < minw) | |
265 | print_pad(at, left, ret, ' ', minw - numw); | |
266 | } else { | |
267 | /* pad on the left of the number */ | |
268 | /* calculate numw has width of [sign][zeroes][number] */ | |
269 | int numw = w; | |
270 | if(precision == 0 && zero) numw = 0; | |
271 | if(numw < precision) numw = precision; | |
272 | if(!prgiven && zeropad && numw < minw) numw = minw; | |
273 | else if(s) numw++; | |
274 | ||
275 | /* pad with spaces */ | |
276 | if(numw < minw) | |
277 | print_pad(at, left, ret, ' ', minw - numw); | |
278 | /* print sign (and one less zeropad if so) */ | |
279 | if(s) { | |
280 | print_pad(at, left, ret, s, 1); | |
281 | numw--; | |
282 | } | |
283 | /* pad with zeroes */ | |
284 | if(w < numw) | |
285 | print_pad(at, left, ret, '0', numw - w); | |
286 | if(precision == 0 && zero) | |
287 | return; | |
288 | /* print the characters for the value */ | |
289 | spool_str_rev(at, left, ret, buf, len); | |
290 | } | |
291 | } | |
292 | ||
293 | /** print %d and %i */ | |
294 | static void | |
295 | print_num_d(char** at, size_t* left, int* ret, int value, | |
296 | int minw, int precision, int prgiven, int zeropad, int minus, | |
297 | int plus, int space) | |
298 | { | |
299 | char buf[PRINT_DEC_BUFSZ]; | |
300 | int negative = (value < 0); | |
301 | int zero = (value == 0); | |
302 | int len = print_dec(buf, (int)sizeof(buf), | |
303 | (unsigned int)(negative?-value:value)); | |
304 | print_num(at, left, ret, minw, precision, prgiven, zeropad, minus, | |
305 | plus, space, zero, negative, buf, len); | |
306 | } | |
307 | ||
308 | /** print %ld and %li */ | |
309 | static void | |
310 | print_num_ld(char** at, size_t* left, int* ret, long value, | |
311 | int minw, int precision, int prgiven, int zeropad, int minus, | |
312 | int plus, int space) | |
313 | { | |
314 | char buf[PRINT_DEC_BUFSZ]; | |
315 | int negative = (value < 0); | |
316 | int zero = (value == 0); | |
317 | int len = print_dec_l(buf, (int)sizeof(buf), | |
318 | (unsigned long)(negative?-value:value)); | |
319 | print_num(at, left, ret, minw, precision, prgiven, zeropad, minus, | |
320 | plus, space, zero, negative, buf, len); | |
321 | } | |
322 | ||
323 | /** print %lld and %lli */ | |
324 | static void | |
325 | print_num_lld(char** at, size_t* left, int* ret, long long value, | |
326 | int minw, int precision, int prgiven, int zeropad, int minus, | |
327 | int plus, int space) | |
328 | { | |
329 | char buf[PRINT_DEC_BUFSZ]; | |
330 | int negative = (value < 0); | |
331 | int zero = (value == 0); | |
332 | int len = print_dec_ll(buf, (int)sizeof(buf), | |
333 | (unsigned long long)(negative?-value:value)); | |
334 | print_num(at, left, ret, minw, precision, prgiven, zeropad, minus, | |
335 | plus, space, zero, negative, buf, len); | |
336 | } | |
337 | ||
338 | /** print %u */ | |
339 | static void | |
340 | print_num_u(char** at, size_t* left, int* ret, unsigned int value, | |
341 | int minw, int precision, int prgiven, int zeropad, int minus, | |
342 | int plus, int space) | |
343 | { | |
344 | char buf[PRINT_DEC_BUFSZ]; | |
345 | int negative = 0; | |
346 | int zero = (value == 0); | |
347 | int len = print_dec(buf, (int)sizeof(buf), value); | |
348 | print_num(at, left, ret, minw, precision, prgiven, zeropad, minus, | |
349 | plus, space, zero, negative, buf, len); | |
350 | } | |
351 | ||
352 | /** print %lu */ | |
353 | static void | |
354 | print_num_lu(char** at, size_t* left, int* ret, unsigned long value, | |
355 | int minw, int precision, int prgiven, int zeropad, int minus, | |
356 | int plus, int space) | |
357 | { | |
358 | char buf[PRINT_DEC_BUFSZ]; | |
359 | int negative = 0; | |
360 | int zero = (value == 0); | |
361 | int len = print_dec_l(buf, (int)sizeof(buf), value); | |
362 | print_num(at, left, ret, minw, precision, prgiven, zeropad, minus, | |
363 | plus, space, zero, negative, buf, len); | |
364 | } | |
365 | ||
366 | /** print %llu */ | |
367 | static void | |
368 | print_num_llu(char** at, size_t* left, int* ret, unsigned long long value, | |
369 | int minw, int precision, int prgiven, int zeropad, int minus, | |
370 | int plus, int space) | |
371 | { | |
372 | char buf[PRINT_DEC_BUFSZ]; | |
373 | int negative = 0; | |
374 | int zero = (value == 0); | |
375 | int len = print_dec_ll(buf, (int)sizeof(buf), value); | |
376 | print_num(at, left, ret, minw, precision, prgiven, zeropad, minus, | |
377 | plus, space, zero, negative, buf, len); | |
378 | } | |
379 | ||
380 | /** print %x */ | |
381 | static void | |
382 | print_num_x(char** at, size_t* left, int* ret, unsigned int value, | |
383 | int minw, int precision, int prgiven, int zeropad, int minus, | |
384 | int plus, int space) | |
385 | { | |
386 | char buf[PRINT_DEC_BUFSZ]; | |
387 | int negative = 0; | |
388 | int zero = (value == 0); | |
389 | int len = print_hex(buf, (int)sizeof(buf), value); | |
390 | print_num(at, left, ret, minw, precision, prgiven, zeropad, minus, | |
391 | plus, space, zero, negative, buf, len); | |
392 | } | |
393 | ||
394 | /** print %lx */ | |
395 | static void | |
396 | print_num_lx(char** at, size_t* left, int* ret, unsigned long value, | |
397 | int minw, int precision, int prgiven, int zeropad, int minus, | |
398 | int plus, int space) | |
399 | { | |
400 | char buf[PRINT_DEC_BUFSZ]; | |
401 | int negative = 0; | |
402 | int zero = (value == 0); | |
403 | int len = print_hex_l(buf, (int)sizeof(buf), value); | |
404 | print_num(at, left, ret, minw, precision, prgiven, zeropad, minus, | |
405 | plus, space, zero, negative, buf, len); | |
406 | } | |
407 | ||
408 | /** print %llx */ | |
409 | static void | |
410 | print_num_llx(char** at, size_t* left, int* ret, unsigned long long value, | |
411 | int minw, int precision, int prgiven, int zeropad, int minus, | |
412 | int plus, int space) | |
413 | { | |
414 | char buf[PRINT_DEC_BUFSZ]; | |
415 | int negative = 0; | |
416 | int zero = (value == 0); | |
417 | int len = print_hex_ll(buf, (int)sizeof(buf), value); | |
418 | print_num(at, left, ret, minw, precision, prgiven, zeropad, minus, | |
419 | plus, space, zero, negative, buf, len); | |
420 | } | |
421 | ||
422 | /** print %llp */ | |
423 | static void | |
424 | print_num_llp(char** at, size_t* left, int* ret, void* value, | |
425 | int minw, int precision, int prgiven, int zeropad, int minus, | |
426 | int plus, int space) | |
427 | { | |
428 | char buf[PRINT_DEC_BUFSZ]; | |
429 | int negative = 0; | |
430 | int zero = (value == 0); | |
431 | #if defined(UINTPTR_MAX) && defined(UINT32_MAX) && (UINTPTR_MAX == UINT32_MAX) | |
432 | /* avoid warning about upcast on 32bit systems */ | |
433 | unsigned long long llvalue = (unsigned long)value; | |
434 | #else | |
435 | unsigned long long llvalue = (unsigned long long)value; | |
436 | #endif | |
437 | int len = print_hex_ll(buf, (int)sizeof(buf), llvalue); | |
438 | if(zero) { | |
439 | buf[0]=')'; | |
440 | buf[1]='l'; | |
441 | buf[2]='i'; | |
442 | buf[3]='n'; | |
443 | buf[4]='('; | |
444 | len = 5; | |
445 | } else { | |
446 | /* put '0x' in front of the (reversed) buffer result */ | |
447 | if(len < PRINT_DEC_BUFSZ) | |
448 | buf[len++] = 'x'; | |
449 | if(len < PRINT_DEC_BUFSZ) | |
450 | buf[len++] = '0'; | |
451 | } | |
452 | print_num(at, left, ret, minw, precision, prgiven, zeropad, minus, | |
453 | plus, space, zero, negative, buf, len); | |
454 | } | |
455 | ||
456 | #define PRINT_FLOAT_BUFSZ 64 /* xx.yy with 20.20 about the max */ | |
457 | /** spool remainder after the decimal point to buffer, in reverse */ | |
458 | static int | |
459 | print_remainder(char* buf, int max, double r, int prec) | |
460 | { | |
461 | unsigned long long cap = 1; | |
462 | unsigned long long value; | |
463 | int len, i; | |
464 | if(prec > 19) prec = 19; /* max we can do */ | |
465 | if(max < prec) return 0; | |
466 | for(i=0; i<prec; i++) { | |
467 | cap *= 10; | |
468 | } | |
469 | r *= (double)cap; | |
470 | value = (unsigned long long)r; | |
471 | /* see if we need to round up */ | |
472 | if(((unsigned long long)((r - (double)value)*10.0)) >= 5) { | |
473 | value++; | |
474 | /* that might carry to numbers before the comma, if so, | |
475 | * just ignore that rounding. failure because 64bitprintout */ | |
476 | if(value >= cap) | |
477 | value = cap-1; | |
478 | } | |
479 | len = print_dec_ll(buf, max, value); | |
480 | while(len < prec) { /* pad with zeroes, e.g. if 0.0012 */ | |
481 | buf[len++] = '0'; | |
482 | } | |
483 | if(len < max) | |
484 | buf[len++] = '.'; | |
485 | return len; | |
486 | } | |
487 | ||
488 | /** spool floating point to buffer */ | |
489 | static int | |
490 | print_float(char* buf, int max, double value, int prec) | |
491 | { | |
492 | /* as xxx.xxx if prec==0, no '.', with prec decimals after . */ | |
493 | /* no conversion for NAN and INF, because we do not want to require | |
494 | linking with -lm. */ | |
495 | /* Thus, the conversions use 64bit integers to convert the numbers, | |
496 | * which makes 19 digits before and after the decimal point the max */ | |
497 | unsigned long long whole = (unsigned long long)value; | |
498 | double remain = value - (double)whole; | |
499 | int len = 0; | |
500 | if(prec != 0) | |
501 | len = print_remainder(buf, max, remain, prec); | |
502 | len += print_dec_ll(buf+len, max-len, whole); | |
503 | return len; | |
504 | } | |
505 | ||
506 | /** print %f */ | |
507 | static void | |
508 | print_num_f(char** at, size_t* left, int* ret, double value, | |
509 | int minw, int precision, int prgiven, int zeropad, int minus, | |
510 | int plus, int space) | |
511 | { | |
512 | char buf[PRINT_FLOAT_BUFSZ]; | |
513 | int negative = (value < 0); | |
514 | int zero = 0; | |
515 | int len; | |
516 | if(!prgiven) precision = 6; | |
517 | len = print_float(buf, (int)sizeof(buf), negative?-value:value, | |
518 | precision); | |
519 | print_num(at, left, ret, minw, 1, 0, zeropad, minus, | |
520 | plus, space, zero, negative, buf, len); | |
521 | } | |
522 | ||
523 | /* rudimentary %g support */ | |
524 | static int | |
525 | print_float_g(char* buf, int max, double value, int prec) | |
526 | { | |
527 | unsigned long long whole = (unsigned long long)value; | |
528 | double remain = value - (double)whole; | |
529 | int before = 0; | |
530 | int len = 0; | |
531 | ||
532 | /* number of digits before the decimal point */ | |
533 | while(whole > 0) { | |
534 | before++; | |
535 | whole /= 10; | |
536 | } | |
537 | whole = (unsigned long long)value; | |
538 | ||
539 | if(prec > before && remain != 0.0) { | |
540 | /* see if the last decimals are zero, if so, skip them */ | |
541 | len = print_remainder(buf, max, remain, prec-before); | |
542 | while(len > 0 && buf[0]=='0') { | |
543 | memmove(buf, buf+1, --len); | |
544 | } | |
545 | } | |
546 | len += print_dec_ll(buf+len, max-len, whole); | |
547 | return len; | |
548 | } | |
549 | ||
550 | ||
551 | /** print %g */ | |
552 | static void | |
553 | print_num_g(char** at, size_t* left, int* ret, double value, | |
554 | int minw, int precision, int prgiven, int zeropad, int minus, | |
555 | int plus, int space) | |
556 | { | |
557 | char buf[PRINT_FLOAT_BUFSZ]; | |
558 | int negative = (value < 0); | |
559 | int zero = 0; | |
560 | int len; | |
561 | if(!prgiven) precision = 6; | |
562 | if(precision == 0) precision = 1; | |
563 | len = print_float_g(buf, (int)sizeof(buf), negative?-value:value, | |
564 | precision); | |
565 | print_num(at, left, ret, minw, 1, 0, zeropad, minus, | |
566 | plus, space, zero, negative, buf, len); | |
567 | } | |
568 | ||
569 | ||
570 | /** strnlen (compat implementation) */ | |
571 | static int | |
572 | my_strnlen(const char* s, int max) | |
573 | { | |
574 | int i; | |
575 | for(i=0; i<max; i++) | |
576 | if(s[i]==0) | |
577 | return i; | |
578 | return max; | |
579 | } | |
580 | ||
581 | /** print %s */ | |
582 | static void | |
583 | print_str(char** at, size_t* left, int* ret, char* s, | |
584 | int minw, int precision, int prgiven, int minus) | |
585 | { | |
586 | int w; | |
587 | /* with prec: no more than x characters from this string, stop at 0 */ | |
588 | if(prgiven) | |
589 | w = my_strnlen(s, precision); | |
590 | else w = (int)strlen(s); /* up to the nul */ | |
591 | if(w < minw && !minus) | |
592 | print_pad(at, left, ret, ' ', minw - w); | |
593 | spool_str(at, left, ret, s, w); | |
594 | if(w < minw && minus) | |
595 | print_pad(at, left, ret, ' ', minw - w); | |
596 | } | |
597 | ||
598 | /** print %c */ | |
599 | static void | |
600 | print_char(char** at, size_t* left, int* ret, int c, | |
601 | int minw, int minus) | |
602 | { | |
603 | if(1 < minw && !minus) | |
604 | print_pad(at, left, ret, ' ', minw - 1); | |
605 | print_pad(at, left, ret, c, 1); | |
606 | if(1 < minw && minus) | |
607 | print_pad(at, left, ret, ' ', minw - 1); | |
608 | } | |
609 | ||
610 | ||
611 | /** | |
612 | * Print to string. | |
613 | * str: string buffer for result. result will be null terminated. | |
614 | * size: size of the buffer. null is put inside buffer. | |
615 | * format: printf format string. | |
616 | * arg: '...' arguments to print. | |
617 | * returns number of characters. a null is printed after this. | |
618 | * return number of bytes that would have been written | |
619 | * if the buffer had been large enough. | |
620 | * | |
621 | * supported format specifiers: | |
622 | * %s, %u, %d, %x, %i, %f, %g, %c, %p, %n. | |
623 | * length: l, ll (for d, u, x). | |
624 | * precision: 6.6d (for d, u, x) | |
625 | * %f, %g precisions, 0.3f | |
626 | * %20s, '.*s' | |
627 | * and %%. | |
628 | */ | |
629 | int vsnprintf(char* str, size_t size, const char* format, va_list arg) | |
630 | { | |
631 | char* at = str; | |
632 | size_t left = size; | |
633 | int ret = 0; | |
634 | const char* fmt = format; | |
635 | int conv, minw, precision, prgiven, zeropad, minus, plus, space, length; | |
636 | while(*fmt) { | |
637 | /* copy string before % */ | |
638 | while(*fmt && *fmt!='%') { | |
639 | if(left > 1) { | |
640 | *at++ = *fmt++; | |
641 | left--; | |
642 | } else fmt++; | |
643 | ret++; | |
644 | } | |
645 | ||
646 | /* see if we are at end */ | |
647 | if(!*fmt) break; | |
648 | ||
649 | /* fetch next argument % designation from format string */ | |
650 | fmt++; /* skip the '%' */ | |
651 | ||
652 | /********************************/ | |
653 | /* get the argument designation */ | |
654 | /********************************/ | |
655 | /* we must do this vararg stuff inside this function for | |
656 | * portability. Hence, get_designation, and print_designation | |
657 | * are not their own functions. */ | |
658 | ||
659 | /* printout designation: | |
660 | * conversion specifier: x, d, u, s, c, n, m, p | |
661 | * flags: # not supported | |
662 | * 0 zeropad (on the left) | |
663 | * - left adjust (right by default) | |
664 | * ' ' printspace for positive number (in - position). | |
665 | * + alwayssign | |
666 | * fieldwidth: [1-9][0-9]* minimum field width. | |
667 | * if this is * then type int next argument specifies the minwidth. | |
668 | * if this is negative, the - flag is set (with positive width). | |
669 | * precision: period[digits]*, %.2x. | |
670 | * if this is * then type int next argument specifies the precision. | |
671 | * just '.' or negative value means precision=0. | |
672 | * this is mindigits to print for d, i, u, x | |
673 | * this is aftercomma digits for f | |
674 | * this is max number significant digits for g | |
675 | * maxnumber characters to be printed for s | |
676 | * length: 0-none (int), 1-l (long), 2-ll (long long) | |
677 | * notsupported: hh (char), h (short), L (long double), q, j, z, t | |
678 | * Does not support %m$ and *m$ argument designation as array indices. | |
679 | * Does not support %#x | |
680 | * | |
681 | */ | |
682 | minw = 0; | |
683 | precision = 1; | |
684 | prgiven = 0; | |
685 | zeropad = 0; | |
686 | minus = 0; | |
687 | plus = 0; | |
688 | space = 0; | |
689 | length = 0; | |
690 | ||
691 | /* get flags in any order */ | |
692 | for(;;) { | |
693 | if(*fmt == '0') | |
694 | zeropad = 1; | |
695 | else if(*fmt == '-') | |
696 | minus = 1; | |
697 | else if(*fmt == '+') | |
698 | plus = 1; | |
699 | else if(*fmt == ' ') | |
700 | space = 1; | |
701 | else break; | |
702 | fmt++; | |
703 | } | |
704 | ||
705 | /* field width */ | |
706 | if(*fmt == '*') { | |
707 | fmt++; /* skip char */ | |
708 | minw = va_arg(arg, int); | |
709 | if(minw < 0) { | |
710 | minus = 1; | |
711 | minw = -minw; | |
712 | } | |
713 | } else while(*fmt >= '0' && *fmt <= '9') { | |
714 | minw = minw*10 + (*fmt++)-'0'; | |
715 | } | |
716 | ||
717 | /* precision */ | |
718 | if(*fmt == '.') { | |
719 | fmt++; /* skip period */ | |
720 | prgiven = 1; | |
721 | precision = 0; | |
722 | if(*fmt == '*') { | |
723 | fmt++; /* skip char */ | |
724 | precision = va_arg(arg, int); | |
725 | if(precision < 0) | |
726 | precision = 0; | |
727 | } else while(*fmt >= '0' && *fmt <= '9') { | |
728 | precision = precision*10 + (*fmt++)-'0'; | |
729 | } | |
730 | } | |
731 | ||
732 | /* length */ | |
733 | if(*fmt == 'l') { | |
734 | fmt++; /* skip char */ | |
735 | length = 1; | |
736 | if(*fmt == 'l') { | |
737 | fmt++; /* skip char */ | |
738 | length = 2; | |
739 | } | |
740 | } | |
741 | ||
742 | /* get the conversion */ | |
743 | if(!*fmt) conv = 0; | |
744 | else conv = *fmt++; | |
745 | ||
746 | /***********************************/ | |
747 | /* print that argument designation */ | |
748 | /***********************************/ | |
749 | switch(conv) { | |
750 | case 'i': | |
751 | case 'd': | |
752 | if(length == 0) | |
753 | print_num_d(&at, &left, &ret, va_arg(arg, int), | |
754 | minw, precision, prgiven, zeropad, minus, plus, space); | |
755 | else if(length == 1) | |
756 | print_num_ld(&at, &left, &ret, va_arg(arg, long), | |
757 | minw, precision, prgiven, zeropad, minus, plus, space); | |
758 | else if(length == 2) | |
759 | print_num_lld(&at, &left, &ret, | |
760 | va_arg(arg, long long), | |
761 | minw, precision, prgiven, zeropad, minus, plus, space); | |
762 | break; | |
763 | case 'u': | |
764 | if(length == 0) | |
765 | print_num_u(&at, &left, &ret, | |
766 | va_arg(arg, unsigned int), | |
767 | minw, precision, prgiven, zeropad, minus, plus, space); | |
768 | else if(length == 1) | |
769 | print_num_lu(&at, &left, &ret, | |
770 | va_arg(arg, unsigned long), | |
771 | minw, precision, prgiven, zeropad, minus, plus, space); | |
772 | else if(length == 2) | |
773 | print_num_llu(&at, &left, &ret, | |
774 | va_arg(arg, unsigned long long), | |
775 | minw, precision, prgiven, zeropad, minus, plus, space); | |
776 | break; | |
777 | case 'x': | |
778 | if(length == 0) | |
779 | print_num_x(&at, &left, &ret, | |
780 | va_arg(arg, unsigned int), | |
781 | minw, precision, prgiven, zeropad, minus, plus, space); | |
782 | else if(length == 1) | |
783 | print_num_lx(&at, &left, &ret, | |
784 | va_arg(arg, unsigned long), | |
785 | minw, precision, prgiven, zeropad, minus, plus, space); | |
786 | else if(length == 2) | |
787 | print_num_llx(&at, &left, &ret, | |
788 | va_arg(arg, unsigned long long), | |
789 | minw, precision, prgiven, zeropad, minus, plus, space); | |
790 | break; | |
791 | case 's': | |
792 | print_str(&at, &left, &ret, va_arg(arg, char*), | |
793 | minw, precision, prgiven, minus); | |
794 | break; | |
795 | case 'c': | |
796 | print_char(&at, &left, &ret, va_arg(arg, int), | |
797 | minw, minus); | |
798 | break; | |
799 | case 'n': | |
800 | *va_arg(arg, int*) = ret; | |
801 | break; | |
802 | case 'm': | |
803 | print_str(&at, &left, &ret, strerror(errno), | |
804 | minw, precision, prgiven, minus); | |
805 | break; | |
806 | case 'p': | |
807 | print_num_llp(&at, &left, &ret, va_arg(arg, void*), | |
808 | minw, precision, prgiven, zeropad, minus, plus, space); | |
809 | break; | |
810 | case '%': | |
811 | print_pad(&at, &left, &ret, '%', 1); | |
812 | break; | |
813 | case 'f': | |
814 | print_num_f(&at, &left, &ret, va_arg(arg, double), | |
815 | minw, precision, prgiven, zeropad, minus, plus, space); | |
816 | break; | |
817 | case 'g': | |
818 | print_num_g(&at, &left, &ret, va_arg(arg, double), | |
819 | minw, precision, prgiven, zeropad, minus, plus, space); | |
820 | break; | |
821 | /* unknown */ | |
822 | default: | |
823 | case 0: break; | |
824 | } | |
825 | } | |
826 | ||
827 | /* zero terminate */ | |
828 | if(left > 0) | |
829 | *at = 0; | |
830 | return ret; | |
831 | } | |
832 | ||
833 | #ifdef SNPRINTF_TEST | |
834 | ||
835 | /** do tests */ | |
836 | #undef snprintf | |
837 | #define DOTEST(bufsz, result, retval, ...) do { \ | |
838 | char buf[bufsz]; \ | |
839 | printf("now test %s\n", #__VA_ARGS__); \ | |
840 | int r=my_snprintf(buf, sizeof(buf), __VA_ARGS__); \ | |
841 | if(r != retval || strcmp(buf, result) != 0) { \ | |
842 | printf("error test(%s) was \"%s\":%d\n", \ | |
843 | ""#bufsz", "#result", "#retval", "#__VA_ARGS__, \ | |
844 | buf, r); \ | |
845 | exit(1); \ | |
846 | } \ | |
847 | r=snprintf(buf, sizeof(buf), __VA_ARGS__); \ | |
848 | if(r != retval || strcmp(buf, result) != 0) { \ | |
849 | printf("error test(%s) differs with system, \"%s\":%d\n", \ | |
850 | ""#bufsz", "#result", "#retval", "#__VA_ARGS__, \ | |
851 | buf, r); \ | |
852 | exit(1); \ | |
853 | } \ | |
854 | printf("test(\"%s\":%d) passed\n", buf, r); \ | |
855 | } while(0); | |
856 | ||
857 | /** test program */ | |
858 | int main(void) | |
859 | { | |
860 | int x = 0; | |
861 | ||
862 | /* bufsize, expectedstring, expectedretval, snprintf arguments */ | |
863 | DOTEST(1024, "hello", 5, "hello"); | |
864 | DOTEST(1024, "h", 1, "h"); | |
865 | /* warning from gcc for format string, but it does work | |
866 | * DOTEST(1024, "", 0, ""); */ | |
867 | ||
868 | DOTEST(3, "he", 5, "hello"); | |
869 | DOTEST(1, "", 7, "%d", 7823089); | |
870 | ||
871 | /* test positive numbers */ | |
872 | DOTEST(1024, "0", 1, "%d", 0); | |
873 | DOTEST(1024, "1", 1, "%d", 1); | |
874 | DOTEST(1024, "9", 1, "%d", 9); | |
875 | DOTEST(1024, "15", 2, "%d", 15); | |
876 | DOTEST(1024, "ab15cd", 6, "ab%dcd", 15); | |
877 | DOTEST(1024, "167", 3, "%d", 167); | |
878 | DOTEST(1024, "7823089", 7, "%d", 7823089); | |
879 | DOTEST(1024, " 12", 3, "%3d", 12); | |
880 | DOTEST(1024, "012", 3, "%.3d", 12); | |
881 | DOTEST(1024, "012", 3, "%3.3d", 12); | |
882 | DOTEST(1024, "012", 3, "%03d", 12); | |
883 | DOTEST(1024, " 012", 4, "%4.3d", 12); | |
884 | DOTEST(1024, "", 0, "%.0d", 0); | |
885 | ||
886 | /* test negative numbers */ | |
887 | DOTEST(1024, "-1", 2, "%d", -1); | |
888 | DOTEST(1024, "-12", 3, "%3d", -12); | |
889 | DOTEST(1024, " -2", 3, "%3d", -2); | |
890 | DOTEST(1024, "-012", 4, "%.3d", -12); | |
891 | DOTEST(1024, "-012", 4, "%3.3d", -12); | |
892 | DOTEST(1024, "-012", 4, "%4.3d", -12); | |
893 | DOTEST(1024, " -012", 5, "%5.3d", -12); | |
894 | DOTEST(1024, "-12", 3, "%03d", -12); | |
895 | DOTEST(1024, "-02", 3, "%03d", -2); | |
896 | DOTEST(1024, "-15", 3, "%d", -15); | |
897 | DOTEST(1024, "-7307", 5, "%d", -7307); | |
898 | DOTEST(1024, "-12 ", 5, "%-5d", -12); | |
899 | DOTEST(1024, "-00012", 6, "%-.5d", -12); | |
900 | ||
901 | /* test + and space flags */ | |
902 | DOTEST(1024, "+12", 3, "%+d", 12); | |
903 | DOTEST(1024, " 12", 3, "% d", 12); | |
904 | ||
905 | /* test %u */ | |
906 | DOTEST(1024, "12", 2, "%u", 12); | |
907 | DOTEST(1024, "0", 1, "%u", 0); | |
908 | DOTEST(1024, "4294967295", 10, "%u", 0xffffffff); | |
909 | ||
910 | /* test %x */ | |
911 | DOTEST(1024, "0", 1, "%x", 0); | |
912 | DOTEST(1024, "c", 1, "%x", 12); | |
913 | DOTEST(1024, "12ab34cd", 8, "%x", 0x12ab34cd); | |
914 | ||
915 | /* test %llu, %lld */ | |
916 | DOTEST(1024, "18446744073709551615", 20, "%llu", | |
917 | (long long)0xffffffffffffffff); | |
918 | DOTEST(1024, "-9223372036854775808", 20, "%lld", | |
919 | (long long)0x8000000000000000); | |
920 | DOTEST(1024, "9223372036854775808", 19, "%llu", | |
921 | (long long)0x8000000000000000); | |
922 | ||
923 | /* test %s */ | |
924 | DOTEST(1024, "hello", 5, "%s", "hello"); | |
925 | DOTEST(1024, " hello", 10, "%10s", "hello"); | |
926 | DOTEST(1024, "hello ", 10, "%-10s", "hello"); | |
927 | DOTEST(1024, "he", 2, "%.2s", "hello"); | |
928 | DOTEST(1024, " he", 4, "%4.2s", "hello"); | |
929 | DOTEST(1024, " h", 4, "%4.2s", "h"); | |
930 | ||
931 | /* test %c */ | |
932 | DOTEST(1024, "a", 1, "%c", 'a'); | |
933 | /* warning from gcc for format string, but it does work | |
934 | DOTEST(1024, " a", 5, "%5c", 'a'); | |
935 | DOTEST(1024, "a", 1, "%.0c", 'a'); */ | |
936 | ||
937 | /* test %n */ | |
938 | DOTEST(1024, "hello", 5, "hello%n", &x); | |
939 | if(x != 5) { printf("the %%n failed\n"); exit(1); } | |
940 | ||
941 | /* test %m */ | |
942 | errno = 0; | |
943 | DOTEST(1024, "Success", 7, "%m"); | |
944 | ||
945 | /* test %p */ | |
946 | DOTEST(1024, "0x10", 4, "%p", (void*)0x10); | |
947 | DOTEST(1024, "(nil)", 5, "%p", (void*)0x0); | |
948 | ||
949 | /* test %% */ | |
950 | DOTEST(1024, "%", 1, "%%"); | |
951 | ||
952 | /* test %f */ | |
953 | DOTEST(1024, "0.000000", 8, "%f", 0.0); | |
954 | DOTEST(1024, "0.00", 4, "%.2f", 0.0); | |
955 | /* differs, "-0.00" DOTEST(1024, "0.00", 4, "%.2f", -0.0); */ | |
956 | DOTEST(1024, "234.00", 6, "%.2f", 234.005); | |
957 | DOTEST(1024, "8973497.1246", 12, "%.4f", 8973497.12456); | |
958 | DOTEST(1024, "-12.000000", 10, "%f", -12.0); | |
959 | DOTEST(1024, "6", 1, "%.0f", 6.0); | |
960 | ||
961 | DOTEST(1024, "6", 1, "%g", 6.0); | |
962 | DOTEST(1024, "6.1", 3, "%g", 6.1); | |
963 | DOTEST(1024, "6.15", 4, "%g", 6.15); | |
964 | ||
965 | /* These format strings are from the code of NSD, Unbound, ldns */ | |
966 | ||
967 | DOTEST(1024, "abcdef", 6, "%s", "abcdef"); | |
968 | DOTEST(1024, "005", 3, "%03u", 5); | |
969 | DOTEST(1024, "12345", 5, "%03u", 12345); | |
970 | DOTEST(1024, "5", 1, "%d", 5); | |
971 | DOTEST(1024, "(nil)", 5, "%p", NULL); | |
972 | DOTEST(1024, "12345", 5, "%ld", (long)12345); | |
973 | DOTEST(1024, "12345", 5, "%lu", (long)12345); | |
974 | DOTEST(1024, " 12345", 12, "%12u", (unsigned)12345); | |
975 | DOTEST(1024, "12345", 5, "%u", (unsigned)12345); | |
976 | DOTEST(1024, "12345", 5, "%llu", (unsigned long long)12345); | |
977 | DOTEST(1024, "12345", 5, "%x", 0x12345); | |
978 | DOTEST(1024, "12345", 5, "%llx", (long long)0x12345); | |
979 | DOTEST(1024, "012345", 6, "%6.6d", 12345); | |
980 | DOTEST(1024, "012345", 6, "%6.6u", 12345); | |
981 | DOTEST(1024, "1234.54", 7, "%g", 1234.54); | |
982 | DOTEST(1024, "123456789.54", 12, "%.12g", 123456789.54); | |
983 | DOTEST(1024, "3456789123456.54", 16, "%.16g", 3456789123456.54); | |
984 | /* %24g does not work with 24 digits, not enough accuracy, | |
985 | * the first 16 digits are correct */ | |
986 | DOTEST(1024, "12345", 5, "%3.3d", 12345); | |
987 | DOTEST(1024, "000", 3, "%3.3d", 0); | |
988 | DOTEST(1024, "001", 3, "%3.3d", 1); | |
989 | DOTEST(1024, "012", 3, "%3.3d", 12); | |
990 | DOTEST(1024, "-012", 4, "%3.3d", -12); | |
991 | DOTEST(1024, "he", 2, "%.2s", "hello"); | |
992 | DOTEST(1024, "helloworld", 10, "%s%s", "hello", "world"); | |
993 | DOTEST(1024, "he", 2, "%.*s", 2, "hello"); | |
994 | DOTEST(1024, " hello", 7, "%*s", 7, "hello"); | |
995 | DOTEST(1024, "hello ", 7, "%*s", -7, "hello"); | |
996 | DOTEST(1024, "0", 1, "%c", '0'); | |
997 | DOTEST(1024, "A", 1, "%c", 'A'); | |
998 | DOTEST(1024, "", 1, "%c", 0); | |
999 | DOTEST(1024, "\010", 1, "%c", 8); | |
1000 | DOTEST(1024, "%", 1, "%%"); | |
1001 | DOTEST(1024, "0a", 2, "%02x", 0x0a); | |
1002 | DOTEST(1024, "bd", 2, "%02x", 0xbd); | |
1003 | DOTEST(1024, "12", 2, "%02ld", (long)12); | |
1004 | DOTEST(1024, "02", 2, "%02ld", (long)2); | |
1005 | DOTEST(1024, "02", 2, "%02u", (unsigned)2); | |
1006 | DOTEST(1024, "765432", 6, "%05u", (unsigned)765432); | |
1007 | DOTEST(1024, "10.234", 6, "%0.3f", 10.23421); | |
1008 | DOTEST(1024, "123456.234", 10, "%0.3f", 123456.23421); | |
1009 | DOTEST(1024, "123456789.234", 13, "%0.3f", 123456789.23421); | |
1010 | DOTEST(1024, "123456.23", 9, "%.2f", 123456.23421); | |
1011 | DOTEST(1024, "123456", 6, "%.0f", 123456.23421); | |
1012 | DOTEST(1024, "0123", 4, "%.4x", 0x0123); | |
1013 | DOTEST(1024, "00000123", 8, "%.8x", 0x0123); | |
1014 | DOTEST(1024, "ffeb0cde", 8, "%.8x", 0xffeb0cde); | |
1015 | DOTEST(1024, " 987654321", 10, "%10lu", (unsigned long)987654321); | |
1016 | DOTEST(1024, " 987654321", 12, "%12lu", (unsigned long)987654321); | |
1017 | DOTEST(1024, "987654321", 9, "%i", 987654321); | |
1018 | DOTEST(1024, "-87654321", 9, "%i", -87654321); | |
1019 | DOTEST(1024, "hello ", 16, "%-16s", "hello"); | |
1020 | DOTEST(1024, " ", 16, "%-16s", ""); | |
1021 | DOTEST(1024, "a ", 16, "%-16s", "a"); | |
1022 | DOTEST(1024, "foobarfoobar ", 16, "%-16s", "foobarfoobar"); | |
1023 | DOTEST(1024, "foobarfoobarfoobar", 18, "%-16s", "foobarfoobarfoobar"); | |
1024 | ||
1025 | /* combined expressions */ | |
1026 | DOTEST(1024, "foo 1.0 size 512 edns", 21, | |
1027 | "foo %s size %d %s%s", "1.0", 512, "", "edns"); | |
1028 | DOTEST(15, "foo 1.0 size 5", 21, | |
1029 | "foo %s size %d %s%s", "1.0", 512, "", "edns"); | |
1030 | DOTEST(1024, "packet 1203ceff id", 18, | |
1031 | "packet %2.2x%2.2x%2.2x%2.2x id", 0x12, 0x03, 0xce, 0xff); | |
1032 | DOTEST(1024, "/tmp/testbound_123abcd.tmp", 26, "/tmp/testbound_%u%s%s.tmp", 123, "ab", "cd"); | |
1033 | ||
1034 | return 0; | |
1035 | } | |
1036 | #endif /* SNPRINTF_TEST */ |