/src/njs/src/njs_sprintf.c
Line | Count | Source |
1 | | |
2 | | /* |
3 | | * Copyright (C) Igor Sysoev |
4 | | * Copyright (C) NGINX, Inc. |
5 | | */ |
6 | | |
7 | | |
8 | | #include <njs_main.h> |
9 | | |
10 | | |
11 | | /* |
12 | | * Supported formats: |
13 | | * |
14 | | * %[0][width][x][X]O njs_off_t |
15 | | * %[0][width]T njs_time_t |
16 | | * %[0][width][u][x|X]z ssize_t/size_t |
17 | | * %[0][width][u][x|X]d int/u_int |
18 | | * %[0][width][u][x|X]l long |
19 | | * %[0][width|m][u][x|X]i njs_int_t/njs_uint_t |
20 | | * %[0][width][u][x|X]D int32_t/uint32_t |
21 | | * %[0][width][u][x|X]L int64_t/uint64_t |
22 | | * %[0][width][.width]f double, max valid number fits to %18.15f |
23 | | * |
24 | | * %d int |
25 | | * |
26 | | * %s null-terminated string |
27 | | * %*s length and string |
28 | | * |
29 | | * %p void * |
30 | | * %P symbolized function address |
31 | | * %b njs_bool_t |
32 | | * %V njs_str_t * |
33 | | * %Z '\0' |
34 | | * %n '\n' |
35 | | * %c char |
36 | | * %% % |
37 | | * |
38 | | * Reserved: |
39 | | * %t ptrdiff_t |
40 | | * %S null-terminated wchar string |
41 | | * %C wchar |
42 | | * %[0][width][u][x|X]Q int128_t/uint128_t |
43 | | */ |
44 | | |
45 | | |
46 | | u_char * |
47 | | njs_sprintf(u_char *buf, u_char *end, const char *fmt, ...) |
48 | 65.3k | { |
49 | 65.3k | u_char *p; |
50 | 65.3k | va_list args; |
51 | | |
52 | 65.3k | va_start(args, fmt); |
53 | 65.3k | p = njs_vsprintf(buf, end, fmt, args); |
54 | 65.3k | va_end(args); |
55 | | |
56 | 65.3k | return p; |
57 | 65.3k | } |
58 | | |
59 | | |
60 | | /* |
61 | | * njs_sprintf_t is used: |
62 | | * to pass several parameters of njs_integer() via single pointer |
63 | | * and to store little used variables of njs_vsprintf(). |
64 | | */ |
65 | | |
66 | | typedef struct { |
67 | | u_char *end; |
68 | | const u_char *hex; |
69 | | uint32_t width; |
70 | | int32_t frac_width; |
71 | | uint8_t max_width; |
72 | | u_char padding; |
73 | | } njs_sprintf_t; |
74 | | |
75 | | |
76 | | static u_char *njs_integer(njs_sprintf_t *spf, u_char *buf, uint64_t ui64); |
77 | | static u_char *njs_float(njs_sprintf_t *spf, u_char *buf, double n); |
78 | | |
79 | | |
80 | | /* A right way of "f == 0.0". */ |
81 | 0 | #define njs_double_is_zero(f) (fabs(f) <= FLT_EPSILON) |
82 | | |
83 | | |
84 | | u_char * |
85 | | njs_vsprintf(u_char *buf, u_char *end, const char *fmt, va_list args) |
86 | 527k | { |
87 | 527k | u_char *p; |
88 | 527k | int d; |
89 | 527k | double f, i; |
90 | 527k | size_t size, length; |
91 | 527k | int64_t i64; |
92 | 527k | uint64_t ui64, frac; |
93 | 527k | njs_str_t *v; |
94 | 527k | njs_uint_t scale, n; |
95 | 527k | njs_bool_t sign; |
96 | 527k | njs_sprintf_t spf; |
97 | | |
98 | 527k | static const u_char hexadecimal[] = "0123456789abcdef"; |
99 | 527k | static const u_char HEXADECIMAL[] = "0123456789ABCDEF"; |
100 | 527k | static const u_char nan[] = "[nan]"; |
101 | 527k | static const u_char infinity[] = "[infinity]"; |
102 | | |
103 | 527k | spf.end = end; |
104 | | |
105 | 10.8M | while (*fmt != '\0' && buf < end) { |
106 | | |
107 | | /* |
108 | | * "buf < end" means that we could copy at least one character: |
109 | | * a plain character, "%%", "%c", or a minus without test. |
110 | | */ |
111 | | |
112 | 10.3M | if (*fmt != '%') { |
113 | 9.58M | *buf++ = *fmt++; |
114 | 9.58M | continue; |
115 | 9.58M | } |
116 | | |
117 | 751k | fmt++; |
118 | | |
119 | | /* Test some often used text formats first. */ |
120 | | |
121 | 751k | switch (*fmt) { |
122 | | |
123 | 101k | case 'V': |
124 | 101k | fmt++; |
125 | 101k | v = va_arg(args, njs_str_t *); |
126 | | |
127 | 101k | if (njs_fast_path(v != NULL)) { |
128 | 101k | length = v->length; |
129 | 101k | p = v->start; |
130 | 101k | goto copy; |
131 | 101k | } |
132 | | |
133 | 0 | continue; |
134 | | |
135 | 420k | case 's': |
136 | 420k | p = va_arg(args, u_char *); |
137 | | |
138 | 420k | if (njs_fast_path(p != NULL)) { |
139 | 11.8M | while (*p != '\0' && buf < end) { |
140 | 11.3M | *buf++ = *p++; |
141 | 11.3M | } |
142 | 420k | } |
143 | | |
144 | 420k | fmt++; |
145 | 420k | continue; |
146 | | |
147 | 52.3k | case '*': |
148 | 52.3k | length = va_arg(args, size_t); |
149 | | |
150 | 52.3k | fmt++; |
151 | | |
152 | 52.3k | if (*fmt == 's') { |
153 | 52.3k | fmt++; |
154 | 52.3k | p = va_arg(args, u_char *); |
155 | | |
156 | 52.3k | if (njs_fast_path(p != NULL)) { |
157 | 52.3k | goto copy; |
158 | 52.3k | } |
159 | 52.3k | } |
160 | | |
161 | 0 | continue; |
162 | | |
163 | 177k | default: |
164 | 177k | break; |
165 | 751k | } |
166 | | |
167 | 177k | spf.hex = NULL; |
168 | 177k | spf.width = 0; |
169 | 177k | spf.frac_width = -1; |
170 | 177k | spf.max_width = 0; |
171 | 177k | spf.padding = (*fmt == '0') ? '0' : ' '; |
172 | | |
173 | 177k | sign = 1; |
174 | | |
175 | 177k | i64 = 0; |
176 | 177k | ui64 = 0; |
177 | | |
178 | 436k | while (*fmt >= '0' && *fmt <= '9') { |
179 | 259k | spf.width = spf.width * 10 + (*fmt++ - '0'); |
180 | 259k | } |
181 | | |
182 | | |
183 | 205k | for ( ;; ) { |
184 | 205k | switch (*fmt) { |
185 | | |
186 | 28.3k | case 'u': |
187 | 28.3k | sign = 0; |
188 | 28.3k | fmt++; |
189 | 28.3k | continue; |
190 | | |
191 | 0 | case 'm': |
192 | 0 | spf.max_width = 1; |
193 | 0 | fmt++; |
194 | 0 | continue; |
195 | | |
196 | 0 | case 'X': |
197 | 0 | spf.hex = HEXADECIMAL; |
198 | 0 | sign = 0; |
199 | 0 | fmt++; |
200 | 0 | continue; |
201 | | |
202 | 0 | case 'x': |
203 | 0 | spf.hex = hexadecimal; |
204 | 0 | sign = 0; |
205 | 0 | fmt++; |
206 | 0 | continue; |
207 | | |
208 | 0 | case '.': |
209 | 0 | fmt++; |
210 | 0 | spf.frac_width = 0; |
211 | |
|
212 | 0 | while (*fmt >= '0' && *fmt <= '9') { |
213 | 0 | spf.frac_width = spf.frac_width * 10 + *fmt++ - '0'; |
214 | 0 | } |
215 | |
|
216 | 0 | break; |
217 | | |
218 | 177k | default: |
219 | 177k | break; |
220 | 205k | } |
221 | | |
222 | 177k | break; |
223 | 205k | } |
224 | | |
225 | | |
226 | 177k | switch (*fmt) { |
227 | | |
228 | 0 | case 'O': |
229 | 0 | i64 = (int64_t) va_arg(args, njs_off_t); |
230 | 0 | sign = 1; |
231 | 0 | goto number; |
232 | | |
233 | 0 | case 'T': |
234 | 0 | i64 = (int64_t) va_arg(args, njs_time_t); |
235 | 0 | sign = 1; |
236 | 0 | goto number; |
237 | | |
238 | 525 | case 'z': |
239 | 525 | if (sign) { |
240 | 525 | i64 = (int64_t) va_arg(args, ssize_t); |
241 | 525 | } else { |
242 | 0 | ui64 = (uint64_t) va_arg(args, size_t); |
243 | 0 | } |
244 | 525 | goto number; |
245 | | |
246 | 0 | case 'i': |
247 | 0 | if (sign) { |
248 | 0 | i64 = (int64_t) va_arg(args, njs_int_t); |
249 | 0 | } else { |
250 | 0 | ui64 = (uint64_t) va_arg(args, njs_uint_t); |
251 | 0 | } |
252 | |
|
253 | 0 | if (spf.max_width != 0) { |
254 | 0 | spf.width = NJS_INT_T_LEN; |
255 | 0 | } |
256 | |
|
257 | 0 | goto number; |
258 | | |
259 | 34.2k | case 'd': |
260 | 34.2k | if (sign) { |
261 | 34.2k | i64 = (int64_t) va_arg(args, int); |
262 | 34.2k | } else { |
263 | 0 | ui64 = (uint64_t) va_arg(args, u_int); |
264 | 0 | } |
265 | 34.2k | goto number; |
266 | | |
267 | 0 | case 'l': |
268 | 0 | if (sign) { |
269 | 0 | i64 = (int64_t) va_arg(args, long); |
270 | 0 | } else { |
271 | 0 | ui64 = (uint64_t) va_arg(args, u_long); |
272 | 0 | } |
273 | 0 | goto number; |
274 | | |
275 | 28.3k | case 'D': |
276 | 28.3k | if (sign) { |
277 | 0 | i64 = (int64_t) va_arg(args, int32_t); |
278 | 28.3k | } else { |
279 | 28.3k | ui64 = (uint64_t) va_arg(args, uint32_t); |
280 | 28.3k | } |
281 | 28.3k | goto number; |
282 | | |
283 | 98.3k | case 'L': |
284 | 98.3k | if (sign) { |
285 | 98.3k | i64 = va_arg(args, int64_t); |
286 | 98.3k | } else { |
287 | 0 | ui64 = va_arg(args, uint64_t); |
288 | 0 | } |
289 | 98.3k | goto number; |
290 | | |
291 | 0 | case 'b': |
292 | 0 | ui64 = (uint64_t) va_arg(args, njs_bool_t); |
293 | 0 | sign = 0; |
294 | 0 | goto number; |
295 | | |
296 | 0 | case 'f': |
297 | 0 | fmt++; |
298 | |
|
299 | 0 | f = va_arg(args, double); |
300 | |
|
301 | 0 | if (f < 0) { |
302 | 0 | *buf++ = '-'; |
303 | 0 | f = -f; |
304 | 0 | } |
305 | |
|
306 | 0 | if (njs_slow_path(isnan(f))) { |
307 | 0 | p = (u_char *) nan; |
308 | 0 | length = njs_length(nan); |
309 | |
|
310 | 0 | goto copy; |
311 | |
|
312 | 0 | } else if (njs_slow_path(isinf(f))) { |
313 | 0 | p = (u_char *) infinity; |
314 | 0 | length = njs_length(infinity); |
315 | |
|
316 | 0 | goto copy; |
317 | 0 | } |
318 | | |
319 | 0 | (void) modf(f, &i); |
320 | 0 | frac = 0; |
321 | |
|
322 | 0 | if (spf.frac_width > 0) { |
323 | |
|
324 | 0 | scale = 1; |
325 | 0 | for (n = spf.frac_width; n != 0; n--) { |
326 | 0 | scale *= 10; |
327 | 0 | } |
328 | |
|
329 | 0 | frac = (uint64_t) ((f - i) * scale + 0.5); |
330 | |
|
331 | 0 | if (frac == scale) { |
332 | 0 | i += 1; |
333 | 0 | frac = 0; |
334 | 0 | } |
335 | 0 | } |
336 | |
|
337 | 0 | buf = njs_float(&spf, buf, i); |
338 | |
|
339 | 0 | if (spf.frac_width > 0) { |
340 | |
|
341 | 0 | if (buf < end) { |
342 | 0 | *buf++ = '.'; |
343 | |
|
344 | 0 | spf.hex = NULL; |
345 | 0 | spf.padding = '0'; |
346 | 0 | spf.width = spf.frac_width; |
347 | 0 | buf = njs_integer(&spf, buf, frac); |
348 | 0 | } |
349 | |
|
350 | 0 | } else if (spf.frac_width < 0) { |
351 | 0 | f = modf(f, &i); |
352 | |
|
353 | 0 | if (!njs_double_is_zero(f) && buf < end) { |
354 | 0 | *buf++ = '.'; |
355 | |
|
356 | 0 | while (!njs_double_is_zero(f) && buf < end) { |
357 | 0 | f *= 10; |
358 | 0 | f = modf(f, &i); |
359 | 0 | *buf++ = (u_char) i + '0'; |
360 | 0 | } |
361 | 0 | } |
362 | 0 | } |
363 | |
|
364 | 0 | continue; |
365 | | |
366 | 0 | case 'p': |
367 | 0 | ui64 = (uintptr_t) va_arg(args, void *); |
368 | 0 | sign = 0; |
369 | 0 | spf.hex = HEXADECIMAL; |
370 | | /* |
371 | | * spf.width = NJS_PTR_SIZE * 2; |
372 | | * spf.padding = '0'; |
373 | | */ |
374 | 0 | goto number; |
375 | | |
376 | 0 | case 'P': |
377 | 0 | buf = njs_addr2line(buf, end, va_arg(args, void *)); |
378 | 0 | fmt++; |
379 | 0 | continue; |
380 | | |
381 | 15.6k | case 'c': |
382 | 15.6k | d = va_arg(args, int); |
383 | 15.6k | *buf++ = (u_char) (d & 0xFF); |
384 | 15.6k | fmt++; |
385 | | |
386 | 15.6k | continue; |
387 | | |
388 | 0 | case 'Z': |
389 | 0 | *buf++ = '\0'; |
390 | 0 | fmt++; |
391 | 0 | continue; |
392 | | |
393 | 0 | case 'n': |
394 | 0 | *buf++ = '\n'; |
395 | 0 | fmt++; |
396 | 0 | continue; |
397 | | |
398 | 0 | case '%': |
399 | 0 | *buf++ = '%'; |
400 | 0 | fmt++; |
401 | 0 | continue; |
402 | | |
403 | 0 | default: |
404 | 0 | *buf++ = *fmt++; |
405 | 0 | continue; |
406 | 177k | } |
407 | | |
408 | 161k | number: |
409 | | |
410 | 161k | if (sign) { |
411 | 133k | if (i64 < 0) { |
412 | 127 | *buf++ = '-'; |
413 | 127 | ui64 = (uint64_t) -i64; |
414 | | |
415 | 132k | } else { |
416 | 132k | ui64 = (uint64_t) i64; |
417 | 132k | } |
418 | 133k | } |
419 | | |
420 | 161k | buf = njs_integer(&spf, buf, ui64); |
421 | | |
422 | 161k | fmt++; |
423 | 161k | continue; |
424 | | |
425 | 153k | copy: |
426 | | |
427 | 153k | size = njs_min((size_t) (end - buf), length); |
428 | | |
429 | 153k | if (size != 0) { |
430 | 151k | buf = njs_cpymem(buf, p, size); |
431 | 151k | } |
432 | | |
433 | 153k | continue; |
434 | 177k | } |
435 | | |
436 | 527k | return buf; |
437 | 527k | } |
438 | | |
439 | | |
440 | | static u_char * |
441 | | njs_integer(njs_sprintf_t *spf, u_char *buf, uint64_t ui64) |
442 | 161k | { |
443 | 161k | u_char *p, *end; |
444 | 161k | size_t length; |
445 | 161k | u_char temp[NJS_INT64_T_LEN]; |
446 | | |
447 | 161k | p = temp + NJS_INT64_T_LEN; |
448 | | |
449 | 161k | if (spf->hex == NULL) { |
450 | | |
451 | | #if (NJS_32BIT) |
452 | | |
453 | | for ( ;; ) { |
454 | | u_char *start; |
455 | | uint32_t ui32; |
456 | | |
457 | | /* |
458 | | * 32-bit platforms usually lack hardware support of 64-bit |
459 | | * division and remainder operations. For this reason C compiler |
460 | | * adds calls to the runtime library functions which provides |
461 | | * these operations. These functions usually have about hundred |
462 | | * lines of code. |
463 | | * |
464 | | * For 32-bit numbers and some constant divisors GCC, Clang and |
465 | | * other compilers can use inlined multiplications and shifts |
466 | | * which are faster than division or remainder operations. |
467 | | * For example, unsigned "ui32 / 10" is compiled to |
468 | | * |
469 | | * ((uint64_t) ui32 * 0xCCCCCCCD) >> 35 |
470 | | * |
471 | | * So a 64-bit number is split to parts by 10^9. The parts fit |
472 | | * to 32 bits and are processed separately as 32-bit numbers. A |
473 | | * number of 64-bit division/remainder operations is significantly |
474 | | * decreased depending on the 64-bit number's value, it is |
475 | | * 0 if the 64-bit value is less than 4294967296, |
476 | | * 1 if the 64-bit value is greater than 4294967295 |
477 | | * and less than 4294967296000000000, |
478 | | * 2 otherwise. |
479 | | */ |
480 | | |
481 | | if (ui64 <= 0xFFFFFFFF) { |
482 | | ui32 = (uint32_t) ui64; |
483 | | start = NULL; |
484 | | |
485 | | } else { |
486 | | ui32 = (uint32_t) (ui64 % 1000000000); |
487 | | start = p - 9; |
488 | | } |
489 | | |
490 | | do { |
491 | | *(--p) = (u_char) (ui32 % 10 + '0'); |
492 | | ui32 /= 10; |
493 | | } while (ui32 != 0); |
494 | | |
495 | | if (start == NULL) { |
496 | | break; |
497 | | } |
498 | | |
499 | | /* Add leading zeros of part. */ |
500 | | |
501 | | while (p > start) { |
502 | | *(--p) = '0'; |
503 | | } |
504 | | |
505 | | ui64 /= 1000000000; |
506 | | } |
507 | | |
508 | | #else /* NJS_64BIT */ |
509 | | |
510 | 223k | do { |
511 | 223k | *(--p) = (u_char) (ui64 % 10 + '0'); |
512 | 223k | ui64 /= 10; |
513 | 223k | } while (ui64 != 0); |
514 | | |
515 | 161k | #endif |
516 | | |
517 | 161k | } else { |
518 | |
|
519 | 0 | do { |
520 | 0 | *(--p) = spf->hex[ui64 & 0xF]; |
521 | 0 | ui64 >>= 4; |
522 | 0 | } while (ui64 != 0); |
523 | 0 | } |
524 | | |
525 | 161k | length = (temp + NJS_INT64_T_LEN) - p; |
526 | | |
527 | | /* Zero or space padding. */ |
528 | | |
529 | 161k | if (length < spf->width) { |
530 | 111k | end = buf + spf->width - length; |
531 | 111k | end = njs_min(end, spf->end); |
532 | | |
533 | 229k | while (buf < end) { |
534 | 118k | *buf++ = spf->padding; |
535 | 118k | } |
536 | 111k | } |
537 | | |
538 | | /* Number copying. */ |
539 | | |
540 | 161k | end = buf + length; |
541 | 161k | end = njs_min(end, spf->end); |
542 | | |
543 | 384k | while (buf < end) { |
544 | 223k | *buf++ = *p++; |
545 | 223k | } |
546 | | |
547 | 161k | return buf; |
548 | 161k | } |
549 | | |
550 | | |
551 | | static u_char * |
552 | | njs_float(njs_sprintf_t *spf, u_char *buf, double n) |
553 | 0 | { |
554 | 0 | u_char *p, *end; |
555 | 0 | size_t length; |
556 | 0 | u_char temp[NJS_DOUBLE_LEN]; |
557 | |
|
558 | 0 | p = temp + NJS_DOUBLE_LEN; |
559 | |
|
560 | 0 | do { |
561 | 0 | *(--p) = (u_char) (fmod(n, 10) + '0'); |
562 | 0 | n = trunc(n / 10); |
563 | 0 | } while (!njs_double_is_zero(n)); |
564 | | |
565 | | /* Zero or space padding. */ |
566 | |
|
567 | 0 | if (spf->width != 0) { |
568 | 0 | length = (temp + NJS_DOUBLE_LEN) - p; |
569 | 0 | end = buf + (spf->width - length); |
570 | 0 | end = njs_min(end, spf->end); |
571 | |
|
572 | 0 | while (buf < end) { |
573 | 0 | *buf++ = spf->padding; |
574 | 0 | } |
575 | 0 | } |
576 | | |
577 | | /* Number copying. */ |
578 | |
|
579 | 0 | length = (temp + NJS_DOUBLE_LEN) - p; |
580 | |
|
581 | 0 | end = buf + length; |
582 | 0 | end = njs_min(end, spf->end); |
583 | |
|
584 | 0 | while (buf < end) { |
585 | 0 | *buf++ = *p++; |
586 | 0 | } |
587 | |
|
588 | 0 | return buf; |
589 | 0 | } |
590 | | |
591 | | |
592 | | NJS_EXPORT |
593 | | int njs_dprint(int fd, u_char *buf, size_t size) |
594 | 21.3k | { |
595 | 21.3k | return write(fd, buf, size); |
596 | 21.3k | } |
597 | | |
598 | | |
599 | | int |
600 | | njs_dprintf(int fd, const char *fmt, ...) |
601 | 0 | { |
602 | 0 | size_t size; |
603 | 0 | u_char text[16384], *p; |
604 | 0 | va_list args; |
605 | |
|
606 | 0 | va_start(args, fmt); |
607 | 0 | p = njs_vsprintf(text, text + sizeof(text), fmt, args); |
608 | 0 | va_end(args); |
609 | |
|
610 | 0 | size = p - text; |
611 | |
|
612 | 0 | return write(fd, text, size); |
613 | 0 | } |