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