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
awk()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
nextrecord(wchar_t * cp,FILE * fp)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
isclvar(wchar_t * arg)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 *
newfile()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 *
defrecord(wchar_t * bp,int lim,FILE * fp)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 *
charrecord(wchar_t * abp,int alim,FILE * fp)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 *
multirecord(wchar_t * abp,int limit,FILE * fp)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 *
whitefield(wchar_t ** endp)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 *
blackfield(wchar_t ** endp)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 *
refield(wchar_t ** endpp)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
dobegin()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
doend(int s)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
s_print(NODE * np)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
s_prf(NODE * np)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 *
f_getline(NODE * np)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 *
openfile(NODE * np,int flag,int fatal)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
awkclose(OFILE * op)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
xprintf(NODE * np,FILE * fp,wchar_t ** cp)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 *
nextarg(NODE ** npp)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
adjust_buf(wchar_t ** bp,int * len,wchar_t ** offset,char * fmtstr,size_t slen)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
awk_putwc(wchar_t c,FILE * fp)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