1 /*
2 * This version of snprintf() and vsnprintf() is based on Sprint from
3 * SIO by Panagiotis Tsirigotis, as included with xidentd-2.2.1.
4 *
5 * The modifications were made by The XFree86 Project, Inc and are
6 * Copyright 1999 by The XFree86 Project, Inc. These modifications
7 * consist of removing the support for writing to file streams,
8 * renaming some functions, ansification, and making some adjustments
9 * to achieve the semantics for snprintf and vsnprintf() as described
10 * in the relevant man page for FreeBSD 2.2.8.
11 *
12 *
13 * The original version carries the following notice:
14 *
15 * (c) Copyright 1992, 1993 by Panagiotis Tsirigotis
16 *
17 * The author (Panagiotis Tsirigotis) grants permission to use, copy,
18 * and distribute this software and its documentation for any purpose
19 * and without fee, provided that a) the above copyright notice extant in
20 * files in this distribution is not removed from files included in any
21 * redistribution, and b) this file is also included in any redistribution.
22 *
23 * Modifications to this software may be distributed, either by distributing
24 * the modified software or by distributing patches to the original software,
25 * under the following additional terms:
26 *
27 * 1. The version number will be modified as follows:
28 * a. The first 3 components of the version number
29 * (i.e. <number>.<number>.<number>) will remain unchanged.
30 * b. A new component will be appended to the version number to indicate
31 * the modification level. The form of this component is up to the
32 * author of the modifications.
33 *
34 * 2. The author of the modifications will include his/her name by appending
35 * it along with the new version number to this file and will be
36 * responsible for any wrong behavior of the modified software.
37 *
38 * The author makes no representations about the suitability of this
39 * software for any purpose. It is provided "as is" without any express
40 * or implied warranty.
41 *
42 * Changes and modifications for:
43 *
44 * xinetd Version 2.1.4-bsdi
45 * xinetd Version 2.1.4-freebsd
46 * xinetd Version 2.1.4-linux
47 *
48 * are
49 *
50 * (c) Copyright 1995 by Charles Murcko
51 * All Rights Reserved
52 */
53
54 /* $XFree86: xc/lib/misc/snprintf.c,v 3.1 1999/04/28 15:04:51 dawes Exp $ */
55
56
57 /*
58 * Assumption: systems that don't have snprintf and vsnprintf do have
59 * ecvt, fcvt and gcvt.
60 */
61
62 /* From: Id: sprint.c,v 1.5 1995/09/10 18:35:09 chuck Exp */
63
64 #ifdef HAVE_CONFIG_H
65 # include <config.h>
66 #endif
67
68 #ifndef HAVE_STDARG_H
69 #error Need stdarg here!
70 #endif
71
72 #include <ctype.h>
73 #include <stdlib.h>
74 #include <stdarg.h>
75 #include <string.h>
76 #include <sys/types.h>
77
78 #ifndef SCOPE
79 #define SCOPE /**/
80 #endif
81
82 #ifndef WIDE_INT
83 #define WIDE_INT long
84 #endif
85
86 typedef WIDE_INT wide_int;
87 typedef unsigned WIDE_INT u_wide_int;
88 typedef int bool_int;
89
90 #ifndef FALSE
91 #define FALSE 0
92 #define TRUE 1
93 #endif
94
95 #define NUL '\0'
96
97 #define S_NULL "(null)"
98 #define S_NULL_LEN 6
99
100 #define FLOAT_DIGITS 6
101 #define EXPONENT_LENGTH 10
102
103 typedef enum { NO = 0, YES = 1 } boolean_e ;
104
105 /*
106 * NUM_BUF_SIZE is the size of the buffer used for arithmetic conversions
107 *
108 * XXX: this is a magic number; do not decrease it
109 */
110 #define NUM_BUF_SIZE 512
111
112 /*
113 * The INS_CHAR macro inserts a character in the buffer.
114 * It uses the char pointers sp and bep:
115 * sp points to the next available character in the buffer
116 * bep points to the end-of-buffer
117 * While using this macro, note that the nextb pointer is NOT updated.
118 *
119 * Excess characters are discarded if the string overflows.
120 *
121 * NOTE: Evaluation of the c argument should not have any side-effects
122 */
123 #define INS_CHAR( c, sp, bep, cc ) \
124 { \
125 if ( sp < bep ) \
126 *sp++ = c ; \
127 cc++ ; \
128 }
129
130 #define NUM( c ) ( c - '0' )
131
132 #define STR_TO_DEC( str, num ) \
133 num = NUM( *str++ ) ; \
134 while ( isdigit( *str ) ) { \
135 num *= 10 ; \
136 num += NUM( *str++ ) ; \
137 }
138
139 /*
140 * This macro does zero padding so that the precision
141 * requirement is satisfied. The padding is done by
142 * adding '0's to the left of the string that is going
143 * to be printed.
144 */
145 #define FIX_PRECISION( adjust, precision, s, s_len ) \
146 if ( adjust ) \
147 while ( s_len < precision ) { \
148 *--s = '0' ; \
149 s_len++ ; \
150 }
151
152 /*
153 * Macro that does padding. The padding is done by printing
154 * the character ch.
155 */
156 #define PAD( width, len, ch ) \
157 do { \
158 INS_CHAR( ch, sp, bep, cc ) ; \
159 width-- ; \
160 } while ( width > len )
161
162 /*
163 * Prefix the character ch to the string str
164 * Increase length
165 * Set the has_prefix flag
166 */
167 #define PREFIX( str, length, ch ) \
168 *--str = ch ; length++ ; has_prefix = YES
169
170 static char *conv_10(wide_int num, bool_int is_unsigned,
171 bool_int * is_negative, char *buf_end, int *len);
172 SCOPE int vsnprintf(char *str, size_t size, const char *fmt, va_list ap);
173
174 /*
175 * snprintf is based on SIO's Sprint.
176 *
177 * Sprint is the equivalent of printf for SIO.
178 * It returns the # of chars written
179 * Assumptions:
180 * - all floating point arguments are passed as doubles
181 */
182 SCOPE int
snprintf(char * str,size_t size,const char * fmt,...)183 snprintf(char *str, size_t size, const char *fmt, ...)
184 {
185 int cc;
186 va_list ap;
187
188 va_start(ap, fmt);
189 cc = vsnprintf(str, size, fmt, ap);
190 va_end(ap);
191 return cc;
192 }
193
194 /*
195 * Convert a floating point number to a string formats 'f', 'e' or 'E'.
196 * The result is placed in buf, and len denotes the length of the string
197 * The sign is returned in the is_negative argument (and is not placed
198 * in buf). Always add decimal point if add_dp is YES.
199 */
200 static char *
conv_fp(char format,double num,boolean_e add_dp,int precision,bool_int * is_negative,char buf[],int * len)201 conv_fp(char format, double num, boolean_e add_dp, int precision,
202 bool_int *is_negative, char buf[], int *len)
203 {
204 char *s = buf;
205 char *p;
206 int decimal_point;
207
208 if (format == 'f')
209 p = fcvt(num, precision, &decimal_point, is_negative);
210 else /* either e or E format */
211 p = ecvt(num, precision + 1, &decimal_point, is_negative);
212
213 /*
214 * Check for Infinity and NaN
215 */
216 if (isalpha(*p)) {
217 *len = strlen(strcpy(buf, p));
218 *is_negative = FALSE;
219 return (buf);
220 }
221 if (format == 'f')
222 if (decimal_point <= 0) {
223 *s++ = '0';
224 if (precision > 0) {
225 *s++ = '.';
226 while (decimal_point++ < 0)
227 *s++ = '0';
228 } else if (add_dp)
229 *s++ = '.';
230 } else {
231 while (decimal_point-- > 0)
232 *s++ = *p++;
233 if (precision > 0 || add_dp)
234 *s++ = '.';
235 } else {
236 *s++ = *p++;
237 if (precision > 0 || add_dp)
238 *s++ = '.';
239 }
240
241 /*
242 * copy the rest of p, the NUL is NOT copied
243 */
244 while (*p)
245 *s++ = *p++;
246
247 if (format != 'f') {
248 char temp[EXPONENT_LENGTH]; /* for exponent conversion */
249 int t_len;
250 bool_int exponent_is_negative;
251
252 *s++ = format; /* either e or E */
253 decimal_point--;
254 if (decimal_point != 0) {
255 p = conv_10((wide_int) decimal_point, FALSE, &exponent_is_negative,
256 &temp[EXPONENT_LENGTH], &t_len);
257 *s++ = exponent_is_negative ? '-' : '+';
258
259 /*
260 * Make sure the exponent has at least 2 digits
261 */
262 if (t_len == 1)
263 *s++ = '0';
264 while (t_len--)
265 *s++ = *p++;
266 } else {
267 *s++ = '+';
268 *s++ = '0';
269 *s++ = '0';
270 }
271 }
272 *len = s - buf;
273 return (buf);
274 }
275
276 /*
277 * Convert num to a base X number where X is a power of 2. nbits determines X.
278 * For example, if nbits is 3, we do base 8 conversion
279 * Return value:
280 * a pointer to a string containing the number
281 *
282 * The caller provides a buffer for the string: that is the buf_end argument
283 * which is a pointer to the END of the buffer + 1 (i.e. if the buffer
284 * is declared as buf[ 100 ], buf_end should be &buf[ 100 ])
285 */
286 static char *
conv_p2(u_wide_int num,int nbits,char format,char * buf_end,int * len)287 conv_p2(u_wide_int num, int nbits, char format, char *buf_end, int *len)
288 {
289 int mask = (1 << nbits) - 1;
290 char *p = buf_end;
291 static char low_digits[] = "0123456789abcdef";
292 static char upper_digits[] = "0123456789ABCDEF";
293 char *digits = (format == 'X') ? upper_digits : low_digits;
294
295 do {
296 *--p = digits[num & mask];
297 num >>= nbits;
298 }
299 while (num);
300
301 *len = buf_end - p;
302 return (p);
303 }
304
305 /*
306 * Convert num to its decimal format.
307 * Return value:
308 * - a pointer to a string containing the number (no sign)
309 * - len contains the length of the string
310 * - is_negative is set to TRUE or FALSE depending on the sign
311 * of the number (always set to FALSE if is_unsigned is TRUE)
312 *
313 * The caller provides a buffer for the string: that is the buf_end argument
314 * which is a pointer to the END of the buffer + 1 (i.e. if the buffer
315 * is declared as buf[ 100 ], buf_end should be &buf[ 100 ])
316 */
317 static char *
conv_10(wide_int num,bool_int is_unsigned,bool_int * is_negative,char * buf_end,int * len)318 conv_10(wide_int num, bool_int is_unsigned, bool_int *is_negative,
319 char *buf_end, int *len)
320 {
321 char *p = buf_end;
322 u_wide_int magnitude;
323
324 if (is_unsigned) {
325 magnitude = (u_wide_int) num;
326 *is_negative = FALSE;
327 } else {
328 *is_negative = (num < 0);
329
330 /*
331 * On a 2's complement machine, negating the most negative integer
332 * results in a number that cannot be represented as a signed integer.
333 * Here is what we do to obtain the number's magnitude:
334 * a. add 1 to the number
335 * b. negate it (becomes positive)
336 * c. convert it to unsigned
337 * d. add 1
338 */
339 if (*is_negative) {
340 wide_int t = num + 1;
341
342 magnitude = ((u_wide_int) - t) + 1;
343 } else
344 magnitude = (u_wide_int) num;
345 }
346
347 /*
348 * We use a do-while loop so that we write at least 1 digit
349 */
350 do {
351 register u_wide_int new_magnitude = magnitude / 10;
352
353 *--p = magnitude - new_magnitude * 10 + '0';
354 magnitude = new_magnitude;
355 }
356 while (magnitude);
357
358 *len = buf_end - p;
359 return (p);
360 }
361
362 /*
363 * Do format conversion.
364 */
365 SCOPE int
vsnprintf(char * str,size_t size,const char * fmt,va_list ap)366 vsnprintf(char *str, size_t size, const char *fmt, va_list ap)
367 {
368 char *sp;
369 char *bep;
370 int cc = 0;
371 int i;
372
373 char *s;
374 char *q;
375 int s_len;
376
377 int min_width;
378 int precision;
379 enum {
380 LEFT, RIGHT
381 } adjust;
382 char pad_char;
383 char prefix_char;
384
385 double fp_num;
386 wide_int i_num;
387 u_wide_int ui_num;
388
389 char num_buf[NUM_BUF_SIZE];
390 char char_buf[2]; /* for printing %% and %<unknown> */
391
392 /*
393 * Flag variables
394 */
395 boolean_e is_long;
396 boolean_e alternate_form;
397 boolean_e print_sign;
398 boolean_e print_blank;
399 boolean_e adjust_precision;
400 boolean_e adjust_width;
401 bool_int is_negative;
402
403 if (size == 0)
404 return 0;
405
406 sp = str;
407 bep = str + size - 1;
408
409 while (*fmt) {
410 if (*fmt != '%') {
411 INS_CHAR(*fmt, sp, bep, cc);
412 } else {
413 /*
414 * Default variable settings
415 */
416 adjust = RIGHT;
417 alternate_form = print_sign = print_blank = NO;
418 pad_char = ' ';
419 prefix_char = NUL;
420
421 fmt++;
422
423 /*
424 * Try to avoid checking for flags, width or precision
425 */
426 if (isascii(*fmt) && !islower(*fmt)) {
427 /*
428 * Recognize flags: -, #, BLANK, +
429 */
430 for (;; fmt++) {
431 if (*fmt == '-')
432 adjust = LEFT;
433 else if (*fmt == '+')
434 print_sign = YES;
435 else if (*fmt == '#')
436 alternate_form = YES;
437 else if (*fmt == ' ')
438 print_blank = YES;
439 else if (*fmt == '0')
440 pad_char = '0';
441 else
442 break;
443 }
444
445 /*
446 * Check if a width was specified
447 */
448 if (isdigit(*fmt)) {
449 STR_TO_DEC(fmt, min_width);
450 adjust_width = YES;
451 } else if (*fmt == '*') {
452 min_width = va_arg(ap, int);
453
454 fmt++;
455 adjust_width = YES;
456 if (min_width < 0) {
457 adjust = LEFT;
458 min_width = -min_width;
459 }
460 } else
461 adjust_width = NO;
462
463 /*
464 * Check if a precision was specified
465 *
466 * XXX: an unreasonable amount of precision may be specified
467 * resulting in overflow of num_buf. Currently we
468 * ignore this possibility.
469 */
470 if (*fmt == '.') {
471 adjust_precision = YES;
472 fmt++;
473 if (isdigit(*fmt)) {
474 STR_TO_DEC(fmt, precision);
475 } else if (*fmt == '*') {
476 precision = va_arg(ap, int);
477
478 fmt++;
479 if (precision < 0)
480 precision = 0;
481 } else
482 precision = 0;
483 } else
484 adjust_precision = NO;
485 } else
486 adjust_precision = adjust_width = NO;
487
488 /*
489 * Modifier check
490 */
491 if (*fmt == 'l') {
492 is_long = YES;
493 fmt++;
494 } else
495 is_long = NO;
496
497 /*
498 * Argument extraction and printing.
499 * First we determine the argument type.
500 * Then, we convert the argument to a string.
501 * On exit from the switch, s points to the string that
502 * must be printed, s_len has the length of the string
503 * The precision requirements, if any, are reflected in s_len.
504 *
505 * NOTE: pad_char may be set to '0' because of the 0 flag.
506 * It is reset to ' ' by non-numeric formats
507 */
508 switch (*fmt) {
509 case 'd':
510 case 'i':
511 case 'u':
512 if (is_long)
513 i_num = va_arg(ap, wide_int);
514 else
515 i_num = (wide_int) va_arg(ap, int);
516
517 s = conv_10(i_num, (*fmt) == 'u', &is_negative,
518 &num_buf[NUM_BUF_SIZE], &s_len);
519 FIX_PRECISION(adjust_precision, precision, s, s_len);
520
521 if (*fmt != 'u') {
522 if (is_negative)
523 prefix_char = '-';
524 else if (print_sign)
525 prefix_char = '+';
526 else if (print_blank)
527 prefix_char = ' ';
528 }
529 break;
530
531 case 'o':
532 if (is_long)
533 ui_num = va_arg(ap, u_wide_int);
534 else
535 ui_num = (u_wide_int) va_arg(ap, unsigned int);
536
537 s = conv_p2(ui_num, 3, *fmt,
538 &num_buf[NUM_BUF_SIZE], &s_len);
539 FIX_PRECISION(adjust_precision, precision, s, s_len);
540 if (alternate_form && *s != '0') {
541 *--s = '0';
542 s_len++;
543 }
544 break;
545
546 case 'x':
547 case 'X':
548 if (is_long)
549 ui_num = (u_wide_int) va_arg(ap, u_wide_int);
550 else
551 ui_num = (u_wide_int) va_arg(ap, unsigned int);
552
553 s = conv_p2(ui_num, 4, *fmt,
554 &num_buf[NUM_BUF_SIZE], &s_len);
555 FIX_PRECISION(adjust_precision, precision, s, s_len);
556 if (alternate_form && i_num != 0) {
557 *--s = *fmt; /* 'x' or 'X' */
558 *--s = '0';
559 s_len += 2;
560 }
561 break;
562
563 case 's':
564 s = va_arg(ap, char *);
565
566 if (s != NULL) {
567 s_len = strlen(s);
568 if (adjust_precision && precision < s_len)
569 s_len = precision;
570 } else {
571 s = S_NULL;
572 s_len = S_NULL_LEN;
573 }
574 pad_char = ' ';
575 break;
576
577 case 'f':
578 case 'e':
579 case 'E':
580 fp_num = va_arg(ap, double);
581
582 s = conv_fp(*fmt, fp_num, alternate_form,
583 (adjust_precision == NO) ? FLOAT_DIGITS : precision,
584 &is_negative, &num_buf[1], &s_len);
585 if (is_negative)
586 prefix_char = '-';
587 else if (print_sign)
588 prefix_char = '+';
589 else if (print_blank)
590 prefix_char = ' ';
591 break;
592
593 case 'g':
594 case 'G':
595 if (adjust_precision == NO)
596 precision = FLOAT_DIGITS;
597 else if (precision == 0)
598 precision = 1;
599 /*
600 * We use &num_buf[ 1 ], so that we have room for the sign
601 */
602 s = gcvt(va_arg(ap, double), precision, &num_buf[1]);
603
604 if (*s == '-')
605 prefix_char = *s++;
606 else if (print_sign)
607 prefix_char = '+';
608 else if (print_blank)
609 prefix_char = ' ';
610
611 s_len = strlen(s);
612
613 if (alternate_form && (q = strchr(s, '.')) == NULL)
614 s[s_len++] = '.';
615 if (*fmt == 'G' && (q = strchr(s, 'e')) != NULL)
616 *q = 'E';
617 break;
618
619 case 'c':
620 char_buf[0] = (char)(va_arg(ap, int));
621
622 s = &char_buf[0];
623 s_len = 1;
624 pad_char = ' ';
625 break;
626
627 case '%':
628 char_buf[0] = '%';
629 s = &char_buf[0];
630 s_len = 1;
631 pad_char = ' ';
632 break;
633
634 case 'n':
635 *(va_arg(ap, int *)) = cc;
636
637 break;
638
639 /*
640 * If the pointer size is equal to the size of an unsigned
641 * integer we convert the pointer to a hex number, otherwise
642 * we print "%p" to indicate that we don't handle "%p".
643 */
644 case 'p':
645 ui_num = (u_wide_int) va_arg(ap, void *);
646
647 if (sizeof(void *) <= sizeof(u_wide_int))
648 s = conv_p2(ui_num, 4, 'x',
649 &num_buf[NUM_BUF_SIZE], &s_len);
650
651 else {
652 s = "%p";
653 s_len = 2;
654 }
655 pad_char = ' ';
656 break;
657
658 case NUL:
659 /*
660 * The last character of the format string was %.
661 * We ignore it.
662 */
663 continue;
664
665 /*
666 * The default case is for unrecognized %'s.
667 * We print %<char> to help the user identify what
668 * option is not understood.
669 * This is also useful in case the user wants to pass
670 * the output of __sio_converter to another function
671 * that understands some other %<char> (like syslog).
672 * Note that we can't point s inside fmt because the
673 * unknown <char> could be preceded by width etc.
674 */
675 default:
676 char_buf[0] = '%';
677 char_buf[1] = *fmt;
678 s = char_buf;
679 s_len = 2;
680 pad_char = ' ';
681 break;
682 }
683
684 if (prefix_char != NUL) {
685 *--s = prefix_char;
686 s_len++;
687 }
688 if (adjust_width && adjust == RIGHT && min_width > s_len) {
689 if (pad_char == '0' && prefix_char != NUL) {
690 INS_CHAR(*s, sp, bep, cc)
691 s++;
692 s_len--;
693 min_width--;
694 }
695 PAD(min_width, s_len, pad_char);
696 }
697 /*
698 * Print the string s.
699 */
700 for (i = s_len; i != 0; i--) {
701 INS_CHAR(*s, sp, bep, cc);
702 s++;
703 }
704
705 if (adjust_width && adjust == LEFT && min_width > s_len)
706 PAD(min_width, s_len, pad_char);
707 }
708 fmt++;
709 }
710 if (cc < size)
711 str[cc] = NUL;
712 else
713 str[size - 1] = NUL;
714 return cc;
715 }
716
717