Home | History | Annotate | Download | only in awk_xpg4
      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