1/***********************************************************************
2*                                                                      *
3*               This software is part of the ast package               *
4*          Copyright (c) 1982-2012 AT&T Intellectual Property          *
5*                      and is licensed under the                       *
6*                 Eclipse Public License, Version 1.0                  *
7*                    by AT&T Intellectual Property                     *
8*                                                                      *
9*                A copy of the License is available at                 *
10*          http://www.eclipse.org/org/documents/epl-v10.html           *
11*         (with md5 checksum b35adb5213ca9657e911e9befb180842)         *
12*                                                                      *
13*              Information and Software Systems Research               *
14*                            AT&T Research                             *
15*                           Florham Park NJ                            *
16*                                                                      *
17*                  David Korn <dgk@research.att.com>                   *
18*                                                                      *
19***********************************************************************/
20#pragma prototyped
21/*
22 * echo [arg...]
23 * print [-nrps] [-f format] [-u filenum] [arg...]
24 * printf  format [arg...]
25 *
26 *   David Korn
27 *   AT&T Labs
28 */
29
30#include	"defs.h"
31#include	<error.h>
32#include	<stak.h>
33#include	"io.h"
34#include	"name.h"
35#include	"history.h"
36#include	"builtins.h"
37#include	"streval.h"
38#include	<tmx.h>
39#include	<ccode.h>
40
41union types_t
42{
43	unsigned char	c;
44	short		h;
45	int		i;
46	long		l;
47	Sflong_t	ll;
48	Sfdouble_t	ld;
49	double		d;
50	float		f;
51	char		*s;
52	int		*ip;
53	char		**p;
54};
55
56struct printf
57{
58	Sffmt_t		hdr;
59	int		argsize;
60	int		intvar;
61	char		**nextarg;
62	char		*lastarg;
63	char		cescape;
64	char		err;
65	Shell_t		*sh;
66};
67
68struct printmap
69{
70	size_t		size;
71	char		*name;
72	char		map[3];
73	const char	*description;
74};
75
76const struct printmap  Pmap[] =
77{
78	3,	"csv",	"q+",	"Equivalent to %#q",
79	4,	"html",	"H",	"Equivalent to %H",
80	3,	"ere",	"R",	"Equivalent to %R",
81	7,	"pattern","P",	"Equivalent to %#P",
82	3,	"url",	"H+",	"Equivalent to %#H",
83	0,	0,	0,
84};
85
86
87static int		extend(Sfio_t*,void*, Sffmt_t*);
88static const char   	preformat[] = "";
89static char		*genformat(char*);
90static int		fmtvecho(const char*, struct printf*);
91static ssize_t		fmtbase64(Sfio_t*, char*, int);
92
93struct print
94{
95	Shell_t         *sh;
96	const char	*options;
97	char		raw;
98	char		echon;
99};
100
101static char* 	nullarg[] = { 0, 0 };
102
103#if !SHOPT_ECHOPRINT
104   int    B_echo(int argc, char *argv[],Shbltin_t *context)
105   {
106	static char bsd_univ;
107	struct print prdata;
108	prdata.options = sh_optecho+5;
109	prdata.raw = prdata.echon = 0;
110	prdata.sh = context->shp;
111	NOT_USED(argc);
112	/* This mess is because /bin/echo on BSD is different */
113	if(!prdata.sh->universe)
114	{
115		register char *universe;
116		if(universe=astconf("UNIVERSE",0,0))
117			bsd_univ = (strcmp(universe,"ucb")==0);
118		prdata.sh->universe = 1;
119	}
120	if(!bsd_univ)
121		return(b_print(0,argv,(Shbltin_t*)&prdata));
122	prdata.options = sh_optecho;
123	prdata.raw = 1;
124	while(argv[1] && *argv[1]=='-')
125	{
126		if(strcmp(argv[1],"-n")==0)
127			prdata.echon = 1;
128#if !SHOPT_ECHOE
129		else if(strcmp(argv[1],"-e")==0)
130			prdata.raw = 0;
131		else if(strcmp(argv[1],"-ne")==0 || strcmp(argv[1],"-en")==0)
132		{
133			prdata.raw = 0;
134			prdata.echon = 1;
135		}
136#endif /* SHOPT_ECHOE */
137		else
138			break;
139		argv++;
140	}
141	return(b_print(0,argv,(Shbltin_t*)&prdata));
142   }
143#endif /* SHOPT_ECHOPRINT */
144
145int    b_printf(int argc, char *argv[],Shbltin_t *context)
146{
147	struct print prdata;
148	NOT_USED(argc);
149	memset(&prdata,0,sizeof(prdata));
150	prdata.sh = context->shp;
151	prdata.options = sh_optprintf;
152	return(b_print(-1,argv,(Shbltin_t*)&prdata));
153}
154
155static int infof(Opt_t* op, Sfio_t* sp, const char* s, Optdisc_t* dp)
156{
157	const struct printmap *pm;
158	char c='%';
159	for(pm=Pmap;pm->size>0;pm++)
160	{
161		sfprintf(sp, "[+%c(%s)q?%s.]",c,pm->name,pm->description);
162	}
163	return(1);
164}
165
166/*
167 * argc==0 when called from echo
168 * argc==-1 when called from printf
169 */
170
171int    b_print(int argc, char *argv[], Shbltin_t *context)
172{
173	register Sfio_t *outfile;
174	register int exitval=0,n, fd = 1;
175	register Shell_t *shp = context->shp;
176	const char *options, *msg = e_file+4;
177	char *format = 0;
178	int sflag = 0, nflag=0, rflag=0, vflag=0;
179	Optdisc_t disc;
180	disc.version = OPT_VERSION;
181	disc.infof = infof;
182	opt_info.disc = &disc;
183	if(argc>0)
184	{
185		options = sh_optprint;
186		nflag = rflag = 0;
187		format = 0;
188	}
189	else
190	{
191		struct print *pp = (struct print*)context;
192		shp = pp->sh;
193		options = pp->options;
194		if(argc==0)
195		{
196			nflag = pp->echon;
197			rflag = pp->raw;
198			argv++;
199			goto skip;
200		}
201	}
202	while((n = optget(argv,options))) switch(n)
203	{
204		case 'n':
205			nflag++;
206			break;
207		case 'p':
208			fd = shp->coutpipe;
209			msg = e_query;
210			break;
211		case 'f':
212			format = opt_info.arg;
213			break;
214		case 's':
215			/* print to history file */
216			if(!sh_histinit((void*)shp))
217				errormsg(SH_DICT,ERROR_system(1),e_history);
218			fd = sffileno(shp->gd->hist_ptr->histfp);
219			sh_onstate(SH_HISTORY);
220			sflag++;
221			break;
222		case 'e':
223			rflag = 0;
224			break;
225		case 'r':
226			rflag = 1;
227			break;
228		case 'u':
229			fd = (int)strtol(opt_info.arg,&opt_info.arg,10);
230			if(*opt_info.arg)
231				fd = -1;
232			else if(!sh_iovalidfd(shp,fd))
233				fd = -1;
234			else if(!(shp->inuse_bits&(1<<fd)) && (sh_inuse(shp,fd) || (shp->gd->hist_ptr && fd==sffileno(shp->gd->hist_ptr->histfp))))
235
236				fd = -1;
237			break;
238		case 'v':
239			vflag='v';
240			break;
241		case 'C':
242			vflag='C';
243			break;
244		case ':':
245			/* The following is for backward compatibility */
246#if OPT_VERSION >= 19990123
247			if(strcmp(opt_info.name,"-R")==0)
248#else
249			if(strcmp(opt_info.option,"-R")==0)
250#endif
251			{
252				rflag = 1;
253				if(error_info.errors==0)
254				{
255					argv += opt_info.index+1;
256					/* special case test for -Rn */
257					if(strchr(argv[-1],'n'))
258						nflag++;
259					if(*argv && strcmp(*argv,"-n")==0)
260					{
261
262						nflag++;
263						argv++;
264					}
265					goto skip2;
266				}
267			}
268			else
269				errormsg(SH_DICT,2, "%s", opt_info.arg);
270			break;
271		case '?':
272			errormsg(SH_DICT,ERROR_usage(2), "%s", opt_info.arg);
273			break;
274	}
275	argv += opt_info.index;
276	if(error_info.errors || (argc<0 && !(format = *argv++)))
277		errormsg(SH_DICT,ERROR_usage(2),"%s",optusage((char*)0));
278	if(vflag && format)
279		errormsg(SH_DICT,ERROR_usage(2),"-%c and -f are mutually exclusive",vflag);
280skip:
281	if(format)
282		format = genformat(format);
283	/* handle special case of '-' operand for print */
284	if(argc>0 && *argv && strcmp(*argv,"-")==0 && strcmp(argv[-1],"--"))
285		argv++;
286skip2:
287	if(fd < 0)
288	{
289		errno = EBADF;
290		n = 0;
291	}
292	else if(!(n=shp->fdstatus[fd]))
293		n = sh_iocheckfd(shp,fd);
294	if(!(n&IOWRITE))
295	{
296		/* don't print error message for stdout for compatibility */
297		if(fd==1)
298			return(1);
299		errormsg(SH_DICT,ERROR_system(1),msg);
300	}
301	if(!(outfile=shp->sftable[fd]))
302	{
303		sh_onstate(SH_NOTRACK);
304		n = SF_WRITE|((n&IOREAD)?SF_READ:0);
305		shp->sftable[fd] = outfile = sfnew(NIL(Sfio_t*),shp->outbuff,IOBSIZE,fd,n);
306		sh_offstate(SH_NOTRACK);
307		sfpool(outfile,shp->outpool,SF_WRITE);
308	}
309	/* turn off share to guarantee atomic writes for printf */
310	n = sfset(outfile,SF_SHARE|SF_PUBLIC,0);
311	if(format)
312	{
313		/* printf style print */
314		Sfio_t *pool;
315		struct printf pdata;
316		memset(&pdata, 0, sizeof(pdata));
317		pdata.sh = shp;
318		pdata.hdr.version = SFIO_VERSION;
319		pdata.hdr.extf = extend;
320		pdata.nextarg = argv;
321		sh_offstate(SH_STOPOK);
322		pool=sfpool(sfstderr,NIL(Sfio_t*),SF_WRITE);
323		do
324		{
325			if(shp->trapnote&SH_SIGSET)
326				break;
327			pdata.hdr.form = format;
328			sfprintf(outfile,"%!",&pdata);
329		} while(*pdata.nextarg && pdata.nextarg!=argv);
330		if(pdata.nextarg == nullarg && pdata.argsize>0)
331			sfwrite(outfile,stakptr(staktell()),pdata.argsize);
332		if(sffileno(outfile)!=sffileno(sfstderr))
333			sfsync(outfile);
334		sfpool(sfstderr,pool,SF_WRITE);
335		exitval = pdata.err;
336	}
337	else if(vflag)
338	{
339		while(*argv)
340		{
341			fmtbase64(outfile,*argv++,vflag=='C');
342			if(!nflag)
343				sfputc(outfile,'\n');
344		}
345	}
346	else
347	{
348		/* echo style print */
349		if(nflag && !argv[0])
350			sfsync((Sfio_t*)0);
351		else if(sh_echolist(shp,outfile,rflag,argv) && !nflag)
352			sfputc(outfile,'\n');
353	}
354	if(sflag)
355	{
356		hist_flush(shp->gd->hist_ptr);
357		sh_offstate(SH_HISTORY);
358	}
359	else if(n&SF_SHARE)
360	{
361		sfset(outfile,SF_SHARE|SF_PUBLIC,1);
362		sfsync(outfile);
363	}
364	return(exitval);
365}
366
367/*
368 * echo the argument list onto <outfile>
369 * if <raw> is non-zero then \ is not a special character.
370 * returns 0 for \c otherwise 1.
371 */
372
373int sh_echolist(Shell_t *shp,Sfio_t *outfile, int raw, char *argv[])
374{
375	register char	*cp;
376	register int	n;
377	struct printf pdata;
378	pdata.cescape = 0;
379	pdata.err = 0;
380	while(!pdata.cescape && (cp= *argv++))
381	{
382		if(!raw  && (n=fmtvecho(cp,&pdata))>=0)
383		{
384			if(n)
385				sfwrite(outfile,stakptr(staktell()),n);
386		}
387		else
388			sfputr(outfile,cp,-1);
389		if(*argv)
390			sfputc(outfile,' ');
391		sh_sigcheck(shp);
392	}
393	return(!pdata.cescape);
394}
395
396/*
397 * modified version of stresc for generating formats
398 */
399static char strformat(char *s)
400{
401        register char*  t;
402        register int    c;
403        char*           b;
404        char*           p;
405#if SHOPT_MULTIBYTE && defined(FMT_EXP_WIDE)
406	int		w;
407#endif
408
409        b = t = s;
410        for (;;)
411        {
412                switch (c = *s++)
413                {
414                    case '\\':
415			if(*s==0)
416				break;
417#if SHOPT_MULTIBYTE && defined(FMT_EXP_WIDE)
418                        c = chrexp(s - 1, &p, &w, FMT_EXP_CHAR|FMT_EXP_LINE|FMT_EXP_WIDE);
419#else
420                        c = chresc(s - 1, &p);
421#endif
422                        s = p;
423#if SHOPT_MULTIBYTE
424#if defined(FMT_EXP_WIDE)
425			if(w)
426			{
427				t += mbwide() ? mbconv(t, c) : wc2utf8(t, c);
428				continue;
429			}
430#else
431			if(c>UCHAR_MAX && mbwide())
432			{
433				t += mbconv(t, c);
434				continue;
435			}
436#endif /* FMT_EXP_WIDE */
437#endif /* SHOPT_MULTIBYTE */
438			if(c=='%')
439				*t++ = '%';
440			else if(c==0)
441			{
442				*t++ = '%';
443				c = 'Z';
444			}
445                        break;
446                    case 0:
447                        *t = 0;
448                        return(t - b);
449                }
450                *t++ = c;
451        }
452}
453
454
455static char *genformat(char *format)
456{
457	register char *fp;
458	stakseek(0);
459	stakputs(preformat);
460	stakputs(format);
461	fp = (char*)stakfreeze(1);
462	strformat(fp+sizeof(preformat)-1);
463	return(fp);
464}
465
466static char *fmthtml(const char *string, int flags)
467{
468	register const char *cp = string;
469	register int c, offset = staktell();
470	if(!(flags&SFFMT_ALTER))
471	{
472		while(c= *(unsigned char*)cp++)
473		{
474#if SHOPT_MULTIBYTE
475			register int s;
476			if((s=mbsize(cp-1)) > 1)
477			{
478				cp += (s-1);
479				continue;
480			}
481#endif /* SHOPT_MULTIBYTE */
482			if(c=='<')
483				stakputs("&lt;");
484			else if(c=='>')
485				stakputs("&gt;");
486			else if(c=='&')
487				stakputs("&amp;");
488			else if(c=='"')
489				stakputs("&quot;");
490			else if(c=='\'')
491				stakputs("&apos;");
492			else if(c==' ')
493				stakputs("&nbsp;");
494			else if(!isprint(c) && c!='\n' && c!='\r')
495				sfprintf(stkstd,"&#%X;",CCMAPC(c,CC_NATIVE,CC_ASCII));
496			else
497				stakputc(c);
498		}
499	}
500	else
501	{
502		while(c= *(unsigned char*)cp++)
503		{
504			if(strchr("!*'();@&+$,#[]<>~.\"{}|\\-`^% ",c) || (!isprint(c) && c!='\n' && c!='\r'))
505				sfprintf(stkstd,"%%%02X",CCMAPC(c,CC_NATIVE,CC_ASCII));
506			else
507				stakputc(c);
508		}
509	}
510	stakputc(0);
511	return(stakptr(offset));
512}
513
514#if 1
515static ssize_t fmtbase64(Sfio_t *iop, char *string, int alt)
516#else
517static void *fmtbase64(char *string, ssize_t *sz, int alt)
518#endif
519{
520	char			*cp;
521	Sfdouble_t		d;
522	ssize_t			size;
523	Namval_t		*np = nv_open(string, NiL, NV_VARNAME|NV_NOASSIGN|NV_NOADD);
524	Namarr_t		*ap;
525	static union types_t	number;
526	if(!np || nv_isnull(np))
527	{
528		if(sh_isoption(SH_NOUNSET))
529			errormsg(SH_DICT,ERROR_exit(1),e_notset,string);
530		return(0);
531	}
532	if(nv_isattr(np,NV_INTEGER))
533	{
534		d = nv_getnum(np);
535		if(nv_isattr(np,NV_DOUBLE))
536		{
537			if(nv_isattr(np,NV_LONG))
538			{
539				size = sizeof(Sfdouble_t);
540				number.ld = d;
541			}
542			else if(nv_isattr(np,NV_SHORT))
543			{
544				size = sizeof(float);
545				number.f = (float)d;
546			}
547			else
548			{
549				size = sizeof(double);
550				number.d = (double)d;
551			}
552		}
553		else
554		{
555			if(nv_isattr(np,NV_LONG))
556			{
557				size =  sizeof(Sflong_t);
558				number.ll = (Sflong_t)d;
559			}
560			else if(nv_isattr(np,NV_SHORT))
561			{
562				size =  sizeof(short);
563				number.h = (short)d;
564			}
565			else
566			{
567				size =  sizeof(short);
568				number.i = (int)d;
569			}
570		}
571#if 1
572		return(sfwrite(iop, (void*)&number, size));
573#else
574		if(sz)
575			*sz = size;
576		return((void*)&number);
577#endif
578	}
579	if(nv_isattr(np,NV_BINARY))
580#if 1
581	{
582		Namfun_t *fp;
583		for(fp=np->nvfun; fp;fp=fp->next)
584		{
585			if(fp->disc && fp->disc->writef)
586				break;
587		}
588		if(fp)
589			return (*fp->disc->writef)(np, iop, 0, fp);
590		else
591		{
592			int n = nv_size(np);
593			if(nv_isarray(np))
594			{
595				nv_onattr(np,NV_RAW);
596				cp = nv_getval(np);
597				nv_offattr(np,NV_RAW);
598			}
599			else
600				cp = (char*)np->nvalue.cp;
601			if((size = n)==0)
602				size = strlen(cp);
603			size = sfwrite(iop, cp, size);
604			return(n?n:size);
605		}
606	}
607	else if(nv_isarray(np) && (ap=nv_arrayptr(np)) && array_elem(ap) && (ap->nelem&(ARRAY_UNDEF|ARRAY_SCAN)))
608	{
609		nv_outnode(np,iop,(alt?-1:0),0);
610		sfputc(iop,')');
611		return(sftell(iop));
612	}
613	else
614	{
615		if(alt && nv_isvtree(np))
616			nv_onattr(np,NV_EXPORT);
617		else
618			alt = 0;
619		cp = nv_getval(np);
620		if(alt)
621			nv_offattr(np,NV_EXPORT);
622		if(!cp)
623			return(0);
624		size = strlen(cp);
625		return(sfwrite(iop,cp,size));
626	}
627#else
628		nv_onattr(np,NV_RAW);
629	cp = nv_getval(np);
630	if(nv_isattr(np,NV_BINARY))
631		nv_offattr(np,NV_RAW);
632	if((size = nv_size(np))==0)
633		size = strlen(cp);
634	if(sz)
635		*sz = size;
636	return((void*)cp);
637#endif
638}
639
640static int varname(const char *str, int n)
641{
642	register int c,dot=1,len=1;
643	if(n < 0)
644	{
645		if(*str=='.')
646			str++;
647		n = strlen(str);
648	}
649	for(;n > 0; n-=len)
650	{
651#ifdef SHOPT_MULTIBYTE
652		len = mbsize(str);
653		c = mbchar(str);
654#else
655		c = *(unsigned char*)str++;
656#endif
657		if(dot && !(isalpha(c)||c=='_'))
658			break;
659		else if(dot==0 && !(isalnum(c) || c=='_' || c == '.'))
660			break;
661		dot = (c=='.');
662	}
663	return(n==0);
664}
665
666static const char *mapformat(Sffmt_t *fe)
667{
668	const struct printmap *pm = Pmap;
669	while(pm->size>0)
670	{
671		if(pm->size==fe->n_str && memcmp(pm->name,fe->t_str,fe->n_str)==0)
672			return(pm->map);
673		pm++;
674	}
675	return(0);
676}
677
678static int extend(Sfio_t* sp, void* v, Sffmt_t* fe)
679{
680	char*		lastchar = "";
681	register int	neg = 0;
682	Sfdouble_t	d;
683	Sfdouble_t	longmin = LDBL_LLONG_MIN;
684	Sfdouble_t	longmax = LDBL_LLONG_MAX;
685	int		format = fe->fmt;
686	int		n;
687	int		fold = fe->base;
688	union types_t*	value = (union types_t*)v;
689	struct printf*	pp = (struct printf*)fe;
690	Shell_t		*shp = pp->sh;
691	register char*	argp = *pp->nextarg;
692	char		*w,*s;
693
694	if(fe->n_str>0 && (format=='T'||format=='Q') && varname(fe->t_str,fe->n_str) && (!argp || varname(argp,-1)))
695	{
696		if(argp)
697			pp->lastarg = argp;
698		else
699			argp = pp->lastarg;
700		if(argp)
701		{
702			sfprintf(pp->sh->strbuf,"%s.%.*s%c",argp,fe->n_str,fe->t_str,0);
703			argp = sfstruse(pp->sh->strbuf);
704		}
705	}
706	else
707		pp->lastarg = 0;
708	fe->flags |= SFFMT_VALUE;
709	if(!argp || format=='Z')
710	{
711		switch(format)
712		{
713		case 'c':
714			value->c = 0;
715			fe->flags &= ~SFFMT_LONG;
716			break;
717		case 'q':
718			format = 's';
719			/* FALL THROUGH */
720		case 's':
721		case 'H':
722		case 'B':
723		case 'P':
724		case 'R':
725		case 'Z':
726		case 'b':
727			fe->fmt = 's';
728			fe->size = -1;
729			fe->base = -1;
730			value->s = "";
731			fe->flags &= ~SFFMT_LONG;
732			break;
733		case 'a':
734		case 'e':
735		case 'f':
736		case 'g':
737		case 'A':
738		case 'E':
739		case 'F':
740		case 'G':
741                        if(SFFMT_LDOUBLE)
742				value->ld = 0.;
743			else
744				value->d = 0.;
745			break;
746		case 'n':
747			value->ip = &pp->intvar;
748			break;
749		case 'Q':
750			value->ll = 0;
751			break;
752		case 'T':
753			fe->fmt = 'd';
754			value->ll = tmxgettime();
755			break;
756		default:
757			if(!strchr("DdXxoUu",format))
758				errormsg(SH_DICT,ERROR_exit(1),e_formspec,format);
759			fe->fmt = 'd';
760			value->ll = 0;
761			break;
762		}
763	}
764	else
765	{
766		switch(format)
767		{
768		case 'p':
769			value->p = (char**)strtol(argp,&lastchar,10);
770			break;
771		case 'n':
772		{
773			Namval_t *np;
774			np = nv_open(argp,shp->var_tree,NV_VARNAME|NV_NOASSIGN|NV_NOARRAY);
775			_nv_unset(np,0);
776			nv_onattr(np,NV_INTEGER);
777			if (np->nvalue.lp = new_of(int32_t,0))
778				*np->nvalue.lp = 0;
779			nv_setsize(np,10);
780			if(sizeof(int)==sizeof(int32_t))
781				value->ip = (int*)np->nvalue.lp;
782			else
783			{
784				int32_t sl = 1;
785				value->ip = (int*)(((char*)np->nvalue.lp) + (*((char*)&sl) ? 0 : sizeof(int)));
786			}
787			nv_close(np);
788			break;
789		}
790		case 'q':
791			if(fe->n_str)
792			{
793				const char *fp = mapformat(fe);
794				if(fp)
795				{
796					format = *fp;
797					if(fp[1])
798						fe->flags |=SFFMT_ALTER;
799				}
800			}
801		case 'b':
802		case 's':
803		case 'B':
804		case 'H':
805		case 'P':
806		case 'R':
807			fe->fmt = 's';
808			fe->size = -1;
809			if(format=='s' && fe->base>=0)
810			{
811				value->p = pp->nextarg;
812				pp->nextarg = nullarg;
813			}
814			else
815			{
816				fe->base = -1;
817				value->s = argp;
818			}
819			fe->flags &= ~SFFMT_LONG;
820			break;
821		case 'c':
822			if(mbwide() && (n = mbsize(argp)) > 1)
823			{
824				fe->fmt = 's';
825				fe->size = n;
826				value->s = argp;
827			}
828			else if(fe->base >=0)
829				value->s = argp;
830			else
831				value->c = *argp;
832			fe->flags &= ~SFFMT_LONG;
833			break;
834		case 'o':
835		case 'x':
836		case 'X':
837		case 'u':
838		case 'U':
839			longmax = LDBL_ULLONG_MAX;
840		case '.':
841			if(fe->size==2 && strchr("bcsqHPRQTZ",*fe->form))
842			{
843				value->ll = ((unsigned char*)argp)[0];
844				break;
845			}
846		case 'd':
847		case 'D':
848		case 'i':
849			switch(*argp)
850			{
851			case '\'':
852			case '"':
853				w = argp + 1;
854				if(mbwide() && mbsize(w) > 1)
855					value->ll = mbchar(w);
856				else
857					value->ll = *(unsigned char*)w++;
858				if(w[0] && (w[0] != argp[0] || w[1]))
859				{
860					errormsg(SH_DICT,ERROR_warn(0),e_charconst,argp);
861					pp->err = 1;
862				}
863				break;
864			default:
865				d = sh_strnum(argp,&lastchar,0);
866				if(d<longmin)
867				{
868					errormsg(SH_DICT,ERROR_warn(0),e_overflow,argp);
869					pp->err = 1;
870					d = longmin;
871				}
872				else if(d>longmax)
873				{
874					errormsg(SH_DICT,ERROR_warn(0),e_overflow,argp);
875					pp->err = 1;
876					d = longmax;
877				}
878				value->ll = (Sflong_t)d;
879				if(lastchar == *pp->nextarg)
880				{
881					value->ll = *argp;
882					lastchar = "";
883				}
884				break;
885			}
886			if(neg)
887				value->ll = -value->ll;
888			fe->size = sizeof(value->ll);
889			break;
890		case 'a':
891		case 'e':
892		case 'f':
893		case 'g':
894		case 'A':
895		case 'E':
896		case 'F':
897		case 'G':
898			d = sh_strnum(*pp->nextarg,&lastchar,0);
899			switch(*argp)
900			{
901			    case '\'':
902			    case '"':
903				d = ((unsigned char*)argp)[1];
904				if(argp[2] && (argp[2] != argp[0] || argp[3]))
905				{
906					errormsg(SH_DICT,ERROR_warn(0),e_charconst,argp);
907					pp->err = 1;
908				}
909				break;
910			    default:
911				d = sh_strnum(*pp->nextarg,&lastchar,0);
912				break;
913			}
914                        if(SFFMT_LDOUBLE)
915			{
916				value->ld = d;
917				fe->size = sizeof(value->ld);
918			}
919			else
920			{
921				value->d = d;
922				fe->size = sizeof(value->d);
923			}
924			break;
925		case 'Q':
926			value->ll = (Sflong_t)strelapsed(*pp->nextarg,&lastchar,1);
927			break;
928		case 'T':
929			value->ll = (Sflong_t)tmxdate(*pp->nextarg,&lastchar,TMX_NOW);
930			break;
931		default:
932			value->ll = 0;
933			fe->fmt = 'd';
934			fe->size = sizeof(value->ll);
935			errormsg(SH_DICT,ERROR_exit(1),e_formspec,format);
936			break;
937		}
938		if (format == '.')
939			value->i = value->ll;
940		if(*lastchar)
941		{
942			errormsg(SH_DICT,ERROR_warn(0),e_argtype,format);
943			pp->err = 1;
944		}
945		pp->nextarg++;
946	}
947	switch(format)
948	{
949	case 'Z':
950		fe->fmt = 'c';
951		fe->base = -1;
952		value->c = 0;
953		break;
954	case 'b':
955		if((n=fmtvecho(value->s,pp))>=0)
956		{
957			if(pp->nextarg == nullarg)
958			{
959				pp->argsize = n;
960				return -1;
961			}
962			value->s = stakptr(staktell());
963			fe->size = n;
964		}
965		break;
966	case 'B':
967		if(!shp->strbuf2)
968			shp->strbuf2 = sfstropen();
969		fe->size = fmtbase64(shp->strbuf2,value->s, fe->flags&SFFMT_ALTER);
970		value->s = sfstruse(shp->strbuf2);
971		fe->flags |= SFFMT_SHORT;
972		break;
973	case 'H':
974		value->s = fmthtml(value->s, fe->flags);
975		break;
976	case 'q':
977		value->s = sh_fmtqf(value->s, !!(fe->flags & SFFMT_ALTER), fold);
978		break;
979	case 'P':
980		s = fmtmatch(value->s);
981		if(!s || *s==0)
982			errormsg(SH_DICT,ERROR_exit(1),e_badregexp,value->s);
983		value->s = s;
984		break;
985	case 'R':
986		s = fmtre(value->s);
987		if(!s || *s==0)
988			errormsg(SH_DICT,ERROR_exit(1),e_badregexp,value->s);
989		value->s = s;
990		break;
991	case 'Q':
992		if (fe->n_str>0)
993		{
994			fe->fmt = 'd';
995			fe->size = sizeof(value->ll);
996		}
997		else
998		{
999			value->s = fmtelapsed(value->ll, 1);
1000			fe->fmt = 's';
1001			fe->size = -1;
1002		}
1003		break;
1004	case 'T':
1005		if(fe->n_str>0)
1006		{
1007			n = fe->t_str[fe->n_str];
1008			fe->t_str[fe->n_str] = 0;
1009			value->s = fmttmx(fe->t_str, value->ll);
1010			fe->t_str[fe->n_str] = n;
1011		}
1012		else value->s = fmttmx(NIL(char*), value->ll);
1013		fe->fmt = 's';
1014		fe->size = -1;
1015		break;
1016	}
1017	return 0;
1018}
1019
1020/*
1021 * construct System V echo string out of <cp>
1022 * If there are not escape sequences, returns -1
1023 * Otherwise, puts null terminated result on stack, but doesn't freeze it
1024 * returns length of output.
1025 */
1026
1027static int fmtvecho(const char *string, struct printf *pp)
1028{
1029	register const char *cp = string, *cpmax;
1030	register int c;
1031	register int offset = staktell();
1032#if SHOPT_MULTIBYTE
1033	int chlen;
1034	if(mbwide())
1035	{
1036		while(1)
1037		{
1038			if ((chlen = mbsize(cp)) > 1)
1039				/* Skip over multibyte characters */
1040				cp += chlen;
1041			else if((c= *cp++)==0 || c == '\\')
1042				break;
1043		}
1044	}
1045	else
1046#endif /* SHOPT_MULTIBYTE */
1047	while((c= *cp++) && (c!='\\'));
1048	if(c==0)
1049		return(-1);
1050	c = --cp - string;
1051	if(c>0)
1052		stakwrite((void*)string,c);
1053	for(; c= *cp; cp++)
1054	{
1055#if SHOPT_MULTIBYTE
1056		if (mbwide() && ((chlen = mbsize(cp)) > 1))
1057		{
1058			stakwrite(cp,chlen);
1059			cp +=  (chlen-1);
1060			continue;
1061		}
1062#endif /* SHOPT_MULTIBYTE */
1063		if( c=='\\') switch(*++cp)
1064		{
1065			case 'E':
1066				c = ('a'==97?'\033':39); /* ASCII/EBCDIC */
1067				break;
1068			case 'a':
1069				c = '\a';
1070				break;
1071			case 'b':
1072				c = '\b';
1073				break;
1074			case 'c':
1075				pp->cescape++;
1076				pp->nextarg = nullarg;
1077				goto done;
1078			case 'f':
1079				c = '\f';
1080				break;
1081			case 'n':
1082				c = '\n';
1083				break;
1084			case 'r':
1085				c = '\r';
1086				break;
1087			case 'v':
1088				c = '\v';
1089				break;
1090			case 't':
1091				c = '\t';
1092				break;
1093			case '\\':
1094				c = '\\';
1095				break;
1096			case '0':
1097				c = 0;
1098				cpmax = cp + 4;
1099				while(++cp<cpmax && *cp>='0' && *cp<='7')
1100				{
1101					c <<= 3;
1102					c |= (*cp-'0');
1103				}
1104			default:
1105				cp--;
1106		}
1107		stakputc(c);
1108	}
1109done:
1110	c = staktell()-offset;
1111	stakputc(0);
1112	stakseek(offset);
1113	return(c);
1114}
1115