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