mirror of
git://git.code.sf.net/p/cdesktopenv/code
synced 2025-02-24 23:14:14 +00:00
This allows scripts to check for a nonzero exit status on the 'print', 'printf' and 'echo' builtins and prevent possible infinite loops if SIGPIPE is ignored. sfsync() was already returning a negative value on I/O error, so all we need to do is add a check. The stream buffer will need to be filled before an I/O error can be detected, but this is the same on other shells. See manual page: src/lib/libast/man/sfio.3 Ref.: https://github.com/att/ast/issues/1093 https://github.com/att/ast/pull/1363 src/cmd/ksh93/bltins/print.c: b_print(): - Make sure an error result from sfsync() is reflected in the output builtin's exit status (exitval). - Write an I/O error message (e_io) if the exit status is nonzero. src/cmd/ksh93/data/msg.c, src/cmd/ksh93/include/io.h: - Add the e_io[] error message. src/cmd/ksh93/tests/builtins.sh: - Add I/O error regression test, checking for the correct error message and exit status. All three output builtins use the same b_print() function so we only need to test one. src/cmd/ksh93/tests/basic.sh, src/cmd/ksh93/tests/coprocess.sh: - Redirect stderr on a few 'print' commands to /dev/null; these now issue an expected I/O error. This does not cause failures. NEWS, TODO: - Update. (cherry picked from commit 9011fa933552e483dab460f7dd1593d64e059d94)
1128 lines
22 KiB
C
1128 lines
22 KiB
C
/***********************************************************************
|
|
* *
|
|
* This software is part of the ast package *
|
|
* Copyright (c) 1982-2012 AT&T Intellectual Property *
|
|
* and is licensed under the *
|
|
* Eclipse Public License, Version 1.0 *
|
|
* by AT&T Intellectual Property *
|
|
* *
|
|
* A copy of the License is available at *
|
|
* http://www.eclipse.org/org/documents/epl-v10.html *
|
|
* (with md5 checksum b35adb5213ca9657e911e9befb180842) *
|
|
* *
|
|
* Information and Software Systems Research *
|
|
* AT&T Research *
|
|
* Florham Park NJ *
|
|
* *
|
|
* David Korn <dgk@research.att.com> *
|
|
* *
|
|
***********************************************************************/
|
|
#pragma prototyped
|
|
/*
|
|
* echo [arg...]
|
|
* print [-nrps] [-f format] [-u filenum] [arg...]
|
|
* printf format [arg...]
|
|
*
|
|
* David Korn
|
|
* AT&T Labs
|
|
*/
|
|
|
|
#include "defs.h"
|
|
#include <error.h>
|
|
#include <stak.h>
|
|
#include "io.h"
|
|
#include "name.h"
|
|
#include "history.h"
|
|
#include "builtins.h"
|
|
#include "streval.h"
|
|
#include <tmx.h>
|
|
#include <ccode.h>
|
|
|
|
union types_t
|
|
{
|
|
unsigned char c;
|
|
short h;
|
|
int i;
|
|
long l;
|
|
Sflong_t ll;
|
|
Sfdouble_t ld;
|
|
double d;
|
|
float f;
|
|
char *s;
|
|
int *ip;
|
|
char **p;
|
|
};
|
|
|
|
struct printf
|
|
{
|
|
Sffmt_t hdr;
|
|
int argsize;
|
|
int intvar;
|
|
char **nextarg;
|
|
char *lastarg;
|
|
char cescape;
|
|
char err;
|
|
Shell_t *sh;
|
|
};
|
|
|
|
struct printmap
|
|
{
|
|
size_t size;
|
|
char *name;
|
|
char map[3];
|
|
const char *description;
|
|
};
|
|
|
|
const struct printmap Pmap[] =
|
|
{
|
|
3, "csv", "q+", "Equivalent to %#q",
|
|
4, "html", "H", "Equivalent to %H",
|
|
3, "ere", "R", "Equivalent to %R",
|
|
7, "pattern","P", "Equivalent to %#P",
|
|
3, "url", "H+", "Equivalent to %#H",
|
|
0, 0, 0,
|
|
};
|
|
|
|
|
|
static int extend(Sfio_t*,void*, Sffmt_t*);
|
|
static const char preformat[] = "";
|
|
static char *genformat(char*);
|
|
static int fmtvecho(const char*, struct printf*);
|
|
static ssize_t fmtbase64(Sfio_t*, char*, int);
|
|
|
|
struct print
|
|
{
|
|
Shell_t *sh;
|
|
const char *options;
|
|
char raw;
|
|
char echon;
|
|
};
|
|
|
|
static char* nullarg[] = { 0, 0 };
|
|
|
|
#if !SHOPT_ECHOPRINT
|
|
int B_echo(int argc, char *argv[],Shbltin_t *context)
|
|
{
|
|
static char bsd_univ;
|
|
struct print prdata;
|
|
prdata.options = sh_optecho+5;
|
|
prdata.raw = prdata.echon = 0;
|
|
prdata.sh = context->shp;
|
|
NOT_USED(argc);
|
|
/* This mess is because /bin/echo on BSD is different */
|
|
if(!prdata.sh->universe)
|
|
{
|
|
register char *universe;
|
|
if(universe=astconf("UNIVERSE",0,0))
|
|
bsd_univ = (strcmp(universe,"ucb")==0);
|
|
prdata.sh->universe = 1;
|
|
}
|
|
if(!bsd_univ)
|
|
return(b_print(0,argv,(Shbltin_t*)&prdata));
|
|
prdata.options = sh_optecho;
|
|
prdata.raw = 1;
|
|
while(argv[1] && *argv[1]=='-')
|
|
{
|
|
if(strcmp(argv[1],"-n")==0)
|
|
prdata.echon = 1;
|
|
#if !SHOPT_ECHOE
|
|
else if(strcmp(argv[1],"-e")==0)
|
|
prdata.raw = 0;
|
|
else if(strcmp(argv[1],"-ne")==0 || strcmp(argv[1],"-en")==0)
|
|
{
|
|
prdata.raw = 0;
|
|
prdata.echon = 1;
|
|
}
|
|
#endif /* SHOPT_ECHOE */
|
|
else
|
|
break;
|
|
argv++;
|
|
}
|
|
return(b_print(0,argv,(Shbltin_t*)&prdata));
|
|
}
|
|
#endif /* SHOPT_ECHOPRINT */
|
|
|
|
int b_printf(int argc, char *argv[],Shbltin_t *context)
|
|
{
|
|
struct print prdata;
|
|
NOT_USED(argc);
|
|
memset(&prdata,0,sizeof(prdata));
|
|
prdata.sh = context->shp;
|
|
prdata.options = sh_optprintf;
|
|
return(b_print(-1,argv,(Shbltin_t*)&prdata));
|
|
}
|
|
|
|
static int infof(Opt_t* op, Sfio_t* sp, const char* s, Optdisc_t* dp)
|
|
{
|
|
const struct printmap *pm;
|
|
char c='%';
|
|
for(pm=Pmap;pm->size>0;pm++)
|
|
{
|
|
sfprintf(sp, "[+%c(%s)q?%s.]",c,pm->name,pm->description);
|
|
}
|
|
return(1);
|
|
}
|
|
|
|
/*
|
|
* argc==0 when called from echo
|
|
* argc==-1 when called from printf
|
|
*/
|
|
|
|
int b_print(int argc, char *argv[], Shbltin_t *context)
|
|
{
|
|
register Sfio_t *outfile;
|
|
register int exitval=0,n, fd = 1;
|
|
register Shell_t *shp = context->shp;
|
|
const char *options, *msg = e_file+4;
|
|
char *format = 0;
|
|
int sflag = 0, nflag=0, rflag=0, vflag=0;
|
|
Optdisc_t disc;
|
|
disc.version = OPT_VERSION;
|
|
disc.infof = infof;
|
|
opt_info.disc = &disc;
|
|
if(argc>0)
|
|
{
|
|
options = sh_optprint;
|
|
nflag = rflag = 0;
|
|
format = 0;
|
|
}
|
|
else
|
|
{
|
|
struct print *pp = (struct print*)context;
|
|
shp = pp->sh;
|
|
options = pp->options;
|
|
if(argc==0)
|
|
{
|
|
nflag = pp->echon;
|
|
rflag = pp->raw;
|
|
argv++;
|
|
goto skip;
|
|
}
|
|
}
|
|
while((n = optget(argv,options))) switch(n)
|
|
{
|
|
case 'n':
|
|
nflag++;
|
|
break;
|
|
case 'p':
|
|
fd = shp->coutpipe;
|
|
msg = e_query;
|
|
break;
|
|
case 'f':
|
|
format = opt_info.arg;
|
|
break;
|
|
case 's':
|
|
/* print to history file */
|
|
if(!sh_histinit((void*)shp))
|
|
errormsg(SH_DICT,ERROR_system(1),e_history);
|
|
fd = sffileno(shp->gd->hist_ptr->histfp);
|
|
sh_onstate(SH_HISTORY);
|
|
sflag++;
|
|
break;
|
|
case 'e':
|
|
rflag = 0;
|
|
break;
|
|
case 'r':
|
|
rflag = 1;
|
|
break;
|
|
case 'u':
|
|
fd = (int)strtol(opt_info.arg,&opt_info.arg,10);
|
|
if(*opt_info.arg)
|
|
fd = -1;
|
|
else if(!sh_iovalidfd(shp,fd))
|
|
fd = -1;
|
|
else if(!(shp->inuse_bits&(1<<fd)) && (sh_inuse(shp,fd) || (shp->gd->hist_ptr && fd==sffileno(shp->gd->hist_ptr->histfp))))
|
|
|
|
fd = -1;
|
|
break;
|
|
case 'v':
|
|
vflag='v';
|
|
break;
|
|
case 'C':
|
|
vflag='C';
|
|
break;
|
|
case ':':
|
|
/* The following is for backward compatibility */
|
|
#if OPT_VERSION >= 19990123
|
|
if(strcmp(opt_info.name,"-R")==0)
|
|
#else
|
|
if(strcmp(opt_info.option,"-R")==0)
|
|
#endif
|
|
{
|
|
rflag = 1;
|
|
if(error_info.errors==0)
|
|
{
|
|
argv += opt_info.index+1;
|
|
/* special case test for -Rn */
|
|
if(strchr(argv[-1],'n'))
|
|
nflag++;
|
|
if(*argv && strcmp(*argv,"-n")==0)
|
|
{
|
|
|
|
nflag++;
|
|
argv++;
|
|
}
|
|
goto skip2;
|
|
}
|
|
}
|
|
else
|
|
errormsg(SH_DICT,2, "%s", opt_info.arg);
|
|
break;
|
|
case '?':
|
|
errormsg(SH_DICT,ERROR_usage(2), "%s", opt_info.arg);
|
|
break;
|
|
}
|
|
argv += opt_info.index;
|
|
if(error_info.errors || (argc<0 && !(format = *argv++)))
|
|
errormsg(SH_DICT,ERROR_usage(2),"%s",optusage((char*)0));
|
|
if(vflag && format)
|
|
errormsg(SH_DICT,ERROR_usage(2),"-%c and -f are mutually exclusive",vflag);
|
|
skip:
|
|
if(format)
|
|
format = genformat(format);
|
|
/* handle special case of '-' operand for print */
|
|
if(argc>0 && *argv && strcmp(*argv,"-")==0 && strcmp(argv[-1],"--"))
|
|
argv++;
|
|
skip2:
|
|
if(fd < 0)
|
|
{
|
|
errno = EBADF;
|
|
n = 0;
|
|
}
|
|
else if(!(n=shp->fdstatus[fd]))
|
|
n = sh_iocheckfd(shp,fd);
|
|
if(!(n&IOWRITE))
|
|
{
|
|
/* don't print error message for stdout for compatibility */
|
|
if(fd==1)
|
|
return(1);
|
|
errormsg(SH_DICT,ERROR_system(1),msg);
|
|
}
|
|
if(!(outfile=shp->sftable[fd]))
|
|
{
|
|
sh_onstate(SH_NOTRACK);
|
|
n = SF_WRITE|((n&IOREAD)?SF_READ:0);
|
|
shp->sftable[fd] = outfile = sfnew(NIL(Sfio_t*),shp->outbuff,IOBSIZE,fd,n);
|
|
sh_offstate(SH_NOTRACK);
|
|
sfpool(outfile,shp->outpool,SF_WRITE);
|
|
}
|
|
/* turn off share to guarantee atomic writes for printf */
|
|
n = sfset(outfile,SF_SHARE|SF_PUBLIC,0);
|
|
if(format)
|
|
{
|
|
/* printf style print */
|
|
Sfio_t *pool;
|
|
struct printf pdata;
|
|
memset(&pdata, 0, sizeof(pdata));
|
|
pdata.sh = shp;
|
|
pdata.hdr.version = SFIO_VERSION;
|
|
pdata.hdr.extf = extend;
|
|
pdata.nextarg = argv;
|
|
sh_offstate(SH_STOPOK);
|
|
pool=sfpool(sfstderr,NIL(Sfio_t*),SF_WRITE);
|
|
do
|
|
{
|
|
if(shp->trapnote&SH_SIGSET)
|
|
break;
|
|
pdata.hdr.form = format;
|
|
sfprintf(outfile,"%!",&pdata);
|
|
} while(*pdata.nextarg && pdata.nextarg!=argv);
|
|
if(pdata.nextarg == nullarg && pdata.argsize>0)
|
|
sfwrite(outfile,stakptr(staktell()),pdata.argsize);
|
|
/*
|
|
* -f flag skips adding newline at the end of output, which causes issues
|
|
* with syncing history if -s and -f are used together. So don't sync now
|
|
* if sflag was given. History is synced later with hist_flush() function.
|
|
* https://github.com/att/ast/issues/425
|
|
*/
|
|
if(!sflag && sffileno(outfile)!=sffileno(sfstderr))
|
|
if (sfsync(outfile) < 0)
|
|
exitval = 1;
|
|
sfpool(sfstderr,pool,SF_WRITE);
|
|
if (pdata.err)
|
|
exitval = 1;
|
|
}
|
|
else if(vflag)
|
|
{
|
|
while(*argv)
|
|
{
|
|
fmtbase64(outfile,*argv++,vflag=='C');
|
|
if(!nflag)
|
|
sfputc(outfile,'\n');
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* echo style print */
|
|
if(nflag && !argv[0])
|
|
{
|
|
if (sfsync((Sfio_t*)0) < 0)
|
|
exitval = 1;
|
|
}
|
|
else if(sh_echolist(shp,outfile,rflag,argv) && !nflag)
|
|
sfputc(outfile,'\n');
|
|
}
|
|
if(sflag)
|
|
{
|
|
hist_flush(shp->gd->hist_ptr);
|
|
sh_offstate(SH_HISTORY);
|
|
}
|
|
else if(n&SF_SHARE)
|
|
{
|
|
sfset(outfile,SF_SHARE|SF_PUBLIC,1);
|
|
if (sfsync(outfile) < 0)
|
|
exitval = 1;
|
|
}
|
|
if (exitval)
|
|
errormsg(SH_DICT, 2, e_io);
|
|
return(exitval);
|
|
}
|
|
|
|
/*
|
|
* echo the argument list onto <outfile>
|
|
* if <raw> is non-zero then \ is not a special character.
|
|
* returns 0 for \c otherwise 1.
|
|
*/
|
|
|
|
int sh_echolist(Shell_t *shp,Sfio_t *outfile, int raw, char *argv[])
|
|
{
|
|
register char *cp;
|
|
register int n;
|
|
struct printf pdata;
|
|
pdata.cescape = 0;
|
|
pdata.err = 0;
|
|
while(!pdata.cescape && (cp= *argv++))
|
|
{
|
|
if(!raw && (n=fmtvecho(cp,&pdata))>=0)
|
|
{
|
|
if(n)
|
|
sfwrite(outfile,stakptr(staktell()),n);
|
|
}
|
|
else
|
|
sfputr(outfile,cp,-1);
|
|
if(*argv)
|
|
sfputc(outfile,' ');
|
|
sh_sigcheck(shp);
|
|
}
|
|
return(!pdata.cescape);
|
|
}
|
|
|
|
/*
|
|
* modified version of stresc for generating formats
|
|
*/
|
|
static char strformat(char *s)
|
|
{
|
|
register char* t;
|
|
register int c;
|
|
char* b;
|
|
char* p;
|
|
#if SHOPT_MULTIBYTE && defined(FMT_EXP_WIDE)
|
|
int w;
|
|
#endif
|
|
|
|
b = t = s;
|
|
for (;;)
|
|
{
|
|
switch (c = *s++)
|
|
{
|
|
case '\\':
|
|
if(*s==0)
|
|
break;
|
|
#if SHOPT_MULTIBYTE && defined(FMT_EXP_WIDE)
|
|
c = chrexp(s - 1, &p, &w, FMT_EXP_CHAR|FMT_EXP_LINE|FMT_EXP_WIDE);
|
|
#else
|
|
c = chresc(s - 1, &p);
|
|
#endif
|
|
s = p;
|
|
#if SHOPT_MULTIBYTE
|
|
#if defined(FMT_EXP_WIDE)
|
|
if(w)
|
|
{
|
|
t += mbwide() ? mbconv(t, c) : wc2utf8(t, c);
|
|
continue;
|
|
}
|
|
#else
|
|
if(c>UCHAR_MAX && mbwide())
|
|
{
|
|
t += mbconv(t, c);
|
|
continue;
|
|
}
|
|
#endif /* FMT_EXP_WIDE */
|
|
#endif /* SHOPT_MULTIBYTE */
|
|
if(c=='%')
|
|
*t++ = '%';
|
|
else if(c==0)
|
|
{
|
|
*t++ = '%';
|
|
c = 'Z';
|
|
}
|
|
break;
|
|
case 0:
|
|
*t = 0;
|
|
return(t - b);
|
|
}
|
|
*t++ = c;
|
|
}
|
|
}
|
|
|
|
|
|
static char *genformat(char *format)
|
|
{
|
|
register char *fp;
|
|
stakseek(0);
|
|
stakputs(preformat);
|
|
stakputs(format);
|
|
fp = (char*)stakfreeze(1);
|
|
strformat(fp+sizeof(preformat)-1);
|
|
return(fp);
|
|
}
|
|
|
|
static char *fmthtml(const char *string, int flags)
|
|
{
|
|
register const char *cp = string;
|
|
register int c, offset = staktell();
|
|
if(!(flags&SFFMT_ALTER))
|
|
{
|
|
while(c= *(unsigned char*)cp++)
|
|
{
|
|
#if SHOPT_MULTIBYTE
|
|
register int s;
|
|
if((s=mbsize(cp-1)) > 1)
|
|
{
|
|
cp += (s-1);
|
|
continue;
|
|
}
|
|
#endif /* SHOPT_MULTIBYTE */
|
|
if(c=='<')
|
|
stakputs("<");
|
|
else if(c=='>')
|
|
stakputs(">");
|
|
else if(c=='&')
|
|
stakputs("&");
|
|
else if(c=='"')
|
|
stakputs(""");
|
|
else if(c=='\'')
|
|
stakputs("'");
|
|
else if(c==' ')
|
|
stakputs(" ");
|
|
else if(!isprint(c) && c!='\n' && c!='\r')
|
|
sfprintf(stkstd,"&#%X;",CCMAPC(c,CC_NATIVE,CC_ASCII));
|
|
else
|
|
stakputc(c);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
while(c= *(unsigned char*)cp++)
|
|
{
|
|
if(strchr("!*'();@&+$,#[]<>~.\"{}|\\-`^% ",c) || (!isprint(c) && c!='\n' && c!='\r'))
|
|
sfprintf(stkstd,"%%%02X",CCMAPC(c,CC_NATIVE,CC_ASCII));
|
|
else
|
|
stakputc(c);
|
|
}
|
|
}
|
|
stakputc(0);
|
|
return(stakptr(offset));
|
|
}
|
|
|
|
#if 1
|
|
static ssize_t fmtbase64(Sfio_t *iop, char *string, int alt)
|
|
#else
|
|
static void *fmtbase64(char *string, ssize_t *sz, int alt)
|
|
#endif
|
|
{
|
|
char *cp;
|
|
Sfdouble_t d;
|
|
ssize_t size;
|
|
Namval_t *np = nv_open(string, NiL, NV_VARNAME|NV_NOASSIGN|NV_NOADD);
|
|
Namarr_t *ap;
|
|
static union types_t number;
|
|
if(!np || nv_isnull(np))
|
|
{
|
|
if(sh_isoption(SH_NOUNSET))
|
|
errormsg(SH_DICT,ERROR_exit(1),e_notset,string);
|
|
return(0);
|
|
}
|
|
if(nv_isattr(np,NV_INTEGER))
|
|
{
|
|
d = nv_getnum(np);
|
|
if(nv_isattr(np,NV_DOUBLE))
|
|
{
|
|
if(nv_isattr(np,NV_LONG))
|
|
{
|
|
size = sizeof(Sfdouble_t);
|
|
number.ld = d;
|
|
}
|
|
else if(nv_isattr(np,NV_SHORT))
|
|
{
|
|
size = sizeof(float);
|
|
number.f = (float)d;
|
|
}
|
|
else
|
|
{
|
|
size = sizeof(double);
|
|
number.d = (double)d;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(nv_isattr(np,NV_LONG))
|
|
{
|
|
size = sizeof(Sflong_t);
|
|
number.ll = (Sflong_t)d;
|
|
}
|
|
else if(nv_isattr(np,NV_SHORT))
|
|
{
|
|
size = sizeof(short);
|
|
number.h = (short)d;
|
|
}
|
|
else
|
|
{
|
|
size = sizeof(short);
|
|
number.i = (int)d;
|
|
}
|
|
}
|
|
#if 1
|
|
return(sfwrite(iop, (void*)&number, size));
|
|
#else
|
|
if(sz)
|
|
*sz = size;
|
|
return((void*)&number);
|
|
#endif
|
|
}
|
|
if(nv_isattr(np,NV_BINARY))
|
|
#if 1
|
|
{
|
|
Namfun_t *fp;
|
|
for(fp=np->nvfun; fp;fp=fp->next)
|
|
{
|
|
if(fp->disc && fp->disc->writef)
|
|
break;
|
|
}
|
|
if(fp)
|
|
return (*fp->disc->writef)(np, iop, 0, fp);
|
|
else
|
|
{
|
|
int n = nv_size(np);
|
|
if(nv_isarray(np))
|
|
{
|
|
nv_onattr(np,NV_RAW);
|
|
cp = nv_getval(np);
|
|
nv_offattr(np,NV_RAW);
|
|
}
|
|
else
|
|
cp = (char*)np->nvalue.cp;
|
|
if((size = n)==0)
|
|
size = strlen(cp);
|
|
size = sfwrite(iop, cp, size);
|
|
return(n?n:size);
|
|
}
|
|
}
|
|
else if(nv_isarray(np) && (ap=nv_arrayptr(np)) && array_elem(ap) && (ap->nelem&(ARRAY_UNDEF|ARRAY_SCAN)))
|
|
{
|
|
nv_outnode(np,iop,(alt?-1:0),0);
|
|
sfputc(iop,')');
|
|
return(sftell(iop));
|
|
}
|
|
else
|
|
{
|
|
if(alt && nv_isvtree(np))
|
|
nv_onattr(np,NV_EXPORT);
|
|
else
|
|
alt = 0;
|
|
cp = nv_getval(np);
|
|
if(alt)
|
|
nv_offattr(np,NV_EXPORT);
|
|
if(!cp)
|
|
return(0);
|
|
size = strlen(cp);
|
|
return(sfwrite(iop,cp,size));
|
|
}
|
|
#else
|
|
nv_onattr(np,NV_RAW);
|
|
cp = nv_getval(np);
|
|
if(nv_isattr(np,NV_BINARY))
|
|
nv_offattr(np,NV_RAW);
|
|
if((size = nv_size(np))==0)
|
|
size = strlen(cp);
|
|
if(sz)
|
|
*sz = size;
|
|
return((void*)cp);
|
|
#endif
|
|
}
|
|
|
|
static int varname(const char *str, int n)
|
|
{
|
|
register int c,dot=1,len=1;
|
|
if(n < 0)
|
|
{
|
|
if(*str=='.')
|
|
str++;
|
|
n = strlen(str);
|
|
}
|
|
for(;n > 0; n-=len)
|
|
{
|
|
#ifdef SHOPT_MULTIBYTE
|
|
len = mbsize(str);
|
|
c = mbchar(str);
|
|
#else
|
|
c = *(unsigned char*)str++;
|
|
#endif
|
|
if(dot && !(isalpha(c)||c=='_'))
|
|
break;
|
|
else if(dot==0 && !(isalnum(c) || c=='_' || c == '.'))
|
|
break;
|
|
dot = (c=='.');
|
|
}
|
|
return(n==0);
|
|
}
|
|
|
|
static const char *mapformat(Sffmt_t *fe)
|
|
{
|
|
const struct printmap *pm = Pmap;
|
|
while(pm->size>0)
|
|
{
|
|
if(pm->size==fe->n_str && memcmp(pm->name,fe->t_str,fe->n_str)==0)
|
|
return(pm->map);
|
|
pm++;
|
|
}
|
|
return(0);
|
|
}
|
|
|
|
static int extend(Sfio_t* sp, void* v, Sffmt_t* fe)
|
|
{
|
|
char* lastchar = "";
|
|
register int neg = 0;
|
|
Sfdouble_t d;
|
|
Sfdouble_t longmin = LDBL_LLONG_MIN;
|
|
Sfdouble_t longmax = LDBL_LLONG_MAX;
|
|
int format = fe->fmt;
|
|
int n;
|
|
int fold = fe->base;
|
|
union types_t* value = (union types_t*)v;
|
|
struct printf* pp = (struct printf*)fe;
|
|
Shell_t *shp = pp->sh;
|
|
register char* argp = *pp->nextarg;
|
|
char *w,*s;
|
|
|
|
if(fe->n_str>0 && (format=='T'||format=='Q') && varname(fe->t_str,fe->n_str) && (!argp || varname(argp,-1)))
|
|
{
|
|
if(argp)
|
|
pp->lastarg = argp;
|
|
else
|
|
argp = pp->lastarg;
|
|
if(argp)
|
|
{
|
|
sfprintf(pp->sh->strbuf,"%s.%.*s%c",argp,fe->n_str,fe->t_str,0);
|
|
argp = sfstruse(pp->sh->strbuf);
|
|
}
|
|
}
|
|
else
|
|
pp->lastarg = 0;
|
|
fe->flags |= SFFMT_VALUE;
|
|
if(!argp || format=='Z')
|
|
{
|
|
switch(format)
|
|
{
|
|
case 'c':
|
|
value->c = 0;
|
|
fe->flags &= ~SFFMT_LONG;
|
|
break;
|
|
case 'q':
|
|
format = 's';
|
|
/* FALL THROUGH */
|
|
case 's':
|
|
case 'H':
|
|
case 'B':
|
|
case 'P':
|
|
case 'R':
|
|
case 'Z':
|
|
case 'b':
|
|
fe->fmt = 's';
|
|
fe->size = -1;
|
|
fe->base = -1;
|
|
value->s = "";
|
|
fe->flags &= ~SFFMT_LONG;
|
|
break;
|
|
case 'a':
|
|
case 'e':
|
|
case 'f':
|
|
case 'g':
|
|
case 'A':
|
|
case 'E':
|
|
case 'F':
|
|
case 'G':
|
|
if(SFFMT_LDOUBLE)
|
|
value->ld = 0.;
|
|
else
|
|
value->d = 0.;
|
|
break;
|
|
case 'n':
|
|
value->ip = &pp->intvar;
|
|
break;
|
|
case 'Q':
|
|
value->ll = 0;
|
|
break;
|
|
case 'T':
|
|
fe->fmt = 'd';
|
|
value->ll = tmxgettime();
|
|
break;
|
|
default:
|
|
if(!strchr("DdXxoUu",format))
|
|
errormsg(SH_DICT,ERROR_exit(1),e_formspec,format);
|
|
fe->fmt = 'd';
|
|
value->ll = 0;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch(format)
|
|
{
|
|
case 'p':
|
|
value->p = (char**)strtol(argp,&lastchar,10);
|
|
break;
|
|
case 'n':
|
|
{
|
|
Namval_t *np;
|
|
np = nv_open(argp,shp->var_tree,NV_VARNAME|NV_NOASSIGN|NV_NOARRAY);
|
|
_nv_unset(np,0);
|
|
nv_onattr(np,NV_INTEGER);
|
|
if (np->nvalue.lp = new_of(int32_t,0))
|
|
*np->nvalue.lp = 0;
|
|
nv_setsize(np,10);
|
|
if(sizeof(int)==sizeof(int32_t))
|
|
value->ip = (int*)np->nvalue.lp;
|
|
else
|
|
{
|
|
int32_t sl = 1;
|
|
value->ip = (int*)(((char*)np->nvalue.lp) + (*((char*)&sl) ? 0 : sizeof(int)));
|
|
}
|
|
nv_close(np);
|
|
break;
|
|
}
|
|
case 'q':
|
|
if(fe->n_str)
|
|
{
|
|
const char *fp = mapformat(fe);
|
|
if(fp)
|
|
{
|
|
format = *fp;
|
|
if(fp[1])
|
|
fe->flags |=SFFMT_ALTER;
|
|
}
|
|
}
|
|
case 'b':
|
|
case 's':
|
|
case 'B':
|
|
case 'H':
|
|
case 'P':
|
|
case 'R':
|
|
fe->fmt = 's';
|
|
fe->size = -1;
|
|
if(format=='s' && fe->base>=0)
|
|
{
|
|
value->p = pp->nextarg;
|
|
pp->nextarg = nullarg;
|
|
}
|
|
else
|
|
{
|
|
fe->base = -1;
|
|
value->s = argp;
|
|
}
|
|
fe->flags &= ~SFFMT_LONG;
|
|
break;
|
|
case 'c':
|
|
if(mbwide() && (n = mbsize(argp)) > 1)
|
|
{
|
|
fe->fmt = 's';
|
|
fe->size = n;
|
|
value->s = argp;
|
|
}
|
|
else if(fe->base >=0)
|
|
value->s = argp;
|
|
else
|
|
value->c = *argp;
|
|
fe->flags &= ~SFFMT_LONG;
|
|
break;
|
|
case 'o':
|
|
case 'x':
|
|
case 'X':
|
|
case 'u':
|
|
case 'U':
|
|
longmax = LDBL_ULLONG_MAX;
|
|
case '.':
|
|
if(fe->size==2 && strchr("bcsqHPRQTZ",*fe->form))
|
|
{
|
|
value->ll = ((unsigned char*)argp)[0];
|
|
break;
|
|
}
|
|
case 'd':
|
|
case 'D':
|
|
case 'i':
|
|
switch(*argp)
|
|
{
|
|
case '\'':
|
|
case '"':
|
|
w = argp + 1;
|
|
if(mbwide() && mbsize(w) > 1)
|
|
value->ll = mbchar(w);
|
|
else
|
|
value->ll = *(unsigned char*)w++;
|
|
if(w[0] && (w[0] != argp[0] || w[1]))
|
|
{
|
|
errormsg(SH_DICT,ERROR_warn(0),e_charconst,argp);
|
|
pp->err = 1;
|
|
}
|
|
break;
|
|
default:
|
|
d = sh_strnum(argp,&lastchar,0);
|
|
if(d<longmin)
|
|
{
|
|
errormsg(SH_DICT,ERROR_warn(0),e_overflow,argp);
|
|
pp->err = 1;
|
|
d = longmin;
|
|
}
|
|
else if(d>longmax)
|
|
{
|
|
errormsg(SH_DICT,ERROR_warn(0),e_overflow,argp);
|
|
pp->err = 1;
|
|
d = longmax;
|
|
}
|
|
value->ll = (Sflong_t)d;
|
|
if(lastchar == *pp->nextarg)
|
|
{
|
|
value->ll = *argp;
|
|
lastchar = "";
|
|
}
|
|
break;
|
|
}
|
|
if(neg)
|
|
value->ll = -value->ll;
|
|
fe->size = sizeof(value->ll);
|
|
break;
|
|
case 'a':
|
|
case 'e':
|
|
case 'f':
|
|
case 'g':
|
|
case 'A':
|
|
case 'E':
|
|
case 'F':
|
|
case 'G':
|
|
d = sh_strnum(*pp->nextarg,&lastchar,0);
|
|
switch(*argp)
|
|
{
|
|
case '\'':
|
|
case '"':
|
|
d = ((unsigned char*)argp)[1];
|
|
if(argp[2] && (argp[2] != argp[0] || argp[3]))
|
|
{
|
|
errormsg(SH_DICT,ERROR_warn(0),e_charconst,argp);
|
|
pp->err = 1;
|
|
}
|
|
break;
|
|
default:
|
|
d = sh_strnum(*pp->nextarg,&lastchar,0);
|
|
break;
|
|
}
|
|
if(SFFMT_LDOUBLE)
|
|
{
|
|
value->ld = d;
|
|
fe->size = sizeof(value->ld);
|
|
}
|
|
else
|
|
{
|
|
value->d = d;
|
|
fe->size = sizeof(value->d);
|
|
}
|
|
break;
|
|
case 'Q':
|
|
value->ll = (Sflong_t)strelapsed(*pp->nextarg,&lastchar,1);
|
|
break;
|
|
case 'T':
|
|
value->ll = (Sflong_t)tmxdate(*pp->nextarg,&lastchar,TMX_NOW);
|
|
break;
|
|
default:
|
|
value->ll = 0;
|
|
fe->fmt = 'd';
|
|
fe->size = sizeof(value->ll);
|
|
errormsg(SH_DICT,ERROR_exit(1),e_formspec,format);
|
|
break;
|
|
}
|
|
if (format == '.')
|
|
value->i = value->ll;
|
|
if(*lastchar)
|
|
{
|
|
errormsg(SH_DICT,ERROR_warn(0),e_argtype,format);
|
|
pp->err = 1;
|
|
}
|
|
pp->nextarg++;
|
|
}
|
|
switch(format)
|
|
{
|
|
case 'Z':
|
|
fe->fmt = 'c';
|
|
fe->base = -1;
|
|
value->c = 0;
|
|
break;
|
|
case 'b':
|
|
if((n=fmtvecho(value->s,pp))>=0)
|
|
{
|
|
if(pp->nextarg == nullarg)
|
|
{
|
|
pp->argsize = n;
|
|
return -1;
|
|
}
|
|
value->s = stakptr(staktell());
|
|
fe->size = n;
|
|
}
|
|
break;
|
|
case 'B':
|
|
if(!shp->strbuf2)
|
|
shp->strbuf2 = sfstropen();
|
|
fe->size = fmtbase64(shp->strbuf2,value->s, fe->flags&SFFMT_ALTER);
|
|
value->s = sfstruse(shp->strbuf2);
|
|
fe->flags |= SFFMT_SHORT;
|
|
break;
|
|
case 'H':
|
|
value->s = fmthtml(value->s, fe->flags);
|
|
break;
|
|
case 'q':
|
|
value->s = sh_fmtqf(value->s, !!(fe->flags & SFFMT_ALTER), fold);
|
|
break;
|
|
case 'P':
|
|
s = fmtmatch(value->s);
|
|
if(!s || *s==0)
|
|
errormsg(SH_DICT,ERROR_exit(1),e_badregexp,value->s);
|
|
value->s = s;
|
|
break;
|
|
case 'R':
|
|
s = fmtre(value->s);
|
|
if(!s || *s==0)
|
|
errormsg(SH_DICT,ERROR_exit(1),e_badregexp,value->s);
|
|
value->s = s;
|
|
break;
|
|
case 'Q':
|
|
if (fe->n_str>0)
|
|
{
|
|
fe->fmt = 'd';
|
|
fe->size = sizeof(value->ll);
|
|
}
|
|
else
|
|
{
|
|
value->s = fmtelapsed(value->ll, 1);
|
|
fe->fmt = 's';
|
|
fe->size = -1;
|
|
}
|
|
break;
|
|
case 'T':
|
|
if(fe->n_str>0)
|
|
{
|
|
n = fe->t_str[fe->n_str];
|
|
fe->t_str[fe->n_str] = 0;
|
|
value->s = fmttmx(fe->t_str, value->ll);
|
|
fe->t_str[fe->n_str] = n;
|
|
}
|
|
else value->s = fmttmx(NIL(char*), value->ll);
|
|
fe->fmt = 's';
|
|
fe->size = -1;
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* construct System V echo string out of <cp>
|
|
* If there are not escape sequences, returns -1
|
|
* Otherwise, puts null terminated result on stack, but doesn't freeze it
|
|
* returns length of output.
|
|
*/
|
|
|
|
static int fmtvecho(const char *string, struct printf *pp)
|
|
{
|
|
register const char *cp = string, *cpmax;
|
|
register int c;
|
|
register int offset = staktell();
|
|
#if SHOPT_MULTIBYTE
|
|
int chlen;
|
|
if(mbwide())
|
|
{
|
|
while(1)
|
|
{
|
|
if ((chlen = mbsize(cp)) > 1)
|
|
/* Skip over multibyte characters */
|
|
cp += chlen;
|
|
else if((c= *cp++)==0 || c == '\\')
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
#endif /* SHOPT_MULTIBYTE */
|
|
while((c= *cp++) && (c!='\\'));
|
|
if(c==0)
|
|
return(-1);
|
|
c = --cp - string;
|
|
if(c>0)
|
|
stakwrite((void*)string,c);
|
|
for(; c= *cp; cp++)
|
|
{
|
|
#if SHOPT_MULTIBYTE
|
|
if (mbwide() && ((chlen = mbsize(cp)) > 1))
|
|
{
|
|
stakwrite(cp,chlen);
|
|
cp += (chlen-1);
|
|
continue;
|
|
}
|
|
#endif /* SHOPT_MULTIBYTE */
|
|
if( c=='\\') switch(*++cp)
|
|
{
|
|
case 'E':
|
|
c = ('a'==97?'\033':39); /* ASCII/EBCDIC */
|
|
break;
|
|
case 'a':
|
|
c = '\a';
|
|
break;
|
|
case 'b':
|
|
c = '\b';
|
|
break;
|
|
case 'c':
|
|
pp->cescape++;
|
|
pp->nextarg = nullarg;
|
|
goto done;
|
|
case 'f':
|
|
c = '\f';
|
|
break;
|
|
case 'n':
|
|
c = '\n';
|
|
break;
|
|
case 'r':
|
|
c = '\r';
|
|
break;
|
|
case 'v':
|
|
c = '\v';
|
|
break;
|
|
case 't':
|
|
c = '\t';
|
|
break;
|
|
case '\\':
|
|
c = '\\';
|
|
break;
|
|
case '0':
|
|
c = 0;
|
|
cpmax = cp + 4;
|
|
while(++cp<cpmax && *cp>='0' && *cp<='7')
|
|
{
|
|
c <<= 3;
|
|
c |= (*cp-'0');
|
|
}
|
|
default:
|
|
cp--;
|
|
}
|
|
stakputc(c);
|
|
}
|
|
done:
|
|
c = staktell()-offset;
|
|
stakputc(0);
|
|
stakseek(offset);
|
|
return(c);
|
|
}
|