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