/* Copyright (C) 1991, 1992, 1993, 1994, 1995 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. The GNU C Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with the GNU C Library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include "_itoa.h" #include "../locale/localeinfo.h" /* Include the shared code for parsing the format string. */ #include "printf-parse.h" /* This function from the GNU C library is also used in libio. To compile for use in libio, compile with -DUSE_IN_LIBIO. */ #ifdef USE_IN_LIBIO /* This code is for use in libio. */ #include #define PUT(f, s, n) _IO_sputn (f, s, n) #define PAD(padchar) \ if (specs[cnt].info.width > 0) \ done += _IO_padn (s, padchar, specs[cnt].info.width) #define PUTC(c, f) _IO_putc (c, f) #define vfprintf _IO_vfprintf #define size_t _IO_size_t #define FILE _IO_FILE #define va_list _IO_va_list #undef BUFSIZ #define BUFSIZ _IO_BUFSIZ #define ARGCHECK(s, format) \ do \ { \ /* Check file argument for consistence. */ \ CHECK_FILE (s, -1); \ if (s->_flags & _IO_NO_WRITES || format == NULL) \ { \ MAYBE_SET_EINVAL; \ return -1; \ } \ } while (0) #define UNBUFFERED_P(s) ((s)->_IO_file_flags & _IO_UNBUFFERED) #else /* ! USE_IN_LIBIO */ /* This code is for use in the GNU C library. */ #include #define PUTC(c, f) putc (c, f) #define PUT(f, s, n) fwrite (s, 1, n, f) ssize_t __printf_pad __P ((FILE *, char pad, size_t n)); #define PAD(padchar) \ if (specs[cnt].info.width > 0) \ { if (__printf_pad (s, padchar, specs[cnt].info.width) == -1) \ return -1; else done += specs[cnt].info.width; } #define ARGCHECK(s, format) \ do \ { \ /* Check file argument for consistence. */ \ if (!__validfp(s) || !s->__mode.__write || format == NULL) \ { \ errno = EINVAL; \ return -1; \ } \ if (!s->__seen) \ { \ if (__flshfp (s, EOF) == EOF) \ return -1; \ } \ } while (0) #define UNBUFFERED_P(s) ((s)->__buffer == NULL) #endif /* USE_IN_LIBIO */ #define outchar(x) \ do \ { \ register const int outc = (x); \ if (putc (outc, s) == EOF) \ return -1; \ else \ ++done; \ } while (0) #define outstring(string, len) \ do \ { \ if (len > 20) \ { \ if (PUT (s, string, len) != len) \ return -1; \ done += len; \ } \ else \ { \ register const char *cp = string; \ register int l = len; \ while (l-- > 0) \ outchar (*cp++); \ } \ } while (0) /* Helper function to provide temporary buffering for unbuffered streams. */ static int buffered_vfprintf __P ((FILE *stream, const char *fmt, va_list)); static printf_function printf_unknown; extern printf_function **__printf_function_table; static char *group_number __P ((char *, char *, const char *, wchar_t)); int vfprintf (s, format, ap) register FILE *s; const char *format; va_list ap; { /* The character used as thousands separator. */ wchar_t thousands_sep; /* The string describing the size of groups of digits. */ const char *grouping; /* Array with information about the needed arguments. This has to be dynamically extendable. */ size_t nspecs; size_t nspecs_max; struct printf_spec *specs; /* The number of arguments the format string requests. This will determine the size of the array needed to store the argument attributes. */ size_t nargs; int *args_type; union printf_arg *args_value; /* Positional parameters refer to arguments directly. This could also determine the maximum number of arguments. Track the maximum number. */ size_t max_ref_arg; /* End of leading constant string. */ const char *lead_str_end; /* Number of characters written. */ register size_t done = 0; /* Running pointer through format string. */ const char *f; /* Just a counter. */ int cnt; ARGCHECK (s, format); if (UNBUFFERED_P (s)) /* Use a helper function which will allocate a local temporary buffer for the stream and then call us again. */ return buffered_vfprintf (s, format, ap); /* Reset multibyte characters to their initial state. */ (void) mblen ((char *) NULL, 0); /* Figure out the thousands separator character. */ if (mbtowc (&thousands_sep, _NL_CURRENT (LC_NUMERIC, THOUSANDS_SEP), strlen (_NL_CURRENT (LC_NUMERIC, THOUSANDS_SEP))) <= 0) thousands_sep = (wchar_t) *_NL_CURRENT (LC_NUMERIC, THOUSANDS_SEP); grouping = _NL_CURRENT (LC_NUMERIC, GROUPING); if (*grouping == '\0' || *grouping == CHAR_MAX || thousands_sep == L'\0') grouping = NULL; nspecs_max = 32; /* A more or less arbitrary start value. */ specs = alloca (nspecs_max * sizeof (struct printf_spec)); nspecs = 0; nargs = 0; max_ref_arg = 0; /* Find the first format specifier. */ lead_str_end = find_spec (format); for (f = lead_str_end; *f != '\0'; f = specs[nspecs++].next_fmt) { if (nspecs >= nspecs_max) { /* Extend the array of format specifiers. */ struct printf_spec *old = specs; nspecs_max *= 2; specs = alloca (nspecs_max * sizeof (struct printf_spec)); if (specs == &old[nspecs]) /* Stack grows up, OLD was the last thing allocated; extend it. */ nspecs_max += nspecs_max / 2; else { /* Copy the old array's elements to the new space. */ memcpy (specs, old, nspecs * sizeof (struct printf_spec)); if (old == &specs[nspecs]) /* Stack grows down, OLD was just below the new SPECS. We can use that space when the new space runs out. */ nspecs_max += nspecs_max / 2; } } /* Parse the format specifier. */ nargs += parse_one_spec (f, nargs, &specs[nspecs], &max_ref_arg); } /* Determine the number of arguments the format string consumes. */ nargs = MAX (nargs, max_ref_arg); /* Allocate memory for the argument descriptions. */ args_type = alloca (nargs * sizeof (int)); args_value = alloca (nargs * sizeof (union printf_arg)); /* XXX Could do sanity check here: Initialize args_type elts to zero. If any is still zero after this loop, format is invalid. */ /* Fill in the types of all the arguments. */ for (cnt = 0; cnt < nspecs; ++cnt) { /* If the width is determined by an argument this is an int. */ if (specs[cnt].width_arg != -1) args_type[specs[cnt].width_arg] = PA_INT; /* If the precision is determined by an argument this is an int. */ if (specs[cnt].prec_arg != -1) args_type[specs[cnt].prec_arg] = PA_INT; switch (specs[cnt].ndata_args) { case 0: /* No arguments. */ break; case 1: /* One argument; we already have the type. */ args_type[specs[cnt].data_arg] = specs[cnt].data_arg_type; break; default: /* We have more than one argument for this format spec. We must call the arginfo function again to determine all the types. */ (void) (*__printf_arginfo_table[specs[cnt].info.spec]) (&specs[cnt].info, specs[cnt].ndata_args, &args_type[specs[cnt].data_arg]); break; } } /* Now we know all the types and the order. Fill in the argument values. */ for (cnt = 0; cnt < nargs; ++cnt) switch (args_type[cnt]) { #define T(tag, mem, type) \ case tag: \ args_value[cnt].mem = va_arg (ap, type); \ break T (PA_CHAR, pa_char, int); /* Promoted. */ T (PA_INT|PA_FLAG_SHORT, pa_short_int, int); /* Promoted. */ T (PA_INT, pa_int, int); T (PA_INT|PA_FLAG_LONG, pa_long_int, long int); T (PA_INT|PA_FLAG_LONG_LONG, pa_long_long_int, long long int); T (PA_FLOAT, pa_float, double); /* Promoted. */ T (PA_DOUBLE, pa_double, double); T (PA_DOUBLE|PA_FLAG_LONG_DOUBLE, pa_long_double, long double); T (PA_STRING, pa_string, const char *); T (PA_POINTER, pa_pointer, void *); #undef T default: if ((args_type[cnt] & PA_FLAG_PTR) != 0) args_value[cnt].pa_pointer = va_arg (ap, void *); break; } /* Write the literal text before the first format. */ outstring (format, lead_str_end - format); /* Now walk through all format specifiers and process them. */ for (cnt = 0; cnt < nspecs; ++cnt) { printf_function *function; /* Auxiliary function to do output. */ int is_neg; /* Decimal integer is negative. */ int base; /* Base of a number to be written. */ unsigned long long int num; /* Integral number to be written. */ const char *str; /* String to be written. */ char errorbuf[1024]; /* Buffer sometimes used by %m. */ if (specs[cnt].width_arg != -1) { /* Extract the field width from an argument. */ specs[cnt].info.width = args_value[specs[cnt].width_arg].pa_int; if (specs[cnt].info.width < 0) /* If the width value is negative left justification is selected and the value is taken as being positive. */ { specs[cnt].info.width = -specs[cnt].info.width; specs[cnt].info.left = 1; } } if (specs[cnt].prec_arg != -1) { /* Extract the precision from an argument. */ specs[cnt].info.prec = args_value[specs[cnt].prec_arg].pa_int; if (specs[cnt].info.prec < 0) /* If the precision is negative the precision is omitted. */ specs[cnt].info.prec = -1; } /* Check for a user-defined handler for this spec. */ function = (__printf_function_table == NULL ? NULL : __printf_function_table[specs[cnt].info.spec]); if (function != NULL) use_function: /* Built-in formats with helpers use this. */ { int function_done; unsigned int i; const void *ptr[specs[cnt].ndata_args]; /* Fill in an array of pointers to the argument values. */ for (i = 0; i < specs[cnt].ndata_args; ++i) ptr[i] = &args_value[specs[cnt].data_arg + i]; /* Call the function. */ function_done = (*function) (s, &specs[cnt].info, ptr); /* If an error occured don't do any further work. */ if (function_done < 0) return -1; done += function_done; } else switch (specs[cnt].info.spec) { case '%': /* Write a literal "%". */ outchar ('%'); break; case 'i': case 'd': { long long int signed_num; /* Decimal integer. */ base = 10; if (specs[cnt].info.is_longlong) signed_num = args_value[specs[cnt].data_arg].pa_long_long_int; else if (specs[cnt].info.is_long) signed_num = args_value[specs[cnt].data_arg].pa_long_int; else if (!specs[cnt].info.is_short) signed_num = args_value[specs[cnt].data_arg].pa_int; else signed_num = args_value[specs[cnt].data_arg].pa_short_int; is_neg = signed_num < 0; num = is_neg ? (- signed_num) : signed_num; goto number; } case 'u': /* Decimal unsigned integer. */ base = 10; goto unsigned_number; case 'o': /* Octal unsigned integer. */ base = 8; goto unsigned_number; case 'X': /* Hexadecimal unsigned integer. */ case 'x': /* Hex with lower-case digits. */ base = 16; unsigned_number: /* Unsigned number of base BASE. */ if (specs[cnt].info.is_longlong) num = args_value[specs[cnt].data_arg].pa_u_long_long_int; else if (specs[cnt].info.is_long) num = args_value[specs[cnt].data_arg].pa_u_long_int; else if (!specs[cnt].info.is_short) num = args_value[specs[cnt].data_arg].pa_u_int; else num = args_value[specs[cnt].data_arg].pa_u_short_int; /* ANSI only specifies the `+' and ` ' flags for signed conversions. */ is_neg = 0; specs[cnt].info.showsign = 0; specs[cnt].info.space = 0; number: /* Number of base BASE. */ { char work[BUFSIZ]; char *const workend = &work[sizeof(work) - 1]; register char *w; /* Supply a default precision if none was given. */ if (specs[cnt].info.prec == -1) specs[cnt].info.prec = 1; /* Put the number in WORK. */ w = _itoa (num, workend + 1, base, specs[cnt].info.spec == 'X'); w -= 1; if (specs[cnt].info.group && grouping) w = group_number (w, workend, grouping, thousands_sep); specs[cnt].info.width -= workend - w; specs[cnt].info.prec -= workend - w; if (num != 0 && specs[cnt].info.alt && base == 8 && specs[cnt].info.prec <= 0) { /* Add octal marker. */ *w-- = '0'; --specs[cnt].info.width; } if (specs[cnt].info.prec > 0) { /* Add zeros to the precision. */ specs[cnt].info.width -= specs[cnt].info.prec; while (specs[cnt].info.prec-- > 0) *w-- = '0'; } if (num != 0 && specs[cnt].info.alt && base == 16) /* Account for 0X hex marker. */ specs[cnt].info.width -= 2; if (is_neg || specs[cnt].info.showsign || specs[cnt].info.space) --specs[cnt].info.width; if (!specs[cnt].info.left && specs[cnt].info.pad == ' ') PAD (' '); if (is_neg) outchar ('-'); else if (specs[cnt].info.showsign) outchar ('+'); else if (specs[cnt].info.space) outchar (' '); if (num != 0 && specs[cnt].info.alt && base == 16) { outchar ('0'); outchar (specs[cnt].info.spec); } if (!specs[cnt].info.left && specs[cnt].info.pad == '0') PAD ('0'); /* Write the number. */ while (++w <= workend) outchar (*w); if (specs[cnt].info.left) PAD (' '); } break; case 'e': case 'E': case 'f': case 'g': case 'G': { /* Floating-point number. This is handled by printf_fp.c. */ extern printf_function __printf_fp; function = __printf_fp; goto use_function; } case 'c': /* Character. */ --specs[cnt].info.width;/* Account for the character itself. */ if (!specs[cnt].info.left) PAD (' '); outchar ((unsigned char) args_value[specs[cnt].data_arg].pa_char); if (specs[cnt].info.left) PAD (' '); break; case 's': { static const char null[] = "(null)"; size_t len; str = args_value[specs[cnt].data_arg].pa_string; string: if (str == NULL) { /* Write "(null)" if there's space. */ if (specs[cnt].info.prec == -1 || specs[cnt].info.prec >= (int) sizeof (null) - 1) { str = null; len = sizeof (null) - 1; } else { str = ""; len = 0; } } else if (specs[cnt].info.prec != -1) { /* Search for the end of the string, but don't search past the length specified by the precision. */ const char *end = memchr (str, '\0', specs[cnt].info.prec); if (end) len = end - str; else len = specs[cnt].info.prec; } else len = strlen (str); specs[cnt].info.width -= len; if (!specs[cnt].info.left) PAD (' '); outstring (str, len); if (specs[cnt].info.left) PAD (' '); } break; case 'p': /* Generic pointer. */ { const void *ptr; ptr = args_value[specs[cnt].data_arg].pa_pointer; if (ptr != NULL) { /* If the pointer is not NULL, write it as a %#x spec. */ base = 16; num = (unsigned long long int) (unsigned long int) ptr; is_neg = 0; specs[cnt].info.alt = 1; specs[cnt].info.spec = 'x'; specs[cnt].info.group = 0; goto number; } else { /* Write "(nil)" for a nil pointer. */ str = "(nil)"; /* Make sure the full string "(nil)" is printed. */ if (specs[cnt].info.prec < 5) specs[cnt].info.prec = 5; goto string; } } break; case 'n': /* Answer the count of characters written. */ if (specs[cnt].info.is_longlong) *(long long int *) args_value[specs[cnt].data_arg].pa_pointer = done; else if (specs[cnt].info.is_long) *(long int *) args_value[specs[cnt].data_arg].pa_pointer = done; else if (!specs[cnt].info.is_short) *(int *) args_value[specs[cnt].data_arg].pa_pointer = done; else *(short int *) args_value[specs[cnt].data_arg].pa_pointer = done; break; case 'm': { extern char *_strerror_internal __P ((int, char *buf, size_t)); str = _strerror_internal (errno, errorbuf, sizeof errorbuf); goto string; } default: /* Unrecognized format specifier. */ function = printf_unknown; goto use_function; } /* Write the following constant string. */ outstring (specs[cnt].end_of_fmt, specs[cnt].next_fmt - specs[cnt].end_of_fmt); } return done; } /* Handle an unknown format specifier. This prints out a canonicalized representation of the format spec itself. */ static int printf_unknown (s, info, args) FILE *s; const struct printf_info *info; const void **const args; { int done = 0; char work[BUFSIZ]; char *const workend = &work[sizeof(work) - 1]; register char *w; outchar ('%'); if (info->alt) outchar ('#'); if (info->group) outchar ('\''); if (info->showsign) outchar ('+'); else if (info->space) outchar (' '); if (info->left) outchar ('-'); if (info->pad == '0') outchar ('0'); if (info->width != 0) { w = _itoa (info->width, workend + 1, 10, 0); while (++w <= workend) outchar (*w); } if (info->prec != -1) { outchar ('.'); w = _itoa (info->prec, workend + 1, 10, 0); while (++w <= workend) outchar (*w); } if (info->spec != '\0') outchar (info->spec); return done; } /* Group the digits according to the grouping rules of the current locale. The interpretation of GROUPING is as in `struct lconv' from . */ static char * group_number (char *w, char *workend, const char *grouping, wchar_t thousands_sep) { int len; char *src, *s; /* We treat all negative values like CHAR_MAX. */ if (*grouping == CHAR_MAX || *grouping < 0) /* No grouping should be done. */ return w; len = *grouping; /* Copy existing string so that nothing gets overwritten. */ src = (char *) alloca (workend - w); memcpy (src, w + 1, workend - w); s = &src[workend - w - 1]; w = workend; /* Process all characters in the string. */ while (s >= src) { *w-- = *s--; if (--len == 0 && s >= src) { /* A new group begins. */ *w-- = thousands_sep; len = *grouping++; if (*grouping == '\0') /* The previous grouping repeats ad infinitum. */ --grouping; else if (*grouping == CHAR_MAX || *grouping < 0) { /* No further grouping to be done. Copy the rest of the number. */ do *w-- = *s--; while (s >= src); break; } } } return w; } #ifdef USE_IN_LIBIO /* Helper "class" for `fprintf to unbuffered': creates a temporary buffer. */ struct helper_file { struct _IO_FILE_plus _f; _IO_FILE *_put_stream; }; static int _IO_helper_overflow (s, c) _IO_FILE *s; int c; { _IO_FILE *target = ((struct helper_file*) s)->_put_stream; int used = s->_IO_write_ptr - s->_IO_write_base; if (used) { _IO_size_t written = _IO_sputn (target, s->_IO_write_base, used); s->_IO_write_ptr -= written; } return _IO_putc (c, s); } static const struct _IO_jump_t _IO_helper_jumps = { JUMP_INIT_DUMMY, JUMP_INIT (finish, _IO_default_finish), JUMP_INIT (overflow, _IO_helper_overflow), JUMP_INIT (underflow, _IO_default_underflow), JUMP_INIT (uflow, _IO_default_uflow), JUMP_INIT (pbackfail, _IO_default_pbackfail), JUMP_INIT (xsputn, _IO_default_xsputn), JUMP_INIT (xsgetn, _IO_default_xsgetn), JUMP_INIT (seekoff, _IO_default_seekoff), JUMP_INIT (seekpos, _IO_default_seekpos), JUMP_INIT (setbuf, _IO_default_setbuf), JUMP_INIT (sync, _IO_default_sync), JUMP_INIT (doallocate, _IO_default_doallocate), JUMP_INIT (read, _IO_default_read), JUMP_INIT (write, _IO_default_write), JUMP_INIT (seek, _IO_default_seek), JUMP_INIT (close, _IO_default_close), JUMP_INIT (stat, _IO_default_stat) }; static int buffered_vfprintf (s, format, args) register _IO_FILE *s; char const *format; _IO_va_list args; { char buf[_IO_BUFSIZ]; struct helper_file helper; register _IO_FILE *hp = (_IO_FILE *) &helper; int result, to_flush; /* Initialize helper. */ helper._put_stream = s; hp->_IO_write_base = buf; hp->_IO_write_ptr = buf; hp->_IO_write_end = buf + sizeof buf; hp->_IO_file_flags = _IO_MAGIC|_IO_NO_READS; _IO_JUMPS (hp) = (struct _IO_jump_t *) &_IO_helper_jumps; /* Now print to helper instead. */ result = _IO_vfprintf (hp, format, args); /* Now flush anything from the helper to the S. */ if ((to_flush = hp->_IO_write_ptr - hp->_IO_write_base) > 0) { if (_IO_sputn (s, hp->_IO_write_base, to_flush) != to_flush) return -1; } return result; } #else /* !USE_IN_LIBIO */ static int buffered_vfprintf (s, format, args) register FILE *s; char const *format; va_list args; { char buf[BUFSIZ]; int result; s->__bufp = s->__buffer = buf; s->__bufsize = sizeof buf; s->__put_limit = s->__buffer + s->__bufsize; s->__get_limit = s->__buffer; /* Now use buffer to print. */ result = vfprintf (s, format, args); if (fflush (s) == EOF) result = -1; s->__buffer = s->__bufp = s->__get_limit = s->__put_limit = NULL; s->__bufsize = 0; return result; } /* Pads string with given number of a specified character. This code is taken from iopadn.c of the GNU I/O library. */ #define PADSIZE 16 static const char blanks[PADSIZE] = {' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '}; static const char zeroes[PADSIZE] = {'0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0'}; ssize_t __printf_pad (s, pad, count) FILE *s; char pad; size_t count; { const char *padptr; register size_t i; padptr = pad == ' ' ? blanks : zeroes; for (i = count; i >= PADSIZE; i -= PADSIZE) if (PUT (s, padptr, PADSIZE) != PADSIZE) return -1; if (i > 0) if (PUT (s, padptr, i) != i) return -1; return count; } #undef PADSIZE #endif /* USE_IN_LIBIO */