xref: /titanic_44/usr/src/cmd/awk_xpg4/awk2.c (revision b7f45089ccbe01bab3d7c7377b49d80d2ae18a69)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * awk -- process input files, field extraction, output
24  *
25  * Copyright (c) 1995, 1996-2000 by Sun Microsystems, Inc.
26  * All rights reserved.
27  *
28  * Copyright 1986, 1994 by Mortice Kern Systems Inc.  All rights reserved.
29  *
30  * Based on MKS awk(1) ported to be /usr/xpg4/bin/awk with POSIX/XCU4 changes
31  */
32 
33 #pragma ident	"%Z%%M%	%I%	%E% SMI"
34 
35 #include "awk.h"
36 #include "y.tab.h"
37 
38 static FILE	*awkinfp;		/* Input file pointer */
39 static int	reclen;			/* Length of last record */
40 static int	exstat;			/* Exit status */
41 
42 static FILE	*openfile(NODE *np, int flag, int fatal);
43 static FILE	*newfile(void);
44 static NODE	*nextarg(NODE **npp);
45 static void	adjust_buf(wchar_t **,int *, wchar_t **, char *, size_t);
46 static void	awk_putwc(wchar_t, FILE *);
47 
48 /*
49  * mainline for awk execution
50  */
51 void
52 awk()
53 {
54 	running = 1;
55 	dobegin();
56 	while (nextrecord(linebuf, awkinfp) > 0)
57 		execute(yytree);
58 	doend(exstat);
59 }
60 
61 /*
62  * "cp" is the buffer to fill.  There is a special case if this buffer is
63  * "linebuf" ($0)
64  * Return 1 if OK, zero on EOF, -1 on error.
65  */
66 int
67 nextrecord(wchar_t *cp, FILE *fp)
68 {
69 	wchar_t *ep = cp;
70 
71 nextfile:
72 	if (fp==FNULL && (fp=newfile())==FNULL)
73 		return (0);
74 	if ((*awkrecord)(ep, NLINE, fp) == NULL) {
75 		if (fp == awkinfp) {
76 			if (fp != stdin)
77 				(void)fclose(awkinfp);
78 			awkinfp = fp = FNULL;
79 			goto nextfile;
80 		}
81 		if (ferror(fp))
82 			return (-1);
83 		return (0);
84 	}
85 	if (fp == awkinfp) {
86 		if (varNR->n_flags & FINT)
87 			++varNR->n_int; else
88 			(void)exprreduce(incNR);
89 		if (varFNR->n_flags & FINT)
90 			++varFNR->n_int; else
91 			(void)exprreduce(incFNR);
92 	}
93 	if (cp == linebuf) {
94 		lbuflen = reclen;
95 		splitdone = 0;
96 		if (needsplit)
97 			fieldsplit();
98 	}
99 	/* if record length is too long then bail out */
100 	if (reclen > NLINE - 2) {
101 		awkerr(gettext("Record too long (LIMIT: %d bytes)"),
102 			NLINE - 1);
103 		/* Not Reached */
104 	}
105 	return (1);
106 }
107 
108 /*
109  * Return the next file from the command line.
110  * Return FNULL when no more files.
111  * Sets awkinfp variable to the new current input file.
112  */
113 static FILE *
114 newfile()
115 {
116 	static int argindex = 1;
117 	static int filedone;
118 	register wchar_t *ap;
119 	register int argc;
120 	wchar_t *arg;
121 	extern void strescape(wchar_t *);
122 
123 	argc = (int)exprint(varARGC);
124 	for (;;) {
125 		if (argindex >= argc) {
126 			if (filedone)
127 				return (FNULL);
128 			++filedone;
129 			awkinfp = stdin;
130 			arg = M_MB_L("-");
131 			break;
132 		}
133 		constant->n_int = argindex++;
134 		arg = (wchar_t *)exprstring(ARGVsubi);
135 		if ((ap = wcschr(arg, '=')) != NULL) {
136 			*ap = '\0';
137 			strescape(ap+1);
138 			strassign(vlook(arg),linebuf,FALLOC|FSENSE,
139 				wcslen(linebuf));
140 			*ap = '=';
141 			continue;
142 		}
143 		if (arg[0] == '\0')
144 			continue;
145 		++filedone;
146 		if (arg[0]=='-' && arg[1]=='\0') {
147 			awkinfp = stdin;
148 			break;
149 		}
150 		if ((awkinfp = fopen(mbunconvert(arg), r)) == FNULL) {
151 			(void) fprintf(stderr, gettext("input file \"%s\""),
152 				mbunconvert(arg));
153 			exstat = 1;
154 			continue;
155 		}
156 		break;
157 	}
158 	strassign(varFILENAME, arg, FALLOC, wcslen(arg));
159 	if (varFNR->n_flags & FINT)
160 		varFNR->n_int = 0; else
161 		(void)exprreduce(clrFNR);
162 	return (awkinfp);
163 }
164 
165 /*
166  * Default record reading code
167  * Uses fgets for potential speedups found in some (e.g. MKS)
168  * stdio packages.
169  */
170 wchar_t *
171 defrecord(wchar_t *bp, int lim, FILE *fp)
172 {
173 	register wchar_t *endp;
174 
175 	if (fgetws(bp, lim, fp) == NULL) {
176 		*bp = '\0';
177 		return (NULL);
178 	}
179 /* XXXX
180 	switch (fgetws(bp, lim, fp)) {
181 	case M_FGETS_EOF:
182 		*bp = '\0';
183 		return (NULL);
184 	case M_FGETS_BINARY:
185 		awkerr(gettext("file is binary"));
186 	case M_FGETS_LONG:
187 		awkerr(gettext("line too long: limit %d"),
188 			lim);
189 	case M_FGETS_ERROR:
190 		awkperr(gettext("error reading file"));
191 	}
192 */
193 
194 	if (*(endp = (bp + (reclen = wcslen(bp))-1)) == '\n') {
195 		*endp = '\0';
196 		reclen--;
197 	}
198 	return (bp);
199 }
200 
201 /*
202  * Read a record separated by one character in the RS.
203  * Compatible calling sequence with fgets, but don't include
204  * record separator character in string.
205  */
206 wchar_t *
207 charrecord(wchar_t *abp, int alim, FILE *fp)
208 {
209 	register wchar_t *bp;
210 	register wint_t c;
211 	register int limit = alim;
212 	register wint_t endc;
213 
214 	bp = abp;
215 	endc = *(wchar_t *)varRS->n_string;
216 	while (--limit>0 && (c = getwc(fp))!=endc && c!=WEOF)
217 		*bp++ = c;
218 	*bp = '\0';
219 	reclen = bp-abp;
220 	return (c==WEOF && bp==abp ? NULL : abp);
221 }
222 
223 /*
224  * Special routine for multiple line records.
225  */
226 wchar_t *
227 multirecord(wchar_t *abp, int limit, FILE *fp)
228 {
229 	register wchar_t *bp;
230 	register int c;
231 
232 	while ((c = getwc(fp)) == '\n')
233 		;
234 	bp = abp;
235 	if (c != WEOF) do {
236 		if (--limit == 0)
237 			break;
238 		if (c=='\n' && bp[-1]=='\n')
239 			break;
240 
241 		*bp++ = c;
242 	} while ((c = getwc(fp)) != WEOF);
243 	*bp = '\0';
244 	if (bp > abp)
245 		*--bp = '\0';
246 	reclen = bp-abp;
247 	return (c==WEOF && bp==abp ? NULL : abp);
248 }
249 
250 /*
251  * Look for fields separated by spaces, tabs or newlines.
252  * Extract the next field, given pointer to start address.
253  * Return pointer to beginning of field or NULL.
254  * Reset end of field reference, which is the beginning of the
255  * next field.
256  */
257 wchar_t *
258 whitefield(wchar_t **endp)
259 {
260 	register wchar_t *sp;
261 	register wchar_t *ep;
262 
263 	sp = *endp;
264 	while (*sp==' ' || *sp=='\t' || *sp=='\n')
265 		++sp;
266 	if (*sp == '\0')
267 		return (NULL);
268 	for (ep = sp; *ep!=' ' && *ep!='\0' && *ep!='\t' && *ep!='\n'; ++ep)
269 		;
270 	*endp = ep;
271 	return (sp);
272 }
273 
274 /*
275  * Look for fields separated by non-whitespace characters.
276  * Same calling sequence as whitefield().
277  */
278 wchar_t *
279 blackfield(wchar_t **endp)
280 {
281 	register wchar_t *cp;
282 	register int endc;
283 
284 	endc = *(wchar_t *)varFS->n_string;
285 	cp = *endp;
286 	if (*cp == '\0')
287 		return (NULL);
288 	if (*cp==endc && fcount!=0)
289 		cp++;
290 	if ((*endp = wcschr(cp, endc)) == NULL)
291 		*endp = wcschr(cp, '\0');
292 	return (cp);
293 }
294 
295 /*
296  * This field separation routine uses the same logic as
297  * blackfield but uses a regular expression to separate
298  * the fields.
299  */
300 wchar_t *
301 refield(wchar_t **endpp)
302 {
303 	register wchar_t *cp, *start;
304 	register int flags;
305 	static	REGWMATCH_T match[10];
306 	int result;
307 
308 	cp = *endpp;
309 	if (*cp == '\0') {
310 		match[0].rm_ep = NULL;
311 		return (NULL);
312 	}
313 	if (match[0].rm_ep != NULL) {
314 		flags = REG_NOTBOL;
315 		cp = (wchar_t *)match[0].rm_ep;
316 	} else
317 		flags = 0;
318 	start = cp;
319 again:
320 	switch ((result = REGWEXEC(resep, cp, 10, match, flags))) {
321 	case REG_OK:
322 		/*
323 		 * Check to see if a null string was matched. If this is the
324 		 * case, then move the current pointer beyond this position.
325 		 */
326 		if (match[0].rm_sp == match[0].rm_ep) {
327 			cp = (wchar_t *)match[0].rm_sp;
328 			if (*cp++ != '\0') {
329 				goto again;
330 			}
331 		}
332 		*endpp = (wchar_t *)match[0].rm_sp;
333 		break;
334 	case REG_NOMATCH:
335 		match[0].rm_ep = NULL;
336 		*endpp = wcschr(cp, '\0');
337 		break;
338 	default:
339 		regerror(result, resep, (char *)linebuf, sizeof(linebuf));
340 		awkerr(gettext("error splitting record: %s"),
341 			(char *)linebuf);
342 	}
343 	return (start);
344 }
345 
346 /*
347  * do begin processing
348  */
349 void
350 dobegin()
351 {
352 	/*l
353 	 * Free all keyword nodes to save space.
354 	 */
355 	{
356 		NODE *np;
357 		int nbuck;
358 		register NODE *knp;
359 
360 		np = NNULL;
361 		nbuck = 0;
362 		while ((knp = symwalk(&nbuck, &np)) != NNULL)
363 			if (knp->n_type == KEYWORD)
364 				delsymtab(knp, 1);
365 	}
366 	/*l
367 	 * Copy ENVIRON array only if needed.
368 	 * Note the convoluted work to assign to an array
369 	 * and that the temporary nodes will be freed by
370 	 * freetemps() because we are "running".
371 	 */
372 	if (needenviron) {
373 		register char **app;
374 		register wchar_t *name, *value;
375 		register NODE *namep = stringnode(_null, FSTATIC, 0);
376 		register NODE *valuep = stringnode(_null, FSTATIC, 0);
377 		register NODE *ENVsubname = node(INDEX, varENVIRON, namep);
378 		extern char **environ;
379 
380 		/* (void) m_setenv(); XXX what's this do? */
381 		for (app = environ; *app != NULL;) {
382 			name = mbstowcsdup(*app++);
383 
384 			if ((value = wcschr(name, '=')) != NULL) {
385 				*value++ = '\0';
386 				valuep->n_strlen = wcslen(value);
387 				valuep->n_string = value;
388 			} else {
389 				valuep->n_strlen = 0;
390 				valuep->n_string = _null;
391 			}
392 			namep->n_strlen = wcslen(namep->n_string = name);
393 			(void)assign(ENVsubname, valuep);
394 			if (value != NULL)
395 				value[-1] = '=';
396 		}
397 	}
398 	phase = BEGIN;
399 	execute(yytree);
400 	phase = 0;
401 	if (npattern == 0)
402 		doend(0);
403 	/*
404 	 * Delete all pattern/action rules that are BEGIN at this
405 	 * point to save space.
406 	 * NOTE: this is not yet implemented.
407 	 */
408 }
409 
410 /*
411  * Do end processing.
412  * Exit with a status
413  */
414 void
415 doend(int s)
416 {
417 	register OFILE *op;
418 
419 	if (phase != END) {
420 		phase = END;
421 		awkinfp = stdin;
422 		execute(yytree);
423 	}
424 	for (op = &ofiles[0]; op < &ofiles[NIOSTREAM]; op++)
425 		if (op->f_fp != FNULL)
426 			awkclose(op);
427 	if (awkinfp == stdin)
428 		(void) fflush(awkinfp);
429 	exit(s);
430 }
431 
432 /*
433  * Print statement.
434  */
435 void
436 s_print(NODE *np)
437 {
438 	register FILE *fp;
439 	NODE *listp;
440 	register char *ofs;
441 	register int notfirst = 0;
442 
443 	fp = openfile(np->n_right, 1, 1);
444 	if (np->n_left == NNULL)
445 		(void)fputs(mbunconvert(linebuf), fp);
446 	else {
447 		ofs =  wcstombsdup((isstring(varOFS->n_flags)) ?
448 			(wchar_t *)varOFS->n_string :
449 			(wchar_t *)exprstring(varOFS));
450 		listp = np->n_left;
451 		while ((np = getlist(&listp)) != NNULL) {
452 			if (notfirst++)
453 				(void)fputs(ofs, fp);
454 			np = exprreduce(np);
455 			if (np->n_flags & FINT)
456 				(void) fprintf(fp, "%lld", (INT)np->n_int);
457 			else if (isstring(np->n_flags))
458 				(void) fprintf(fp, "%S",
459 					np->n_string);
460 			else
461 				(void) fprintf(fp,
462 				  mbunconvert((wchar_t *)exprstring(varOFMT)),
463 				    (double)np->n_real);
464 		}
465 		free(ofs);
466 	}
467 	(void)fputs(mbunconvert(isstring(varORS->n_flags) ?
468 	    (wchar_t *)varORS->n_string : (wchar_t *)exprstring(varORS)),
469 	    fp);
470 	if (ferror(fp))
471 		awkperr("error on print");
472 }
473 
474 /*
475  * printf statement.
476  */
477 void
478 s_prf(NODE *np)
479 {
480 	register FILE *fp;
481 
482 	fp = openfile(np->n_right, 1, 1);
483 	(void)xprintf(np->n_left, fp, (wchar_t **)NULL);
484 	if (ferror(fp))
485 		awkperr("error on printf");
486 }
487 
488 /*
489  * Get next input line.
490  * Read into variable on left of node (or $0 if NULL).
491  * Read from pipe or file on right of node (or from regular
492  * input if NULL).
493  * This is an oddball inasmuch as it is a function
494  * but parses more like the keywords print, etc.
495  */
496 NODE *
497 f_getline(NODE *np)
498 {
499 	register wchar_t *cp;
500 	register INT ret;
501 	register FILE *fp;
502 	register size_t len;
503 
504 	if (np->n_right == NULL && phase == END) {
505 		/* Pretend we've reached end of (the non-existant) file. */
506 		return intnode(0);
507         }
508 
509 	if ((fp = openfile(np->n_right, 0, 0)) != FNULL) {
510 		if (np->n_left == NNULL) {
511 			ret = nextrecord(linebuf, fp);
512 		} else {
513 			cp = emalloc(NLINE * sizeof(wchar_t));
514 			ret = nextrecord(cp, fp);
515 			np = np->n_left;
516 			len = wcslen(cp);
517 			cp = erealloc(cp, (len+1)*sizeof(wchar_t));
518 			if (isleaf(np->n_flags)) {
519 				if (np->n_type == PARM)
520 					np = np->n_next;
521 				strassign(np, cp, FNOALLOC, len);
522 			} else
523 				(void)assign(np, stringnode(cp, FNOALLOC, len));
524 		}
525 	} else
526 		ret = -1;
527 	return (intnode(ret));
528 }
529 
530 /*
531  * Open a file.  Flag is non-zero for output.
532  */
533 static FILE *
534 openfile(NODE *np, int flag, int fatal)
535 {
536 	register OFILE *op;
537 	register char *cp;
538 	register FILE *fp;
539 	register int type;
540 	register OFILE *fop;
541 
542 	if (np == NNULL) {
543 		if (flag)
544 			return (stdout);
545 		if (awkinfp == FNULL)
546 			awkinfp = newfile();
547 		return (awkinfp);
548 	}
549 	if ((type = np->n_type) == APPEND)
550 		type = WRITE;
551 	cp = mbunconvert(exprstring(np->n_left));
552 	fop = (OFILE *)NULL;
553 	for (op = &ofiles[0]; op < &ofiles[NIOSTREAM]; op++) {
554 		if (op->f_fp == FNULL) {
555 			if (fop == (OFILE *)NULL)
556 				fop = op;
557 			continue;
558 		}
559 		if (op->f_mode == type
560 		 && strcmp(op->f_name, cp)==0)
561 			return (op->f_fp);
562 	}
563 	if (fop == (OFILE *)NULL)
564 		awkerr(gettext("too many open streams to %s onto \"%s\""),
565 		    flag ? "print/printf" : "getline", cp);
566 	(void) fflush(stdout);
567 	op = fop;
568 	if (cp[0]=='-' && cp[1]=='\0') {
569 		fp = flag ? stdout : stdin;
570 	} else {
571 		switch (np->n_type) {
572 		case WRITE:
573 			if ((fp = fopen(cp, w)) != FNULL) {
574 				if (isatty(fileno(fp)))
575 					(void) setvbuf(fp, 0, _IONBF, 0);
576 			}
577 			break;
578 
579 		case APPEND:
580 			fp = fopen(cp, "a");
581 			break;
582 
583 		case PIPE:
584 			fp = popen(cp, w);
585 			(void) setvbuf(fp, (char *) 0, _IOLBF, 0);
586 			break;
587 
588 		case PIPESYM:
589 			fp = popen(cp, r);
590 			break;
591 
592 		case LT:
593 			fp = fopen(cp, r);
594 			break;
595 
596 		default:
597 			awkerr(interr, "openfile");
598 		}
599 	}
600 	if (fp != FNULL) {
601 		op->f_name = strdup(cp);
602 		op->f_fp = fp;
603 		op->f_mode = type;
604 	} else if (fatal) {
605 		awkperr(flag ? gettext("output file \"%s\"") :
606 			gettext("input file \"%s\""), cp);
607 	}
608 	return (fp);
609 }
610 
611 /*
612  * Close a stream.
613  */
614 void
615 awkclose(OFILE *op)
616 {
617 	if (op->f_mode==PIPE || op->f_mode==PIPESYM)
618 		(void)pclose(op->f_fp);
619 	else if (fclose(op->f_fp) == EOF)
620 		awkperr("error on stream \"%s\"", op->f_name);
621 	op->f_fp = FNULL;
622 	free(op->f_name);
623 	op->f_name = NULL;
624 }
625 
626 /*
627  * Internal routine common to printf, sprintf.
628  * The node is that describing the arguments.
629  * Returns the number of characters written to file
630  * pointer `fp' or the length of the string return
631  * in cp. If cp is NULL then the file pointer is used. If
632  * cp points to a string pointer, a pointer to an allocated
633  * buffer will be returned in it.
634  */
635 size_t
636 xprintf(NODE *np, FILE *fp, wchar_t **cp)
637 {
638 	register wchar_t *fmt;
639 	register int c;
640 	wchar_t *bptr = (wchar_t *)NULL;
641 	char fmtbuf[40];
642 	register size_t length = 0;
643 	register char *ofmtp;
644 	register NODE *fnp;
645 	register wchar_t *fmtsave;
646 	int slen;
647 	int cplen;
648 
649 	fnp = getlist(&np);
650 	if (isleaf(fnp->n_flags) && fnp->n_type==PARM)
651 		fnp = fnp->n_next;
652 	if (isstring(fnp->n_flags)) {
653 		fmt = fnp->n_string;
654 		fmtsave = NULL;
655 	} else
656 		fmtsave = fmt = (wchar_t *)strsave(exprstring(fnp));
657 
658 	/*
659 	 * if a char * pointer has been passed in then allocate an initial
660 	 * buffer for the string. Make it LINE_MAX plus the length of
661 	 * the format string but do reallocs only based LINE_MAX.
662 	 */
663 	if (cp != (wchar_t **)NULL) {
664 		cplen = LINE_MAX;
665 		bptr = *cp = emalloc(sizeof(wchar_t) * (cplen + wcslen(fmt)));
666 	}
667 
668 	while ((c = *fmt++) != '\0') {
669 		if (c != '%') {
670 			if (bptr == (wchar_t *)NULL)
671 				awk_putwc(c, fp);
672 			else
673 				*bptr++ = c;
674 			++length;
675 			continue;
676 		}
677 		ofmtp = fmtbuf;
678 		*ofmtp++ = (char)c;
679 	nextc:
680 		switch (c = *fmt++) {
681 		case '%':
682 			if (bptr == (wchar_t *)NULL)
683 				awk_putwc(c, fp);
684 			else
685 				*bptr++ = c;
686 			++length;
687 			continue;
688 
689 		case 'c':
690 			*ofmtp++ = 'w';
691 			*ofmtp++ = 'c';
692 			*ofmtp = '\0';
693 			fnp = exprreduce(nextarg(&np));
694 			if (isnumber(fnp->n_flags))
695 				c = exprint(fnp);
696 			else
697 				c = *(wchar_t *)exprstring(fnp);
698 			if (bptr == (wchar_t *)NULL)
699 				length += fprintf(fp, fmtbuf, c);
700 			else {
701 				/*
702 				 * Make sure that the buffer is long
703 				 * enough to hold the formatted string.
704 				 */
705 				adjust_buf(cp, &cplen, &bptr, fmtbuf, 0);
706 				/*
707 				 * Since the call to adjust_buf() has already
708 				 * guaranteed that the buffer will be long
709 				 * enough, just pass in INT_MAX as
710 				 * the length.
711 				 */
712 				(void) wsprintf(bptr, (const char *) fmtbuf, c);
713 				bptr += (slen = wcslen(bptr));
714 				length += slen;
715 			}
716 			continue;
717 /* XXXX Is this bogus? Figure out what s & S mean - look at original code */
718 		case 's':
719 		case 'S':
720 			*ofmtp++ = 'w';
721 			*ofmtp++ = 's';
722 			*ofmtp = '\0';
723 			if (bptr == (wchar_t *)NULL)
724 				length += fprintf(fp, fmtbuf,
725 				    (wchar_t *)exprstring(nextarg(&np)));
726 			else {
727 				wchar_t *ts = exprstring(nextarg(&np));
728 
729 				adjust_buf(cp, &cplen, &bptr, fmtbuf,
730 					   wcslen(ts));
731 				(void) wsprintf(bptr, (const char *) fmtbuf,
732 						ts);
733 				bptr += (slen = wcslen(bptr));
734 				length += slen;
735 			}
736 			continue;
737 
738 		case 'o':
739 		case 'O':
740 		case 'X':
741 		case 'x':
742 		case 'd':
743 		case 'i':
744 		case 'D':
745 		case 'U':
746 		case 'u':
747 			*ofmtp++ = 'l';
748 			*ofmtp++ = 'l'; /* now dealing with long longs */
749 			*ofmtp++ = c;
750 			*ofmtp = '\0';
751 			if (bptr == (wchar_t *)NULL)
752 				length += fprintf(fp, fmtbuf,
753 					exprint(nextarg(&np)));
754 			else {
755 				adjust_buf(cp, &cplen, &bptr, fmtbuf, 0);
756 				(void) wsprintf(bptr, (const char *) fmtbuf,
757 					 exprint(nextarg(&np)));
758 				bptr += (slen = wcslen(bptr));
759 				length += slen;
760 			}
761 			continue;
762 
763 		case 'e':
764 		case 'E':
765 		case 'f':
766 		case 'F':
767 		case 'g':
768 		case 'G':
769 			*ofmtp++ = c;
770 			*ofmtp = '\0';
771 			if (bptr == (wchar_t *)NULL)
772 				length += fprintf(fp, fmtbuf,
773 					exprreal(nextarg(&np)));
774 			else {
775 				adjust_buf(cp, &cplen, &bptr, fmtbuf, 0);
776 				(void) wsprintf(bptr, (const char *) fmtbuf,
777 					 exprreal(nextarg(&np)));
778 				bptr += (slen = wcslen(bptr));
779 				length += slen;
780 			}
781 			continue;
782 
783 		case 'l':
784 		case 'L':
785 			break;
786 
787 		case '*':
788 #ifdef M_BSD_SPRINTF
789 			sprintf(ofmtp, "%lld", (INT)exprint(nextarg(&np)));
790 			ofmtp += strlen(ofmtp);
791 #else
792 			ofmtp += sprintf(ofmtp, "%lld", (INT)exprint(nextarg(&np)));
793 #endif
794 			break;
795 
796 		default:
797 			if(c=='\0') {
798 				*ofmtp = (wchar_t)NULL;
799 				fprintf(fp,"%s",fmtbuf);
800 				continue;
801 			}
802 			else {
803 				*ofmtp++ = (wchar_t)c;
804 				break;
805 			}
806 		}
807 		goto nextc;
808 	}
809 	if (fmtsave != NULL)
810 		free(fmtsave);
811 	/*
812 	 * If printing to a character buffer then make sure it is
813 	 * null-terminated and only uses as much space as required.
814 	 */
815 	if (bptr != (wchar_t *)NULL) {
816 		*bptr = '\0';
817 		*cp = erealloc(*cp, (length+1) * sizeof(wchar_t));
818 	}
819 	return (length);
820 }
821 
822 /*
823  * Return the next argument from the list.
824  */
825 static NODE *
826 nextarg(NODE **npp)
827 {
828 	register NODE *np;
829 
830 	if ((np = getlist(npp)) == NNULL)
831 		awkerr(gettext("insufficient arguments to printf or sprintf"));
832 	if (isleaf(np->n_flags) && np->n_type==PARM)
833 		return (np->n_next);
834 	return (np);
835 }
836 
837 
838 /*
839  * Check and adjust the length of the buffer that has been passed in
840  * to make sure that it has space to accomodate the sequence string
841  * described in fmtstr. This routine is used by xprintf() to allow
842  * for arbitrarily long sprintf() strings.
843  *
844  * bp		= start of current buffer
845  * len		= length of current buffer
846  * offset	= offset in current buffer
847  * fmtstr	= format string to check
848  * slen		= size of string for %s formats
849  */
850 static void
851 adjust_buf(wchar_t **bp, int *len, wchar_t **offset, char *fmtstr, size_t slen)
852 {
853 	int ioff;
854 	int width = 0;
855 	int prec = 0;
856 
857 	do {
858 		fmtstr++;
859 	} while (strchr("-+ 0", *fmtstr) != (char *)0 ||
860 		*fmtstr == ('#'));
861 	if (*fmtstr != '*') {
862 		if (isdigit(*fmtstr)) {
863 			width = *fmtstr-'0';
864 			while (isdigit(*++fmtstr))
865 				width = width * 10 + *fmtstr - '0';
866 		}
867 	} else
868 		fmtstr++;
869 	if (*fmtstr == '.') {
870 		if (*++fmtstr != '*') {
871 			prec = *fmtstr-'0';
872 			while (isdigit(*++fmtstr))
873 				prec = prec * 10 + *fmtstr - '0';
874 		} else
875 			fmtstr++;
876 	}
877 	if (strchr("Llh", *fmtstr) != (char *)0)
878 		fmtstr++;
879 	if (*fmtstr == 'S')
880 	{
881 		if (width && slen < width)
882 			slen = width;
883 		if (prec && slen > prec)
884 			slen = prec;
885 		width = slen+1;
886 	} else
887 		if (width == 0)
888 			width = NUMSIZE;
889 
890 	if (*offset+ width > *bp+ *len) {
891 		ioff = *offset-*bp;
892 		*len += width+1;
893 		*bp = erealloc(*bp, *len * sizeof(wchar_t));
894 		*offset = *bp+ioff;
895 	}
896 }
897 
898 static void
899 awk_putwc(wchar_t c, FILE *fp)
900 {
901 	char mb[MB_LEN_MAX];
902 	size_t mbl;
903 
904 	if ((mbl = wctomb(mb, c)) > 0) {
905 		mb[mbl] = '\0';
906 		(void)fputs(mb, fp);
907 	} else
908 		awkerr(gettext("invalid wide character %x"), c);
909 }
910 
911