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