1 /* @(#)input.c 1.41 18/10/07 Copyright 1985-2018 J. Schilling */
2 #include <schily/mconfig.h>
3 #ifndef lint
4 static UConst char sccsid[] =
5 "@(#)input.c 1.41 18/10/07 Copyright 1985-2018 J. Schilling";
6 #endif
7 /*
8 * bsh command interpreter - Input handling & Alias/Macro Expansion
9 *
10 * Copyright (c) 1985-2018 J. Schilling
11 *
12 * Exported functions:
13 * setinput(f) replaces the current input file
14 * nextch() fetches next char and places it into 'delim'
15 * peekch() peek next char - do not change 'delim'
16 * ungetch(c) pushes 'c' back into the input fstream
17 * nextline() return next line from input
18 * pushline(s) pushes back a line to input
19 * quote() increase quoting level
20 * unquote() decrease quoting level
21 * quoting() return current quoting level
22 * begina(beg) set begin alias flag for alias expansion
23 * sclearerr() clear any error condition on input FILE *
24 *
25 * Exported vars:
26 * int delim last character read by nextch()
27 *
28 * Imported functions:
29 * ab_value() from asym.c
30 * getpwdir() from bsh.c (if ~ expansion is enabled)
31 * make_line() from inputc.c
32 * makestr() from strsubs.c
33 * mkfstream() from libschily::fstream.c
34 * mypwhome() from bsh.c (if ~ expansion is enabled)
35 * fspushcha() from libschily::fstream.c
36 * fspushstr() from libschily::fstream.c
37 * fssetfile() from libschily::fstream.c
38 * fsgetc() from libschily::fstream.c
39 * syntax() from parse.c
40 *
41 * Imported vars:
42 * int ctlc from bsh.c
43 * erestricted from str.c
44 * BOOL noslash from bsh.c
45 * nullstr from str.c
46 * int prompt from bsh.c
47 * slash from str.c
48 */
49 /*
50 * The contents of this file are subject to the terms of the
51 * Common Development and Distribution License, Version 1.0 only
52 * (the "License"). You may not use this file except in compliance
53 * with the License.
54 *
55 * See the file CDDL.Schily.txt in this distribution for details.
56 * A copy of the CDDL is also available via the Internet at
57 * http://www.opensource.org/licenses/cddl1.txt
58 *
59 * When distributing Covered Code, include this CDDL HEADER in each
60 * file and include the License file CDDL.Schily.txt from this distribution.
61 */
62
63 #include <schily/stdio.h>
64 #include "bsh.h"
65 #include "abbrev.h"
66 #include "str.h"
67 #include "strsubs.h"
68 #include "ctype.h"
69 #include <schily/fstream.h>
70 #include <schily/unistd.h> /* getpid() */
71 #include <schily/stdlib.h>
72
73 #define DOL /* include '$' var expansion */
74 #ifdef NO_DOL
75 #undef DOL
76 #endif
77
78 #define MAX_ALIAS_NEST 64
79 #define IWORD_SIZE 1024
80 LOCAL char fsep[] = " \t\n\\':$~/|&;()><%\"=-"; /* Field separators for Expansion */
81
82 EXPORT int delim = EOF; /* Current input character */
83
84 extern BOOL noslash;
85 extern char *inithome;
86 extern pid_t lastbackgrnd;
87
88 LOCAL fstream *instrm = (fstream *) NULL; /* Alias expanded input fstream */
89 LOCAL fstream *rawstrm = (fstream *) NULL; /* Unexpanded input fstream */
90 LOCAL int qlevel = 0; /* Current quoting level */
91 LOCAL int dqlevel = 0; /* Current double quoting level */
92 LOCAL int xqlevel = 0; /* At the begin of "" quoting */
93 LOCAL int begalias = 0; /* Begin aliases allowed? */
94 LOCAL int nextbegin = 0; /* Begin aliases on next word */
95
96 LOCAL int _fsgetc __PR((fstream *is));
97 LOCAL int fillbuf __PR((int c, char *wbuf, fstream *is));
98 LOCAL int readchar __PR((fstream *fsp));
99 EXPORT int nextch __PR((void));
100 EXPORT int peekch __PR((void));
101 EXPORT void ungetch __PR((int c));
102 EXPORT char *nextline __PR((void));
103 EXPORT FILE *setinput __PR((FILE * f));
104 EXPORT void sclearerr __PR((void));
105 EXPORT void pushline __PR((char *s));
106 EXPORT void quote __PR((void));
107 EXPORT void unquote __PR((void));
108 EXPORT int quoting __PR((void));
109 EXPORT void dquote __PR((void));
110 EXPORT void undquote __PR((void));
111 EXPORT int dquoting __PR((void));
112 EXPORT void xquote __PR((void));
113 EXPORT void unxquote __PR((void));
114 EXPORT int begina __PR((BOOL beg));
115 LOCAL int input_expand __PR((fstream * os, fstream * is));
116
117 /*
118 * Wrapper for fsgetc() that calls fspop() is needed.
119 * This is needed since we added a fspush() to implement the push
120 * of data for the input from an alias replacement. fspush() is needed
121 * in order to keep track of the end of the related replacement text.
122 */
123 LOCAL int
_fsgetc(is)124 _fsgetc(is)
125 register fstream *is;
126 {
127 int c;
128
129 again:
130 c = fsgetc(is); /* The real stream lib ibterface */
131 if (c == EOF) {
132 fstream *pushed;
133
134 if ((pushed = fspushed(is)) != NULL) {
135 /*
136 * If we had a pushed begin alias that ends in
137 * a space or a TAB, re-enable begin aliases.
138 */
139 if (pushed->fstr_flags & 1)
140 begina(TRUE);
141 fspop(is);
142 goto again;
143 }
144 return (EOF);
145 }
146 return (c);
147 }
148 #define fsgetc _fsgetc
149
150 LOCAL int
fillbuf(c,wbuf,is)151 fillbuf(c, wbuf, is)
152 register int c;
153 register char *wbuf;
154 register fstream *is;
155 {
156 register char *s;
157
158 s = wbuf;
159 do {
160 *s++ = (char)c;
161 c = fsgetc(is);
162 } while (s < wbuf + IWORD_SIZE && c != EOF && !strchr(fsep, (char)c));
163 *s = '\0';
164 #ifdef SINGLEQUOTE
165 if (c != (int)'\'')
166 #endif
167 fspushcha(is, c); /* Not yet wanted - push back */
168
169 return (c);
170 }
171
172 LOCAL int
readchar(fsp)173 readchar(fsp)
174 register fstream *fsp;
175 {
176 #ifdef INTERACTIVE
177 extern int prompt;
178
179 pushline(get_line(prompt++, fsp->fstr_file));
180 return (fsgetc(fsp));
181 #else
182 return (getc(fsp->fstr_file)); /* read from FILE */
183 #endif
184 }
185
186 /*
187 * Fetch next expanded character from input and put it into 'delim'
188 */
189 EXPORT int
nextch()190 nextch()
191 {
192 int c;
193
194 c = fsgetc(instrm);
195
196 /*
197 * XXX: As long as we do not add special code, the handling of ^C will
198 * XXX: cause a memory leak from the laready parsed code.
199 */
200 if (c == EOF && delim == 3) { /* ^C signalled from editor */
201 delim = '\n'; /* avoid reading 'till EOL */
202 raisecond(sn_ctlc, (long)NULL); /* raise the condition */
203 return (delim);
204 }
205
206 if ((delim = c) == '/' && noslash)
207 syntax(erestricted, slash);
208 #ifdef DEBUG
209 putc(delim, stderr);
210 #endif
211 return (delim);
212 }
213
214 /*
215 * Peek next char but do not change 'delim'
216 */
217 EXPORT int
peekch()218 peekch()
219 {
220 int odelim = delim;
221 int this = nextch();
222
223 ungetch(this);
224 delim = odelim;
225
226 return (this);
227 }
228
229 /*
230 * Push back a single character
231 */
232 EXPORT void
ungetch(c)233 ungetch(c)
234 int c;
235 {
236 fspushcha(instrm, c);
237 }
238
239 /*
240 * Return next line in allocated string
241 */
242 #define f_nextch ((int (*)__PR((FILE *)))nextch)
243
244 EXPORT char *
nextline()245 nextline()
246 {
247 return (make_line(f_nextch, (void *)0));
248 }
249
250 /*
251 * Set up file from where the inout should be read,
252 * returns the old FILE * value.
253 */
254 #define f_input_expand ((fstr_fun)input_expand)
255
256 EXPORT FILE *
setinput(f)257 setinput(f)
258 FILE *f;
259 {
260 /*
261 * The raw fstream contains the unprocessed input.
262 */
263 if (rawstrm == (fstream *) NULL)
264 rawstrm = mkfstream(f, (fstr_fun)0, readchar, (fstr_efun)berror);
265 else
266 f = fssetfile(rawstrm, f);
267
268 /*
269 * The secondary fstream is a store for the processed input.
270 */
271 if (instrm == (fstream *) NULL) /* Pfusch in fsgetc */
272 instrm = mkfstream((FILE *) rawstrm, f_input_expand, (fstr_rfun)0,
273 (fstr_efun)berror);
274 qlevel = 0;
275 return (f);
276 }
277
278 /*
279 * clear error in rawstream
280 */
281 EXPORT void
sclearerr()282 sclearerr()
283 {
284 clearerr(rawstrm->fstr_file);
285 }
286
287 /*
288 * Push back a complete line
289 */
290 EXPORT void
pushline(s)291 pushline(s)
292 char *s;
293 {
294 if (s && rawstrm != (fstream *) NULL) {
295 fspushcha(rawstrm, delim);
296 fspushstr(rawstrm, s);
297 }
298 }
299
300
301 /*
302 * Increase quoting level by one
303 */
304 EXPORT void
quote()305 quote()
306 {
307 #ifdef DEBUG
308 fprintf(stderr, "QUOTE\n");
309 fflush(stderr);
310 #endif
311 qlevel++;
312 }
313
314 /*
315 * Decrease quoting level by one
316 */
317 EXPORT void
unquote()318 unquote()
319 {
320 #ifdef DEBUG
321 fprintf(stderr, "UNQUOTE\n");
322 fflush(stderr);
323 #endif
324 qlevel--;
325 }
326
327 EXPORT int
quoting()328 quoting()
329 {
330 return (qlevel);
331 }
332
333 /*
334 * Increase double quoting level by one
335 */
336 EXPORT void
dquote()337 dquote()
338 {
339 #ifdef DEBUG
340 fprintf(stderr, "DQUOTE\n");
341 fflush(stderr);
342 #endif
343 dqlevel++;
344 }
345
346 /*
347 * Decrease double quoting level by one
348 */
349 EXPORT void
undquote()350 undquote()
351 {
352 #ifdef DEBUG
353 fprintf(stderr, "UNDQUOTE\n");
354 fflush(stderr);
355 #endif
356 dqlevel--;
357 }
358
359 EXPORT int
dquoting()360 dquoting()
361 {
362 return (dqlevel);
363 }
364
365 /*
366 * Increase X-double quoting level by one
367 */
368 EXPORT void
xquote()369 xquote()
370 {
371 xqlevel++;
372 }
373
374 /*
375 * Decrease X-double quoting level by one
376 */
377 EXPORT void
unxquote()378 unxquote()
379 {
380 xqlevel--;
381 }
382
383
384 /*
385 * Set Begin Alias expansion flag based on parameter
386 */
387 EXPORT int
begina(beg)388 begina(beg)
389 BOOL beg;
390 {
391 int obegalias = begalias;
392
393 #ifdef DEBUG
394 fprintf(stderr, "begalias = %d\n", beg);
395 fflush(stderr);
396 #endif
397 begalias = beg?AB_BEGIN:0;
398
399 return (obegalias != 0);
400 }
401
402 /*
403 * Get the current state for Begin Alias expansion
404 */
405 EXPORT int
getbegina()406 getbegina()
407 {
408 return (begalias != 0);
409 }
410
411 /*
412 * Set Begin Alias expansion flag based on whether the alias
413 * layer previously detected an alias that ends in white space.
414 */
415 EXPORT int
setbegina()416 setbegina()
417 {
418 int obegalias = begalias;
419
420 begina(nextbegin);
421 nextbegin = 0;
422
423 return (obegalias != 0);
424 }
425
426
427 /*
428 * Apply macro expansion on Input while copying data from 'is' to 'os'
429 */
430 LOCAL int
input_expand(os,is)431 input_expand(os, is)
432 fstream *os;
433 fstream *is;
434 {
435 register int c;
436 register char *val;
437 char buf[IWORD_SIZE+1];
438 int loopcnt = MAX_ALIAS_NEST;
439 #ifdef DOL
440 int itmp = 0;
441 int vectype;
442 #endif
443
444 for (;;) {
445 c = fsgetc(is);
446 if (c == EOF) {
447 return (EOF);
448 }
449 if (c == '\\') {
450 c = fsgetc(is);
451 if (c != '\n' || qlevel) {
452 fspushcha(os, c);
453 /*if (qlevel)*/ /* old Quoting */
454 fspushcha(os, '\\');
455 begina(FALSE);
456 } else {
457 fspushcha(os, ' ');
458 }
459 break;
460 } else if (c == 3 && ctlc) { /* ^C signalled from editor */
461 return (EOF);
462 } else if (qlevel != 0) {
463 /*
464 * In quote mode just copy data
465 */
466 fspushcha(os, c);
467 begina(FALSE);
468 break;
469 } else if (!strchr(fsep, c) && !ctlc) {
470 void *seen = 0;
471 fstream *push = 0;
472
473 /*
474 * Could be a word, so try alias expansion
475 */
476 c = fillbuf(c, buf, is);
477 if ((push = fspushed(is)) != NULL)
478 seen = push->fstr_auxp;
479 if ((val = ab_value(LOCAL_AB, buf, &seen, begalias)) == NULL)
480 val = ab_value(GLOBAL_AB, buf, &seen, begalias);
481 #ifdef DEBUG
482 fprintf(stderr, "expanding '%s': ", buf);
483 fflush(stderr);
484 if (val == NULL)
485 fprintf(stderr, "-> NONE\n");
486 else
487 fprintf(stderr, "-> '%s'\n", val);
488 fflush(stderr);
489 #endif
490 if (val != NULL) {
491 if (--loopcnt >= 0) {
492 fstream *pushed = fspush(is,
493 (fstr_efun)berror);
494
495 if (pushed)
496 pushed->fstr_auxp = seen;
497 /*
498 * If a begin alias was expanded and the
499 * new text ends in a space or tab, the
500 * next word will be a begin alias too.
501 */
502 if (pushed && begalias != 0) {
503 int len = strlen(val);
504
505 if (len > 0 &&
506 (val[len-1] == ' ' ||
507 val[len-1] == '\t')) {
508 pushed->fstr_flags |= 1;
509 }
510 }
511 fspushstr(pushed?pushed:is, val);
512 } else {
513 syntax("Alias loop on '%s'.", buf);
514 break;
515 }
516 } else {
517 /*
518 * No replacement text found, so just push
519 * the original text.
520 */
521 fspushstr(os, buf);
522 begina(FALSE);
523 break;
524 }
525 } else if (c == '~' && !ctlc) {
526 c = fsgetc(is);
527 if (!strchr(fsep, c)) {
528 c = fillbuf(c, buf, is);
529 val = getpwdir(buf);
530 if (!val) {
531 fspushstr(is, buf);
532 val = makestr("~");
533 }
534 } else {
535 fspushcha(is, c);
536 if (!(val = mypwhome()))
537 val = makestr(nullstr);
538 }
539 fspushstr(os, val);
540 free(val);
541 begina(FALSE);
542 break;
543 #ifdef DOL
544 } else if (c == '$' && !ctlc) {
545 c = fsgetc(is);
546 if (!isdigit(c) && c != 'r' && c != '*' && c != '@') {
547 if (c == '#') {
548 val = malloc(10);
549 sprintf(val, "%d", vac);
550 fspushstr(os, val);
551 free(val);
552 } else if (c == '!' && lastbackgrnd) {
553 val = malloc(10);
554 sprintf(val, "%ld", (long)lastbackgrnd);
555 fspushstr(os, val);
556 free(val);
557 } else if (c == '$') {
558 val = malloc(10);
559 sprintf(val, "%ld", (long)getpid());
560 fspushstr(os, val);
561 free(val);
562 } else if (c == '?') {
563 val = malloc(10);
564 sprintf(val, "%d", ex_status);
565 fspushstr(os, val);
566 free(val);
567 } else if (!strchr(fsep, c)) {
568 c = fillbuf(c, buf, is);
569 if ((val = getcurenv(buf)) != NULL) {
570 fspushstr(is, val);
571 } else {
572 fspushstr(is, buf);
573 fspushcha(os, '$');
574 }
575 } else {
576 fspushcha(is, c);
577 fspushcha(os, '$');
578 }
579 begina(FALSE);
580 break;
581 }
582 vectype = 0;
583 if (c == '*') {
584 /*
585 * "$*" -> "$1 $2 ..."
586 */
587 for (c = vac; c > 1; ) {
588 fspushstr(os, vav[--c]);
589 if (c > 1)
590 fspushstr(os, " ");
591 begina(FALSE);
592 }
593 break;
594 } else if (c == '@') {
595 /*
596 * "$@" -> "$1" "$2" ...
597 */
598 if (xqlevel > 0 && vac <= 1 &&
599 *is->fstr_bp == '"') {
600 begina(FALSE);
601 /*
602 * Signal our caller that this argument
603 * needs to be copletely wiped out.
604 */
605 return (-2);
606 }
607 for (c = vac; c > 1; ) {
608 fspushstr(os, vav[--c]);
609 if (c > 1)
610 fspushstr(os, dqlevel > 0?
611 "\" \"":" ");
612 begina(FALSE);
613 }
614 break;
615 } else if (c == 'r') {
616 vectype = c;
617 c = fsgetc(is);
618 if (!isdigit(c)) {
619 fspushcha(is, c);
620 fspushstr(os, "$r");
621 break;
622 }
623 }
624 itmp = 0;
625 {
626 BOOL overflow = FALSE;
627 int maxmult = INT_MAX / 10;
628
629 while (isdigit(c)) {
630 c -= '0';
631 if (itmp > maxmult)
632 overflow = TRUE;
633 itmp *= 10;
634 if (INT_MAX - itmp < c)
635 overflow = TRUE;
636 itmp += c;
637 c = fsgetc(is);
638 }
639 if (overflow)
640 itmp = INT_MAX;
641 }
642 fspushcha(is, c);
643 if (vectype == 'r') {
644 /*
645 * "$r2" -> "$2" "$3" ...
646 */
647 for (c = vac; c > itmp; ) {
648 fspushstr(os, vav[--c]);
649 if (c > itmp)
650 fspushstr(os, dqlevel > 0?
651 "\" \"":" ");
652 begina(FALSE);
653 }
654 break;
655 } else if (itmp >= 0 && itmp < vac) {
656 fspushstr(os, vav[itmp]);
657 begina(FALSE);
658 break;
659 }
660 #endif /* DOL */
661 } else {
662 fspushcha(os, c);
663 if (!iswhite(c))
664 begina(FALSE);
665 break;
666 }
667 }
668 return (0);
669 }
670