/* * (C) COPYRIGHT International Business Machines Corp. 1985, 1993 * All Rights Reserved * * (c) Copyright 1990, 1991, 1992 OPEN SOFTWARE FOUNDATION, INC. * ALL RIGHTS RESERVED * * Copyright (c) 1994, 1995 by Sun Microsystems, Inc. * All rights reserved. * */ #pragma ident "@(#)printf.c 1.18 95/03/15 SMI" #include #include #include #include #include #include #include #include #include #define MAXSTR _POSIX2_LINE_MAX /* max string to be output */ /* * This typedef is used as our internal representation of strings. * We need this to avoid problems with real \0 characters being * treated as string terminators. Yuck. */ typedef struct { char *begin; char *end; int bail; } String; static void escwork(char *source, String *Dest); static int doargs(String *Dest, String *Fmtptr, char *args, char *argv[]); static void finishline(String *Dest, String *Fmtptr); static void p_out(String *Dest, const char *format, ...); static char *find_percent(String *s); static void old_printf(char *argv[]); static int error = 0; static char outstr[MAXSTR + 1]; /* output string */ static String Outstr = { outstr, outstr, 0 }; int main(int argc, char **argv) { String Fmt; char *start; int argn; (void) setlocale(LC_ALL, ""); #if !defined(TEXT_DOMAIN) #define TEXT_DOMAIN "SYS_TEST" #endif (void) textdomain(TEXT_DOMAIN); if (argc > 1 && strcmp(argv[1], "--") == 0) { /* XCU4 "--" handling, sigh */ argv++; argc--; } if (argv[1]) { Fmt.begin = argv[1]; Fmt.end = argv[1] + strlen(argv[1]); } else { (void) fprintf(stderr, gettext("Usage: printf format [argument...]\n")); exit(1); } /* * Transform octal numbers and backslash sequences to the correct * character representations and stores the resulting string back * into Fmt.begin. */ escwork(argv[1], &Fmt); /* * If no format specification, simply output the format string */ if (find_percent(&Fmt) == NULL) { (void) fwrite(Fmt.begin, Fmt.end - Fmt.begin, 1, stdout); exit(0); } /* * Escape sequences have been translated. Now process * format arguments. */ start = Fmt.begin; argn = 2; while (argn < argc) { int rc; errno = 0; if ((rc = doargs(&Outstr, &Fmt, argv[argn], argv)) == 1) { /* ending format string is a string */ Fmt.begin = start; } else if (rc == 2) { /* invalid conversion or containing % char(s) */ break; } else if (rc == 3) { /* found a \c, suppress all further */ break; } else argn++; } /* * Check to see if 'format' is done. if not transfer the * rest of the 'format' to output string. */ if (Fmt.begin != Fmt.end) finishline(&Outstr, &Fmt); (void) fwrite(Outstr.begin, Outstr.end - Outstr.begin, 1, stdout); return (error); } /* * escwork * * This routine transforms octal numbers and backslash sequences to the * correct character representations and stores the resulting string * in the String 'Dest'. * * The returned value is a character pointer to the last available position in * destination string, the function itself returns an indication of whether * or not it detected the 'bailout' character \c while parsing the string. */ static void escwork(char *source, /* pointer to source */ String *Dest) /* pointer to destination */ { char *destin; int j; int mbcnt = 0; wchar_t wd; /* * Preserve the underlying string for the sake of '$' arguments. */ Dest->begin = strdup(source); Dest->bail = 0; /* set to 1 when we hit the \c character */ for (destin = Dest->begin; *source; source += (mbcnt > 0) ? mbcnt : 1) { mbcnt = mbtowc(&wd, source, MB_CUR_MAX); if (mbcnt == 1 && wd == '\\') { /* * process escape sequences */ switch (*++source) { case 'a': /* bell/alert */ /*LINTED*/ *destin++ = '\a'; continue; case 'b': *destin++ = '\b'; continue; case 'c': /* no newline, nothing */ Dest->end = destin; Dest->bail = 1; *destin = 0; return; case 'f': *destin++ = '\f'; continue; case 'n': *destin++ = '\n'; continue; case 'r': *destin++ = '\r'; continue; case 't': *destin++ = '\t'; continue; case 'v': *destin++ = '\v'; continue; case '\\': *destin++ = '\\'; continue; case '0': /* 0-prefixed octal chars */ case '1': /* non-0-prefixed octal chars */ case '2': case '3': case '4': case '5': case '6': case '7': /* * the following 2 lines should not be * necessary, but VSC allows for \0ddd */ if (*source == '0') source++; j = wd = 0; while ((*source >= '0' && *source <= '7') && j++ < 3) { wd <<= 3; wd |= (*source++ - '0'); } *destin++ = (char)wd; --source; continue; default: --source; } /* end of switch */ } mbcnt = wctomb(destin, wd); /* normal character */ destin += (mbcnt > 0) ? mbcnt : 1; } /* end of for */ Dest->end = destin; *destin = '\0'; } /* * doargs * * This routine does the actual formatting of the input arguments. * * This routine handles the format of the following form: * %n$pw.df * n: argument number followed by $ * p: prefix, zero or more of {- + # or blank}. * w: width specifier. It is optional. * .: decimal. * d: precision specifier. * A null digit string is treated as zero. * f: format xXioudfeEgGcbs. * * The minimum set required is "%f". Note that "%%" prints one "%" in output. * * RETURN VALUE DESCRIPTION: * 0 forms a valid conversion specification. * 1 the ending format string is a string. * 2 cannot form a valid conversion; or the string contains * literal % char(s). * * NOTE: If a character sequence in the format begins with a % character, * but does not form a valid conversion specification, the doargs() * will pass the invalid format to the sprintf() and let it handle * the situation. */ static int doargs( String *Dest, /* destination string */ String *Fmtptr, /* format string */ char *args, /* argument to process */ char *argv[]) /* full argument list */ { char tmpchar, *last; char *ptr; long lnum; double fnum; int percent; /* flag for "%" */ int width, prec, flag; char *fmt; #define FPLUS 2 #define FMINUS 4 #define FBLANK 8 #define FSHARP 16 #define DOTSEEN 64 #define DIGITSEEN 128 percent = 0; /* * "%" indicates a conversion is about to happen. This section * parses for a "%" */ for (fmt = last = Fmtptr->begin; last < Fmtptr->end; last++) { if (!percent) { if (*last == '%') { percent++; fmt = last; flag = width = prec = 0; } else p_out(Dest, "%c", *last); continue; } /* * '%' has been found check the next character for conversion. */ switch (*last) { case '%': p_out(Dest, "%c", *last); percent = 0; continue; case 'x': case 'X': case 'd': case 'o': case 'i': case 'u': if (*last == 'u') { if (*args == '\'' || *args == '"') { args++; (void) strtoul(args, &ptr, 10); lnum = (int)args[0]; } else lnum = strtoul(args, &ptr, 0); } else { if (*args == '\'' || *args == '"') { args++; (void) strtol(args, &ptr, 10); lnum = (int)args[0]; } else lnum = strtol(args, &ptr, 0); } if (errno) { /* overflow, underflow or invalid base */ (void) fprintf(stderr, "printf: %s: %s\n", args, strerror(errno)); error++; } else if (strcmp(args, ptr) == 0) { (void) fprintf(stderr, gettext( "printf: %s expected numeric value\n"), args); error++; } else if (*ptr != NULL) { (void) fprintf(stderr, gettext( "printf: %s not completely converted\n"), args); error++; } tmpchar = *(++last); *last = '\0'; p_out(Dest, fmt, lnum); *last = tmpchar; break; case 'f': case 'e': case 'E': case 'g': case 'G': if (*args == '\'' || *args == '"') { args++; (void) strtod(args, &ptr); fnum = (int)args[0]; } else fnum = strtod(args, &ptr); /* * strtod() handles error situations somewhat different * from strtoul() and strtol(), e.g., strtod() will set * errno for incomplete conversion, but strtoul() and * strtol() will not. The following error test order * is used in order to have the same behaviour as for * u, d, etc. conversions */ if (strcmp(args, ptr) == 0) { (void) fprintf(stderr, gettext( "printf: %s expected numeric value\n"), args); error++; } else if (*ptr != NULL) { (void) fprintf(stderr, gettext( "printf: %s not completely converted\n"), args); error++; } else if (errno) { /* overflow, underflow or EDOM */ (void) fprintf(stderr, "printf: %s: %s\n", args, strerror(errno)); error++; } tmpchar = *(++last); *last = '\0'; p_out(Dest, fmt, fnum); *last = tmpchar; break; case 'c': if (*args == '\0') { last++; /* printf %c "" => no output */ break; } tmpchar = *(++last); *last = '\0'; p_out(Dest, fmt, *args); *last = tmpchar; break; case 's': tmpchar = *(++last); *last = '\0'; p_out(Dest, fmt, args); *last = tmpchar; break; case 'b': { int pre = 0, post = 0; String Arg, *a = &Arg; char *begin, *end; /* * XXX Sigh - %b is -far- too complex. */ escwork(args, a); begin = a->begin; end = a->end; /* * The 'precision' specifies the minimum number of * chars to be eaten from the string. We need to * check for multibyte characters in case we truncate * one in the middle. Oops. */ if (flag & DOTSEEN) { char *p; int mbcnt, count; wchar_t wd; count = 0; /*LINTED [bogus used-before-set: 1094364]*/ for (p = begin; p < end; p += mbcnt) { mbcnt = mbtowc(&wd, p, MB_CUR_MAX); if (mbcnt <= 0) mbcnt = 1; if (count + mbcnt > prec) end = p; count += mbcnt; } } /* * The 'width' specifies the minimum width, padded * with spaces */ if ((end - begin) < width) { if (flag & FMINUS) { /* left-justified */ post = width - (end - begin); } else { /* right justified */ pre = width - (end - begin); } } /* * write it all out */ if (pre) p_out(Dest, "%*s", pre, ""); while (begin < end) p_out(Dest, "%c", *begin++); if (post) p_out(Dest, "%*s", post, ""); free(a->begin); if (a->bail) { /* * escwork detected the "give up now" * character, bailing at that point in * the string. */ Fmtptr->begin = Fmtptr->end; return (3); /*NOTREACHED*/ } last++; } break; default: /* 0 flag, width or precision */ if (isdigit(*last)) { int value = strtol(last, &ptr, 0); if (errno) { (void) fprintf(stderr, "printf: %s: %s\n", last, strerror(errno)); error++; } flag |= DIGITSEEN; if (flag & DOTSEEN) prec = value; else width = value; if (width > MAXSTR) { (void) fprintf(stderr, gettext("printf: line too long\n")); exit(++error); } last += ptr - last - 1; continue; } switch (*last) { case '-': flag |= FMINUS; continue; case '+': flag |= FPLUS; continue; case ' ': flag |= FBLANK; continue; case '#': flag |= FSHARP; continue; case '.': flag |= DOTSEEN; continue; case '$': /* * This is only allowed for compatibility * with the SVR4 base version of printf. * * Once we see that the format specification * contains a '$', we know that it must be * an 'old' usage of printf, so we simply * discard all the work we've done so far, * and behave exactly the same way the old * printf used to do. */ if (flag == DIGITSEEN) { old_printf(argv); /*NOTREACHED*/ } (void) fprintf(stderr, gettext("printf: bad '$' argument\n")); error++; /*FALLTHROUGH*/ default: tmpchar = *(++last); *last = '\0'; p_out(Dest, fmt); *last = tmpchar; break; } #undef DIGITSEEN #undef DOTSEEN #undef FSHARP #undef FBLANK #undef FMINUS #undef FPLUS } Fmtptr->begin = last; return (0); } /* end of for */ if (find_percent(Fmtptr) == NULL) { /* * Check for the 'bailout' character .. */ if (Fmtptr->bail) return (3); /* * the ending format string is a string * fmtptr points to the end of format string */ Fmtptr->begin = last; return (1); } else { /* * cannot form a valid conversion; or * a string containing literal % char(s) * fmtptr points to the end of format string */ Fmtptr->begin = last; return (2); } } /* * finishline * * This routine finishes processing the extra format specifications * * If a character sequence in the format begins with a % character, * but does not form a valid conversion specification, nothing will * be written to output string. */ static void finishline( String *Dest, /* destination string */ String *Fmtptr) /* format string */ { char tmpchar, *last; int percent; /* flag for "%" */ int width; char *ptr; char *fmtptr; /* * Check remaining format for "%". If no "%", transfer * line to output. If found "%" replace with null for %s or * %c, replace with 0 for all others. */ percent = 0; for (last = fmtptr = Fmtptr->begin; last != Fmtptr->end; last++) { if (!percent) { if (*last == '%') { percent++; fmtptr = last; } else p_out(Dest, "%c", *last); continue; } /* * OK. '%' has been found check the next character * for conversion. */ switch (*last) { case '%': p_out(Dest, "%%"); percent = 0; continue; case 'x': case 'X': case 'd': case 'o': case 'i': case 'u': tmpchar = *(++last); *last = '\0'; p_out(Dest, fmtptr, 0); *last = tmpchar; fmtptr = last; percent = 0; last--; break; case 'f': case 'e': case 'E': case 'g': case 'G': tmpchar = *(++last); *last = '\0'; p_out(Dest, fmtptr, 0.0); *last = tmpchar; fmtptr = last; percent = 0; last--; break; case 'b': case 'c': case 's': *last = 's'; tmpchar = *(++last); *last = '\0'; p_out(Dest, fmtptr, ""); *last = tmpchar; fmtptr = last; percent = 0; last--; break; default: /* 0 flag, width or precision */ if (isdigit(*last)) { width = strtol(last, &ptr, 0); if (errno) { (void) fprintf(stderr, "printf: %s: %s\n", last, strerror(errno)); error++; } if (width > MAXSTR) { (void) fprintf(stderr, gettext("printf: line too long\n")); exit(++error); } last += ptr - last - 1; continue; } switch (*last) { case '-': case '+': case ' ': case '#': case '.': continue; default: break; } } } } /* * p_out * * This routine checks if the current output line is longer than * LINE_MAX bytes. If so, outputs the current line and exits with * non-zero value; otherwise, do the conversion. If the result * of the conversion has no error, copy the result to the output * buffer. If the result of the conversion plus the current output * line is longer than 2048 bytes, the output will be truncated. * */ static void p_out(String *s, const char *format, ...) { char tmpstr[MAXSTR + 1]; /* temp. output string for one conversion */ char *tmpptr; int rc = 0; int line; va_list ap; if ((line = s->end - s->begin) >= MAXSTR) { (void) fprintf(stderr, gettext("printf: line too long\n")); (void) printf("%s", outstr); exit(++error); } tmpptr = tmpstr; va_start(ap, format); rc = vsprintf(tmpptr, format, ap); if (rc < 0) { (void) fprintf(stderr, gettext("printf: bad conversion\n")); rc = 0; error++; } else if (errno != 0 && errno != ERANGE && errno != EINVAL && errno != EDOM) { /* * strtol(), strtoul() or strtod() should've reported * the error if errno is ERANGE, EINVAL or EDOM */ perror("printf"); error++; } else if ((rc + line) > MAXSTR) rc = MAXSTR - line; va_end(ap); s->end = (char *)memcpy(s->end, tmpptr, rc) + rc; } static char * find_percent(String *s) { int mbcnt; wchar_t wd; char *p; /*LINTED [bogus used-before-set: 1094364]*/ for (p = s->begin; p != s->end; p += (mbcnt > 0) ? mbcnt : 1) { mbcnt = mbtowc(&wd, p, MB_CUR_MAX); if (mbcnt == 1 && wd == '%') return (p); } return (NULL); } #include /* * This is the printf from 5.0 -> 5.4 * This version is only used when '$' format specifiers are detected * (in which case all bets are off w.r.t. the rest of format functionality) */ static void old_printf(char *argv[]) { char *fmt; if ((fmt = strdup(argv[1])) == (char *)0) { (void) fprintf(stderr, gettext("malloc failed\n")); exit(1); } (void) strccpy(fmt, argv[1]); (void) printf(fmt, argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], argv[14], argv[15], argv[16], argv[17], argv[18], argv[19], argv[20]); exit(0); }