snprintf.c

Go to the documentation of this file.
00001 /*
00002  * This version of snprintf() and vsnprintf() is based on Sprint from
00003  * SIO by Panagiotis Tsirigotis, as included with xidentd-2.2.1.
00004  *
00005  * The modifications were made by The XFree86 Project, Inc and are
00006  * Copyright 1999 by The XFree86 Project, Inc.  These modifications
00007  * consist of removing the support for writing to file streams,
00008  * renaming some functions, ansification, and making some adjustments
00009  * to achieve the semantics for snprintf and vsnprintf() as described
00010  * in the relevant man page for FreeBSD 2.2.8.
00011  *
00012  *
00013  * The original version carries the following notice:
00014  *
00015  * (c) Copyright 1992, 1993 by Panagiotis Tsirigotis
00016  *
00017  * The author (Panagiotis Tsirigotis) grants permission to use, copy,
00018  * and distribute this software and its documentation for any purpose
00019  * and without fee, provided that a) the above copyright notice extant in
00020  * files in this distribution is not removed from files included in any
00021  * redistribution, and b) this file is also included in any redistribution.
00022  *
00023  * Modifications to this software may be distributed, either by distributing
00024  * the modified software or by distributing patches to the original software,
00025  * under the following additional terms:
00026  *
00027  * 1. The version number will be modified as follows:
00028  *      a. The first 3 components of the version number
00029  *         (i.e. <number>.<number>.<number>) will remain unchanged.
00030  *      b. A new component will be appended to the version number to indicate
00031  *         the modification level. The form of this component is up to the
00032  *         author of the modifications.
00033  *
00034  * 2. The author of the modifications will include his/her name by appending
00035  *    it along with the new version number to this file and will be
00036  *    responsible for any wrong behavior of the modified software.
00037  *
00038  * The author makes no representations about the suitability of this
00039  * software for any purpose.  It is provided "as is" without any express
00040  * or implied warranty.
00041  *
00042  * Changes and modifications for: 
00043  *
00044  * xinetd Version 2.1.4-bsdi
00045  * xinetd Version 2.1.4-freebsd
00046  * xinetd Version 2.1.4-linux
00047  *
00048  * are      
00049  *
00050  * (c) Copyright 1995 by Charles Murcko
00051  * All Rights Reserved
00052  */
00053 
00054 /* $XFree86$ */
00055 
00056 
00057 /*
00058  * Assumption: systems that don't have snprintf and vsnprintf do have
00059  * ecvt, fcvt and gcvt.
00060  */
00061 
00062 /* From: Id: sprint.c,v 1.5 1995/09/10 18:35:09 chuck Exp */
00063 
00064 #include <ctype.h>
00065 #include <stdlib.h>
00066 #include <stdarg.h>
00067 #include <string.h>
00068 #include <sys/types.h>
00069 
00070 #ifndef SCOPE
00071 #define SCOPE                                   
00072 #endif
00073 
00074 #ifndef WIDE_INT
00075 #define WIDE_INT                                long
00076 #endif
00077 
00078 typedef WIDE_INT wide_int;
00079 typedef unsigned WIDE_INT u_wide_int;
00080 typedef int bool_int;
00081 
00082 #ifndef FALSE
00083 #define FALSE                                   0
00084 #define TRUE                                    1
00085 #endif
00086 
00087 #define NUL                                     '\0'
00088 
00089 #define S_NULL                                  "(null)"
00090 #define S_NULL_LEN                              6
00091 
00092 #define FLOAT_DIGITS                            6
00093 #define EXPONENT_LENGTH                         10
00094 
00095 typedef enum { NO = 0, YES = 1 } boolean_e ;
00096 
00097 /*
00098  * NUM_BUF_SIZE is the size of the buffer used for arithmetic conversions
00099  *
00100  * XXX: this is a magic number; do not decrease it
00101  */
00102 #define NUM_BUF_SIZE                            512
00103 
00104 /*
00105  * The INS_CHAR macro inserts a character in the buffer.
00106  * It uses the char pointers sp and bep:
00107  *      sp points to the next available character in the buffer
00108  *      bep points to the end-of-buffer
00109  * While using this macro, note that the nextb pointer is NOT updated.
00110  *
00111  * Excess characters are discarded if the string overflows.
00112  *
00113  * NOTE: Evaluation of the c argument should not have any side-effects
00114  */
00115 #define INS_CHAR( c, sp, bep, cc )                                      \
00116         {                                                               \
00117             if ( sp < bep )                                             \
00118                 *sp++ = c ;                                             \
00119             cc++ ;                                                      \
00120         }
00121 
00122 #define NUM( c )                        ( c - '0' )
00123 
00124 #define STR_TO_DEC( str, num )                                          \
00125         num = NUM( *str++ ) ;                                           \
00126         while ( isdigit( *str ) ) {                                     \
00127                 num *= 10 ;                                             \
00128                 num += NUM( *str++ ) ;                                  \
00129         }
00130 
00131 /*
00132  * This macro does zero padding so that the precision
00133  * requirement is satisfied. The padding is done by
00134  * adding '0's to the left of the string that is going
00135  * to be printed.
00136  */
00137 #define FIX_PRECISION( adjust, precision, s, s_len )                    \
00138         if ( adjust )                                                   \
00139                 while ( s_len < precision ) {                           \
00140                         *--s = '0' ;                                    \
00141                         s_len++ ;                                       \
00142         }
00143 
00144 /*
00145  * Macro that does padding. The padding is done by printing
00146  * the character ch.
00147  */
00148 #define PAD( width, len, ch )                                           \
00149         do {                                                            \
00150                 INS_CHAR( ch, sp, bep, cc ) ;                           \
00151                 width-- ;                                               \
00152         } while ( width > len )
00153 
00154 /*
00155  * Prefix the character ch to the string str
00156  * Increase length
00157  * Set the has_prefix flag
00158  */
00159 #define PREFIX( str, length, ch )                                       \
00160         *--str = ch ; length++ ; has_prefix = YES
00161 
00162 static char *conv_10(wide_int num, bool_int is_unsigned,
00163                         bool_int * is_negative, char *buf_end, int *len);
00164 SCOPE int vsnprintf(char *str, size_t size, const char *fmt, va_list ap);
00165 
00166 /*
00167  * snprintf is based on SIO's Sprint.
00168  *
00169  * Sprint is the equivalent of printf for SIO.
00170  * It returns the # of chars written
00171  * Assumptions:
00172  *     - all floating point arguments are passed as doubles
00173  */
00174 SCOPE int 
00175 snprintf(char *str, size_t size, const char *fmt, ...)
00176 {
00177     int cc;
00178     va_list ap;
00179 
00180     va_start(ap, fmt);
00181     cc = vsnprintf(str, size, fmt, ap);
00182     va_end(ap);
00183     return cc;
00184 }
00185 
00186 /*
00187  * Convert a floating point number to a string formats 'f', 'e' or 'E'.
00188  * The result is placed in buf, and len denotes the length of the string
00189  * The sign is returned in the is_negative argument (and is not placed
00190  * in buf).  Always add decimal point if add_dp is YES.
00191  */
00192 static char *
00193 conv_fp(char format, double num, boolean_e add_dp, int precision,
00194         bool_int *is_negative, char buf[], int *len)
00195 {
00196     char *s = buf;
00197     char *p;
00198     int decimal_point;
00199 
00200     if (format == 'f')
00201         p = fcvt(num, precision, &decimal_point, is_negative);
00202     else                               /* either e or E format */
00203         p = ecvt(num, precision + 1, &decimal_point, is_negative);
00204 
00205     /*
00206      * Check for Infinity and NaN
00207      */
00208     if (isalpha(*p)) {
00209         *len = strlen(strcpy(buf, p));
00210         *is_negative = FALSE;
00211         return (buf);
00212     }
00213     if (format == 'f')
00214         if (decimal_point <= 0) {
00215             *s++ = '0';
00216             if (precision > 0) {
00217                 *s++ = '.';
00218                 while (decimal_point++ < 0)
00219                     *s++ = '0';
00220             } else if (add_dp)
00221                 *s++ = '.';
00222         } else {
00223             while (decimal_point-- > 0)
00224                 *s++ = *p++;
00225             if (precision > 0 || add_dp)
00226                 *s++ = '.';
00227     } else {
00228         *s++ = *p++;
00229         if (precision > 0 || add_dp)
00230             *s++ = '.';
00231     }
00232 
00233     /*
00234      * copy the rest of p, the NUL is NOT copied
00235      */
00236     while (*p)
00237         *s++ = *p++;
00238 
00239     if (format != 'f') {
00240         char temp[EXPONENT_LENGTH];    /* for exponent conversion */
00241         int t_len;
00242         bool_int exponent_is_negative;
00243 
00244         *s++ = format;                 /* either e or E */
00245         decimal_point--;
00246         if (decimal_point != 0) {
00247             p = conv_10((wide_int) decimal_point, FALSE, &exponent_is_negative,
00248                 &temp[EXPONENT_LENGTH], &t_len);
00249             *s++ = exponent_is_negative ? '-' : '+';
00250 
00251             /*
00252              * Make sure the exponent has at least 2 digits
00253              */
00254             if (t_len == 1)
00255                 *s++ = '0';
00256             while (t_len--)
00257                 *s++ = *p++;
00258         } else {
00259             *s++ = '+';
00260             *s++ = '0';
00261             *s++ = '0';
00262         }
00263     }
00264     *len = s - buf;
00265     return (buf);
00266 }
00267 
00268 /*
00269  * Convert num to a base X number where X is a power of 2. nbits determines X.
00270  * For example, if nbits is 3, we do base 8 conversion
00271  * Return value:
00272  *           a pointer to a string containing the number
00273  *
00274  * The caller provides a buffer for the string: that is the buf_end argument
00275  * which is a pointer to the END of the buffer + 1 (i.e. if the buffer
00276  * is declared as buf[ 100 ], buf_end should be &buf[ 100 ])
00277  */
00278 static char *
00279 conv_p2(u_wide_int num, int nbits, char format, char *buf_end, int *len)
00280 {
00281     int mask = (1 << nbits) - 1;
00282     char *p = buf_end;
00283     static char low_digits[] = "0123456789abcdef";
00284     static char upper_digits[] = "0123456789ABCDEF";
00285     char *digits = (format == 'X') ? upper_digits : low_digits;
00286 
00287     do {
00288         *--p = digits[num & mask];
00289         num >>= nbits;
00290     }
00291     while (num);
00292 
00293     *len = buf_end - p;
00294     return (p);
00295 }
00296 
00297 /*
00298  * Convert num to its decimal format.
00299  * Return value:
00300  *       - a pointer to a string containing the number (no sign)
00301  *             - len contains the length of the string
00302  *             - is_negative is set to TRUE or FALSE depending on the sign
00303  *               of the number (always set to FALSE if is_unsigned is TRUE)
00304  *
00305  * The caller provides a buffer for the string: that is the buf_end argument
00306  * which is a pointer to the END of the buffer + 1 (i.e. if the buffer
00307  * is declared as buf[ 100 ], buf_end should be &buf[ 100 ])
00308  */
00309 static char *
00310 conv_10(wide_int num, bool_int is_unsigned, bool_int *is_negative,
00311         char *buf_end, int *len)
00312 {
00313     char *p = buf_end;
00314     u_wide_int magnitude;
00315 
00316     if (is_unsigned) {
00317         magnitude = (u_wide_int) num;
00318         *is_negative = FALSE;
00319     } else {
00320         *is_negative = (num < 0);
00321 
00322         /*
00323          * On a 2's complement machine, negating the most negative integer 
00324          * results in a number that cannot be represented as a signed integer.
00325          * Here is what we do to obtain the number's magnitude:
00326          *              a. add 1 to the number
00327          *              b. negate it (becomes positive)
00328          *              c. convert it to unsigned
00329          *              d. add 1
00330          */
00331         if (*is_negative) {
00332             wide_int t = num + 1;
00333 
00334             magnitude = ((u_wide_int) - t) + 1;
00335         } else
00336             magnitude = (u_wide_int) num;
00337     }
00338 
00339     /*
00340      * We use a do-while loop so that we write at least 1 digit 
00341      */
00342     do {
00343         register u_wide_int new_magnitude = magnitude / 10;
00344 
00345         *--p = magnitude - new_magnitude * 10 + '0';
00346         magnitude = new_magnitude;
00347     }
00348     while (magnitude);
00349 
00350     *len = buf_end - p;
00351     return (p);
00352 }
00353 
00354 /*
00355  * Do format conversion.
00356  */
00357 SCOPE int 
00358 vsnprintf(char *str, size_t size, const char *fmt, va_list ap)
00359 {
00360     char *sp;
00361     char *bep;
00362     int cc = 0;
00363     int i;
00364 
00365     char *s;
00366     char *q;
00367     int s_len;
00368 
00369     int min_width;
00370     int precision;
00371     enum {
00372         LEFT, RIGHT
00373     } adjust;
00374     char pad_char;
00375     char prefix_char;
00376 
00377     double fp_num;
00378     wide_int i_num;
00379     u_wide_int ui_num;
00380 
00381     char num_buf[NUM_BUF_SIZE];
00382     char char_buf[2];                  /* for printing %% and %<unknown> */
00383 
00384     /*
00385      * Flag variables
00386      */
00387     boolean_e is_long;
00388     boolean_e alternate_form;
00389     boolean_e print_sign;
00390     boolean_e print_blank;
00391     boolean_e adjust_precision;
00392     boolean_e adjust_width;
00393     bool_int is_negative;
00394 
00395     if (size == 0)
00396         return 0;
00397 
00398     sp = str;
00399     bep = str + size - 1;
00400 
00401     while (*fmt) {
00402         if (*fmt != '%') {
00403             INS_CHAR(*fmt, sp, bep, cc);
00404         } else {
00405             /*
00406              * Default variable settings
00407              */
00408             adjust = RIGHT;
00409             alternate_form = print_sign = print_blank = NO;
00410             pad_char = ' ';
00411             prefix_char = NUL;
00412 
00413             fmt++;
00414 
00415             /*
00416              * Try to avoid checking for flags, width or precision
00417              */
00418             if (isascii(*fmt) && !islower(*fmt)) {
00419                 /*
00420                  * Recognize flags: -, #, BLANK, +
00421                  */
00422                 for (;; fmt++) {
00423                     if (*fmt == '-')
00424                         adjust = LEFT;
00425                     else if (*fmt == '+')
00426                         print_sign = YES;
00427                     else if (*fmt == '#')
00428                         alternate_form = YES;
00429                     else if (*fmt == ' ')
00430                         print_blank = YES;
00431                     else if (*fmt == '0')
00432                         pad_char = '0';
00433                     else
00434                         break;
00435                 }
00436 
00437                 /*
00438                  * Check if a width was specified
00439                  */
00440                 if (isdigit(*fmt)) {
00441                     STR_TO_DEC(fmt, min_width);
00442                     adjust_width = YES;
00443                 } else if (*fmt == '*') {
00444                     min_width = va_arg(ap, int);
00445 
00446                     fmt++;
00447                     adjust_width = YES;
00448                     if (min_width < 0) {
00449                         adjust = LEFT;
00450                         min_width = -min_width;
00451                     }
00452                 } else
00453                     adjust_width = NO;
00454 
00455                 /*
00456                  * Check if a precision was specified
00457                  *
00458                  * XXX: an unreasonable amount of precision may be specified
00459                  *      resulting in overflow of num_buf. Currently we
00460                  *      ignore this possibility.
00461                  */
00462                 if (*fmt == '.') {
00463                     adjust_precision = YES;
00464                     fmt++;
00465                     if (isdigit(*fmt)) {
00466                         STR_TO_DEC(fmt, precision);
00467                     } else if (*fmt == '*') {
00468                         precision = va_arg(ap, int);
00469 
00470                         fmt++;
00471                         if (precision < 0)
00472                             precision = 0;
00473                     } else
00474                         precision = 0;
00475                 } else
00476                     adjust_precision = NO;
00477             } else
00478                 adjust_precision = adjust_width = NO;
00479 
00480             /*
00481              * Modifier check
00482              */
00483             if (*fmt == 'l') {
00484                 is_long = YES;
00485                 fmt++;
00486             } else
00487                 is_long = NO;
00488 
00489             /*
00490              * Argument extraction and printing.
00491              * First we determine the argument type.
00492              * Then, we convert the argument to a string.
00493              * On exit from the switch, s points to the string that
00494              * must be printed, s_len has the length of the string
00495              * The precision requirements, if any, are reflected in s_len.
00496              *
00497              * NOTE: pad_char may be set to '0' because of the 0 flag.
00498              *                      It is reset to ' ' by non-numeric formats
00499              */
00500             switch (*fmt) {
00501             case 'd':
00502             case 'i':
00503             case 'u':
00504                 if (is_long)
00505                     i_num = va_arg(ap, wide_int);
00506                 else
00507                     i_num = (wide_int) va_arg(ap, int);
00508 
00509                 s = conv_10(i_num, (*fmt) == 'u', &is_negative,
00510                     &num_buf[NUM_BUF_SIZE], &s_len);
00511                 FIX_PRECISION(adjust_precision, precision, s, s_len);
00512 
00513                 if (*fmt != 'u') {
00514                     if (is_negative)
00515                         prefix_char = '-';
00516                     else if (print_sign)
00517                         prefix_char = '+';
00518                     else if (print_blank)
00519                         prefix_char = ' ';
00520                 }
00521                 break;
00522 
00523             case 'o':
00524                 if (is_long)
00525                     ui_num = va_arg(ap, u_wide_int);
00526                 else
00527                     ui_num = (u_wide_int) va_arg(ap, unsigned int);
00528 
00529                 s = conv_p2(ui_num, 3, *fmt,
00530                     &num_buf[NUM_BUF_SIZE], &s_len);
00531                 FIX_PRECISION(adjust_precision, precision, s, s_len);
00532                 if (alternate_form && *s != '0') {
00533                     *--s = '0';
00534                     s_len++;
00535                 }
00536                 break;
00537 
00538             case 'x':
00539             case 'X':
00540                 if (is_long)
00541                     ui_num = (u_wide_int) va_arg(ap, u_wide_int);
00542                 else
00543                     ui_num = (u_wide_int) va_arg(ap, unsigned int);
00544 
00545                 s = conv_p2(ui_num, 4, *fmt,
00546                     &num_buf[NUM_BUF_SIZE], &s_len);
00547                 FIX_PRECISION(adjust_precision, precision, s, s_len);
00548                 if (alternate_form && i_num != 0) {
00549                     *--s = *fmt;       /* 'x' or 'X' */
00550                     *--s = '0';
00551                     s_len += 2;
00552                 }
00553                 break;
00554 
00555             case 's':
00556                 s = va_arg(ap, char *);
00557 
00558                 if (s != NULL) {
00559                     s_len = strlen(s);
00560                     if (adjust_precision && precision < s_len)
00561                         s_len = precision;
00562                 } else {
00563                     s = S_NULL;
00564                     s_len = S_NULL_LEN;
00565                 }
00566                 pad_char = ' ';
00567                 break;
00568 
00569             case 'f':
00570             case 'e':
00571             case 'E':
00572                 fp_num = va_arg(ap, double);
00573 
00574                 s = conv_fp(*fmt, fp_num, alternate_form,
00575                     (adjust_precision == NO) ? FLOAT_DIGITS : precision,
00576                     &is_negative, &num_buf[1], &s_len);
00577                 if (is_negative)
00578                     prefix_char = '-';
00579                 else if (print_sign)
00580                     prefix_char = '+';
00581                 else if (print_blank)
00582                     prefix_char = ' ';
00583                 break;
00584 
00585             case 'g':
00586             case 'G':
00587                 if (adjust_precision == NO)
00588                     precision = FLOAT_DIGITS;
00589                 else if (precision == 0)
00590                     precision = 1;
00591                 /*
00592                  * We use &num_buf[ 1 ], so that we have room for the sign
00593                  */
00594                 s = gcvt(va_arg(ap, double), precision, &num_buf[1]);
00595 
00596                 if (*s == '-')
00597                     prefix_char = *s++;
00598                 else if (print_sign)
00599                     prefix_char = '+';
00600                 else if (print_blank)
00601                     prefix_char = ' ';
00602 
00603                 s_len = strlen(s);
00604 
00605                 if (alternate_form && (q = strchr(s, '.')) == NULL)
00606                     s[s_len++] = '.';
00607                 if (*fmt == 'G' && (q = strchr(s, 'e')) != NULL)
00608                     *q = 'E';
00609                 break;
00610 
00611             case 'c':
00612                 char_buf[0] = (char)(va_arg(ap, int));
00613 
00614                 s = &char_buf[0];
00615                 s_len = 1;
00616                 pad_char = ' ';
00617                 break;
00618 
00619             case '%':
00620                 char_buf[0] = '%';
00621                 s = &char_buf[0];
00622                 s_len = 1;
00623                 pad_char = ' ';
00624                 break;
00625 
00626             case 'n':
00627                 *(va_arg(ap, int *)) = cc;
00628 
00629                 break;
00630 
00631                 /*
00632                  * If the pointer size is equal to the size of an unsigned
00633                  * integer we convert the pointer to a hex number, otherwise 
00634                  * we print "%p" to indicate that we don't handle "%p".
00635                  */
00636             case 'p':
00637                 ui_num = (u_wide_int) va_arg(ap, void *);
00638 
00639                 if (sizeof(void *) <= sizeof(u_wide_int))
00640                         s = conv_p2(ui_num, 4, 'x',
00641                         &num_buf[NUM_BUF_SIZE], &s_len);
00642 
00643                 else {
00644                     s = "%p";
00645                     s_len = 2;
00646                 }
00647                 pad_char = ' ';
00648                 break;
00649 
00650             case NUL:
00651                 /*
00652                  * The last character of the format string was %.
00653                  * We ignore it.
00654                  */
00655                 continue;
00656 
00657                 /*
00658                  * The default case is for unrecognized %'s.
00659                  * We print %<char> to help the user identify what
00660                  * option is not understood.
00661                  * This is also useful in case the user wants to pass
00662                  * the output of __sio_converter to another function
00663                  * that understands some other %<char> (like syslog).
00664                  * Note that we can't point s inside fmt because the
00665                  * unknown <char> could be preceded by width etc.
00666                  */
00667             default:
00668                 char_buf[0] = '%';
00669                 char_buf[1] = *fmt;
00670                 s = char_buf;
00671                 s_len = 2;
00672                 pad_char = ' ';
00673                 break;
00674             }
00675 
00676             if (prefix_char != NUL) {
00677                 *--s = prefix_char;
00678                 s_len++;
00679             }
00680             if (adjust_width && adjust == RIGHT && min_width > s_len) {
00681                 if (pad_char == '0' && prefix_char != NUL) {
00682                     INS_CHAR(*s, sp, bep, cc)
00683                         s++;
00684                     s_len--;
00685                     min_width--;
00686                 }
00687                 PAD(min_width, s_len, pad_char);
00688             }
00689             /*
00690              * Print the string s. 
00691              */
00692             for (i = s_len; i != 0; i--) {
00693                 INS_CHAR(*s, sp, bep, cc);
00694                 s++;
00695             }
00696 
00697             if (adjust_width && adjust == LEFT && min_width > s_len)
00698                 PAD(min_width, s_len, pad_char);
00699         }
00700         fmt++;
00701     }
00702     if (cc < size)
00703         str[cc] = NUL;
00704     else
00705         str[size - 1] = NUL;
00706     return cc;
00707 }
00708 

Generated on Sun Mar 4 15:03:56 2007 for Scilab [trunk] by  doxygen 1.5.1