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