Files
seta75D d6fe8fe829 Init
2021-10-11 22:19:34 -03:00

606 lines
13 KiB
C

static char sccsid[] = "@(#)09 1.14 src/bos/usr/bin/printf/printf.c, cmdposix, bos41J, 9523C_all 6/8/95 10:38:57";
/*
* COMPONENT_NAME: (CMDPOSIX) new commands required by Posix 1003.2
*
* FUNCTIONS: printf
*
* ORIGINS: 27, 71
*
* This module contains IBM CONFIDENTIAL code. -- (IBM
* Confidential Restricted when combined with the aggregated
* modules for this product)
* SOURCE MATERIALS
* (C) COPYRIGHT International Business Machines Corp. 1985, 1995
* All Rights Reserved
*
* US Government Users Restricted Rights - Use, duplication or
* disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
*
* (c) Copyright 1990, 1991, 1992 OPEN SOFTWARE FOUNDATION, INC.
* ALL RIGHTS RESERVED
*
* OSF/1 1.1
*
*/
#define _ILS_MACROS
#include <ctype.h>
#include <errno.h>
#include <math.h>
#include <stdio.h>
#include <string.h>
#include <locale.h>
#include <nl_types.h>
#include <mbstr.h>
#include <stdlib.h>
#include <stdarg.h>
#include "printf_msg.h"
#define MAXSTR ARG_MAX /* max string to be output */
static nl_catd catd;
#define MSGSTR(num,str) catgets(catd,MS_PRINTF,num,str)
static void escwork(char *destin, char *source, int isarg);
static int doargs(char **fmtptr, char *args);
static int finishline(char *fmtptr);
static void p_out(const char *format, int type, ...);
static int convchar(char *, char **);
/* types for p_out (0 is default) */
#define DBL 1
static int error = 0;
static int cflag = 0;
static char outstr[MAXSTR + 1]; /* output string */
main(int argc, char **argv)
{
char *fmtptr, *restart; /* pointer to format */
int did_conv;
(void) setlocale(LC_ALL,"");
catd = catopen(MF_PRINTF, NL_CAT_LOCALE);
/* handle -- argument for XOPEN standards compliance */
if (strcmp(argv[1], "--") == 0) {
argv++;
argc--;
}
if (argv[1]) {
fmtptr = argv[1];
restart = argv[1];
}
else
exit(0);
/*
* If no format specification, simply printf format string
*/
if (!strchr(fmtptr, '%')) {
escwork(NULL, fmtptr, 0);
exit(0);
}
/*
* Escape sequences have been translated. Now process
* format arguments.
*/
argv += 2;
did_conv = 0;
while (*argv && !cflag){
int rc;
errno = 0;
if ((rc=doargs(&fmtptr, *argv)) == 1) {
/* ending format string is a string */
if (!did_conv)
break;
fmtptr = restart;
did_conv = 0;
} else if (rc == 2)
/* invalid conversion */
break;
else {
argv++;
did_conv = 1;
}
}
/*
* Check to see if 'format' is done. if not transfer the
* rest of the 'format' to stdout.
*/
if (!cflag && strlen(fmtptr))
finishline(fmtptr);
exit(error);
}
/*
* escwork
*
* This routine transforms octal numbers and backslash sequences to the
* correct character representations and sends the resulting string
* to stdout.
*
*/
void
escwork(char *destin, char *source, int isarg)
/* char *destin; pointer to destination */
/* char *source; pointer to source */
/* int isarg; format/argument flag */
{
int mbcnt, j;
wchar_t wd;
char *pos = destin;
for(; *source; source += (mbcnt > 0) ? mbcnt : 1) {
mbcnt = mbtowc(&wd, source, MB_CUR_MAX);
if (mbcnt == 1 && wd == '\\') {
/*
* process escape sequences
*/
switch(*++source) {
case 'a':
wd = '\a';
break;
case 'b':
wd = '\b';
break;
case 'f':
wd = '\f';
break;
case 'n':
wd = '\n';
break;
case 'r':
wd = '\r';
break;
case 't':
wd = '\t';
break;
case 'v':
wd = '\v';
break;
case '\\':
wd = '\\';
break;
case '0': case '1': case '2': case '3':
case '4': case '5': case '6': case '7':
j = wd = 0;
if ((isarg) && (*source == '0'))
source++;
while ((*source >= '0' &&
*source <= '7') && j++ < 3) {
wd <<= 3;
wd |= (*source - '0');
source++;
}
source--;
break;
case 'c':
if (isarg) {
cflag++;
return;
}
/* else fall through */
default:
--source;
} /* end of switch */
} /* end of if */
if (destin) {
mbcnt = wctomb(pos, wd);
pos += (mbcnt > 0) ? mbcnt : 1;
} else {
putwc(wd, stdout);
}
} /* end of for */
if (destin)
*pos++ = '\0';
return;
}
/*
* doargs
*
* This routine does the actual formatting of the input arguments.
*
* This routine handles the format of the following form:
* %pw.df
* 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.
*/
int
doargs(char **fmtptr, char *args)
/* char **fmtptr; format string */
/* char *args; argument to process */
{
char tmpchar, *last;
char *ptr;
char *end;
long lnum;
double fnum;
int percent; /* flag for "%" */
int width; /* flag for width */
percent = 0;
/*
* "%" indicates a conversion is about to happen. This section
* parses for a "%"
*/
for (last = *fmtptr; *last != '\0'; last++) {
if (!percent) {
if (*last == '%') {
percent++;
tmpchar = *last;
*last = '\0';
escwork(NULL, *fmtptr, 0);
*last = tmpchar;
*fmtptr = last;
}
continue;
}
/*
* '%' has been found check the next character for conversion.
*/
switch (*last) {
case '%':
percent = 0;
printf("%%");
*fmtptr = last+1;
continue;
case 'x':
case 'X':
case 'd':
case 'o':
case 'i':
case 'u':
if ((*args == '\'') || (*args == '"'))
lnum = convchar(args, &ptr);
else if (*last == 'u')
lnum = strtoul(args, &ptr, 0);
else
lnum = strtol(args, &ptr, 0);
if (errno) { /* overflow, underflow or invalid base */
fprintf(stderr, "printf: %s: %s\n",
args, strerror(errno));
error++;
}
else if (strcmp(args, ptr) == 0) {
fprintf(stderr, MSGSTR(BADNUM,
"printf: %s expected numeric value\n"), args);
error++;
}
else if (*ptr) {
fprintf(stderr, MSGSTR(NOCOMP,
"printf: %s not completely converted\n"), args);
error++;
}
tmpchar = *(++last);
*last = '\0';
p_out(*fmtptr, 0, lnum);
*last = tmpchar;
break;
case 'f':
case 'e':
case 'E':
case 'g':
case 'G':
if ((*args == '\'') || (*args == '"'))
fnum = (double) convchar(args, &ptr);
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) {
fprintf(stderr, MSGSTR(BADNUM,
"printf: %s expected numeric value\n"), args);
error++;
}
else if (*ptr) {
fprintf(stderr, MSGSTR(NOCOMP,
"printf: %s not completely converted\n"), args);
error++;
}
else if (errno) { /* overflow, underflow or EDOM */
fprintf(stderr, "printf: %s: %s\n",
args, strerror(errno));
error++;
}
tmpchar = *(++last);
*last = '\0';
p_out(*fmtptr, DBL, fnum);
*last = tmpchar;
break;
case 'c':
tmpchar = *(++last);
*last = '\0';
p_out(*fmtptr, 0, *args);
*last = tmpchar;
break;
case 's':
tmpchar = *(++last);
*last = '\0';
p_out(*fmtptr, 0, args);
*last = tmpchar;
break;
case 'b':
*last = 's';
tmpchar = *(++last);
*last = '\0';
escwork(outstr, args, 1);
p_out(*fmtptr, 0, outstr);
*last = tmpchar;
if (cflag)
return(0);
break;
default: /* 0 flag, width or precision */
if (isdigit(*last)) {
width = strtol(last, &ptr, 0);
if (errno) {
fprintf(stderr, "printf: %s: %s\n",
last, strerror(errno));
error++;
}
if (width > MAXSTR) {
fprintf(stderr,
MSGSTR(LONGLINE, "printf: line too long\n"));
exit(++error);
}
last += ptr - last - 1;
continue;
}
switch (*last) {
case '-':
case '+':
case ' ':
case '#':
case '.':
continue;
default:
tmpchar = *(++last);
*last = '\0';
p_out(*fmtptr, 0);
*last = tmpchar;
break;
}
}
*fmtptr = last;
percent = 0;
return(0);
} /* end of for */
/* finish parsing the format string */
escwork(NULL, *fmtptr, 0);
*fmtptr = last; /* fmtptr points to the end of format string */
if (!strchr(*fmtptr, '%')) { /* the ending format string is a string */
return(1);
} else { /* cannot form a valid conversion */
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.
*/
int
finishline(char *fmtptr)
/* char *fmtptr; format string */
{
char tmpchar, *last;
int percent; /* flag for "%" */
int width;
char *ptr;
/*
* 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.
*/
if (!strchr(fmtptr, '%')) {
escwork(NULL, fmtptr, 0);
return(0);
}
for (percent = 0, last = fmtptr; *last != '\0'; last++) {
if (!percent) {
if (*last == '%') {
percent++;
tmpchar = *last;
*last = '\0';
escwork(NULL, fmtptr, 0);
*last = tmpchar;
fmtptr = last;
}
continue;
}
/*
* OK. '%' has been found check the next character
* for conversion.
*/
switch (*last) {
case '%':
percent = 0;
printf("%%");
fmtptr = last+1;
continue;
case 'x':
case 'X':
case 'd':
case 'o':
case 'i':
case 'u':
tmpchar = *(++last);
*last = '\0';
p_out(fmtptr, 0, 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(fmtptr, DBL, (double)0.0);
*last = tmpchar;
fmtptr = last;
percent = 0;
last--;
break;
case 'b':
case 'c':
case 's':
*last = 's';
tmpchar = *(++last);
*last = '\0';
p_out(fmtptr, 0, "");
*last = tmpchar;
fmtptr = last;
percent = 0;
last--;
break;
default: /* 0 flag, width or precision */
if (isdigit(*last)) {
width = strtol(last, &ptr, 0);
if (errno) {
fprintf(stderr, "printf: %s: %s\n",
last, strerror(errno));
error++;
}
if (width > MAXSTR) {
fprintf(stderr,
MSGSTR(LONGLINE, "printf: line too long\n"));
exit(++error);
}
last += ptr - last - 1;
continue;
}
switch (*last) {
case '-':
case '+':
case ' ':
case '#':
case '.':
continue;
default:
break;
}
}
}
escwork(NULL, fmtptr, 0);
return(0);
}
/*
* p_out
*
* This routine checks if the current output line is longer than
* ARG_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 ARG_MAX bytes, the output will be truncated.
*
*/
void
p_out(const char *format, int type, ...)
{
char tmpstr[MAXSTR + 1]; /* temp. output string for one conversion */
char *tmpptr;
int rc = 0;
va_list ap;
tmpptr = tmpstr;
va_start(ap, type);
switch(type) {
case DBL:
rc = sprintf(tmpptr, format, va_arg(ap, double));
break;
default:
rc = sprintf(tmpptr, format, va_arg(ap, void *));
break;
}
if (rc < 0) {
fprintf(stderr, MSGSTR(SPRTFERR, "printf: bad conversion\n"));
error++;
}
else if (errno && errno != ERANGE && errno != EINVAL && errno != EDOM) {
/* strtol(), strtoul() or strtod() should have reported the error if
errno is ERANGE, EINVAL or EDOM */
perror("printf");
error++;
}
va_end(ap);
printf("%s", tmpptr);
}
/*
* convchar
*
* This routine converts a character to it's codeset equivalent.
*/
int
convchar(char *arg, char **ptr)
/* char *arg; char to convert */
/* char **ptr; end pointer */
{
int len, i, val = 0;
*ptr = arg;
arg++;
if ((len = mblen(arg, MB_CUR_MAX)) < 1)
return 0;
for(i=0; i<len; i++)
val = 256*val + ((int) arg[i]);
*ptr = arg+len;
return val;
}