xref: /titanic_44/usr/src/lib/libshell/common/edit/hexpand.c (revision 9b79392525856301c6f8962f189c2a32242af618)
1 /***********************************************************************
2 *                                                                      *
3 *               This software is part of the ast package               *
4 *          Copyright (c) 1982-2010 AT&T Intellectual Property          *
5 *                      and is licensed under the                       *
6 *                  Common Public License, Version 1.0                  *
7 *                    by AT&T Intellectual Property                     *
8 *                                                                      *
9 *                A copy of the License is available at                 *
10 *            http://www.opensource.org/licenses/cpl1.0.txt             *
11 *         (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9)         *
12 *                                                                      *
13 *              Information and Software Systems Research               *
14 *                            AT&T Research                             *
15 *                           Florham Park NJ                            *
16 *                                                                      *
17 *                  David Korn <dgk@research.att.com>                   *
18 *                                                                      *
19 ***********************************************************************/
20 #pragma prototyped
21 /*
22  * bash style history expansion
23  *
24  * Author:
25  * Karsten Fleischer
26  * Omnium Software Engineering
27  * An der Luisenburg 7
28  * D-51379 Leverkusen
29  * Germany
30  *
31  * <K.Fleischer@omnium.de>
32  */
33 
34 
35 #include "defs.h"
36 #include "edit.h"
37 
38 #if ! SHOPT_HISTEXPAND
39 
40 NoN(hexpand)
41 
42 #else
43 
44 static char *modifiers = "htrepqxs&";
45 static int mod_flags[] = { 0, 0, 0, 0, HIST_PRINT, HIST_QUOTE, HIST_QUOTE|HIST_QUOTE_BR, 0, 0 };
46 
47 #define	DONE()	{flag |= HIST_ERROR; cp = 0; stakseek(0); goto done;}
48 
49 struct subst
50 {
51 	char *str[2];	/* [0] is "old", [1] is "new" string */
52 };
53 
54 
55 /*
56  * parse an /old/new/ string, delimiter expected as first char.
57  * if "old" not specified, keep sb->str[0]
58  * if "new" not specified, set sb->str[1] to empty string
59  * read up to third delimeter char, \n or \0, whichever comes first.
60  * return adress is one past the last valid char in s:
61  * - the address containing \n or \0 or
62  * - one char beyond the third delimiter
63  */
64 
65 static char *parse_subst(const char *s, struct subst *sb)
66 {
67 	char	*cp,del;
68 	int	off,n = 0;
69 
70 	/* build the strings on the stack, mainly for '&' substition in "new" */
71 	off = staktell();
72 
73 	/* init "new" with empty string */
74 	if(sb->str[1])
75 		free(sb->str[1]);
76 	sb->str[1] = strdup("");
77 
78 	/* get delimiter */
79 	del = *s;
80 
81 	cp = (char*) s + 1;
82 
83 	while(n < 2)
84 	{
85 		if(*cp == del || *cp == '\n' || *cp == '\0')
86 		{
87 			/* delimiter or EOL */
88 			if(staktell() != off)
89 			{
90 				/* dupe string on stack and rewind stack */
91 				stakputc('\0');
92 				if(sb->str[n])
93 					free(sb->str[n]);
94 				sb->str[n] = strdup(stakptr(off));
95 				stakseek(off);
96 			}
97 			n++;
98 
99 			/* if not delimiter, we've reached EOL. Get outta here. */
100 			if(*cp != del)
101 				break;
102 		}
103 		else if(*cp == '\\')
104 		{
105 			if(*(cp+1) == del)	/* quote delimiter */
106 			{
107 				stakputc(del);
108 				cp++;
109 			}
110 			else if(*(cp+1) == '&' && n == 1)
111 			{		/* quote '&' only in "new" */
112 				stakputc('&');
113 				cp++;
114 			}
115 			else
116 				stakputc('\\');
117 		}
118 		else if(*cp == '&' && n == 1 && sb->str[0])
119 			/* substitute '&' with "old" in "new" */
120 			stakputs(sb->str[0]);
121 		else
122 			stakputc(*cp);
123 		cp++;
124 	}
125 
126 	/* rewind stack */
127 	stakseek(off);
128 
129 	return cp;
130 }
131 
132 /*
133  * history expansion main routine
134  */
135 
136 int hist_expand(const char *ln, char **xp)
137 {
138 	int	off,	/* stack offset */
139 		q,	/* quotation flags */
140 		p,	/* flag */
141 		c,	/* current char */
142 		flag=0;	/* HIST_* flags */
143 	Sfoff_t	n,	/* history line number, counter, etc. */
144 		i,	/* counter */
145 		w[2];	/* word range */
146 	char	*sp,	/* stack pointer */
147 		*cp,	/* current char in ln */
148 		*str,	/* search string */
149 		*evp,	/* event/word designator string, for error msgs */
150 		*cc=0,	/* copy of current line up to cp; temp ptr */
151 		hc[3],	/* default histchars */
152 		*qc="\'\"`";	/* quote characters */
153 	Sfio_t	*ref=0,	/* line referenced by event designator */
154 		*tmp=0,	/* temporary line buffer */
155 		*tmp2=0;/* temporary line buffer */
156 	Histloc_t hl;	/* history location */
157 	static Namval_t *np = 0;	/* histchars variable */
158 	static struct subst	sb = {0,0};	/* substition strings */
159 	static Sfio_t	*wm=0;	/* word match from !?string? event designator */
160 
161 	if(!wm)
162 		wm = sfopen(NULL, NULL, "swr");
163 
164 	hc[0] = '!';
165 	hc[1] = '^';
166 	hc[2] = 0;
167 	if((np = nv_open("histchars",sh.var_tree,0)) && (cp = nv_getval(np)))
168 	{
169 		if(cp[0])
170 		{
171 			hc[0] = cp[0];
172 			if(cp[1])
173 			{
174 				hc[1] = cp[1];
175 				if(cp[2])
176 					hc[2] = cp[2];
177 			}
178 		}
179 	}
180 
181 	/* save shell stack */
182 	if(off = staktell())
183 		sp = stakfreeze(0);
184 
185 	cp = (char*)ln;
186 
187 	while(cp && *cp)
188 	{
189 		/* read until event/quick substitution/comment designator */
190 		if((*cp != hc[0] && *cp != hc[1] && *cp != hc[2])
191 		   || (*cp == hc[1] && cp != ln))
192 		{
193 			if(*cp == '\\')	/* skip escaped designators */
194 				stakputc(*cp++);
195 			else if(*cp == '\'') /* skip quoted designators */
196 			{
197 				do
198 					stakputc(*cp);
199 				while(*++cp && *cp != '\'');
200 			}
201 			stakputc(*cp++);
202 			continue;
203 		}
204 
205 		if(hc[2] && *cp == hc[2]) /* history comment designator, skip rest of line */
206 		{
207 			stakputc(*cp++);
208 			stakputs(cp);
209 			DONE();
210 		}
211 
212 		n = -1;
213 		str = 0;
214 		flag &= HIST_EVENT; /* save event flag for returning later */
215 		evp = cp;
216 		ref = 0;
217 
218 		if(*cp == hc[1]) /* shortcut substitution */
219 		{
220 			flag |= HIST_QUICKSUBST;
221 			goto getline;
222 		}
223 
224 		if(*cp == hc[0] && *(cp+1) == hc[0]) /* refer to line -1 */
225 		{
226 			cp += 2;
227 			goto getline;
228 		}
229 
230 		switch(c = *++cp) {
231 		case ' ':
232 		case '\t':
233 		case '\n':
234 		case '\0':
235 		case '=':
236 		case '(':
237 			stakputc(hc[0]);
238 			continue;
239 		case '#': /* the line up to current position */
240 			flag |= HIST_HASH;
241 			cp++;
242 			n = staktell(); /* terminate string and dup */
243 			stakputc('\0');
244 			cc = strdup(stakptr(0));
245 			stakseek(n); /* remove null byte again */
246 			ref = sfopen(ref, cc, "s"); /* open as file */
247 			n = 0; /* skip history file referencing */
248 			break;
249 		case '-': /* back reference by number */
250 			if(!isdigit(*(cp+1)))
251 				goto string_event;
252 			cp++;
253 		case '0': /* reference by number */
254 		case '1':
255 		case '2':
256 		case '3':
257 		case '4':
258 		case '5':
259 		case '6':
260 		case '7':
261 		case '8':
262 		case '9':
263 			n = 0;
264 			while(isdigit(*cp))
265 				n = n * 10 + (*cp++) - '0';
266 			if(c == '-')
267 				n = -n;
268 			break;
269 		case '$':
270 			n = -1;
271 		case ':':
272 			break;
273 		case '?':
274 			cp++;
275 			flag |= HIST_QUESTION;
276 		string_event:
277 		default:
278 			/* read until end of string or word designator/modifier */
279 			str = cp;
280 			while(*cp)
281 			{
282 				cp++;
283 				if((!(flag&HIST_QUESTION) &&
284 				   (*cp == ':' || isspace(*cp)
285 				    || *cp == '^' || *cp == '$'
286 				    || *cp == '*' || *cp == '-'
287 				    || *cp == '%')
288 				   )
289 				   || ((flag&HIST_QUESTION) && (*cp == '?' || *cp == '\n')))
290 				{
291 					c = *cp;
292 					*cp = '\0';
293 				}
294 			}
295 			break;
296 		}
297 
298 getline:
299 		flag |= HIST_EVENT;
300 		if(str)	/* !string or !?string? event designator */
301 		{
302 
303 			/* search history for string */
304 			hl = hist_find(sh.hist_ptr, str,
305 				       sh.hist_ptr->histind,
306 				       flag&HIST_QUESTION, -1);
307 			if((n = hl.hist_command) == -1)
308 				n = 0;	/* not found */
309 		}
310 		if(n)
311 		{
312 			if(n < 0) /* determine index for backref */
313 				n = sh.hist_ptr->histind + n;
314 			/* search and use history file if found */
315 			if(n > 0 && hist_seek(sh.hist_ptr, n) != -1)
316 				ref = sh.hist_ptr->histfp;
317 
318 		}
319 		if(!ref)
320 		{
321 			/* string not found or command # out of range */
322 			c = *cp;
323 			*cp = '\0';
324 			errormsg(SH_DICT, ERROR_ERROR, "%s: event not found", evp);
325 			*cp = c;
326 			DONE();
327 		}
328 
329 		if(str) /* string search: restore orig. line */
330 		{
331 			if(flag&HIST_QUESTION)
332 				*cp++ = c; /* skip second question mark */
333 			else
334 				*cp = c;
335 		}
336 
337 		/* colon introduces either word designators or modifiers */
338 		if(*(evp = cp) == ':')
339 			cp++;
340 
341 		w[0] = 0; /* -1 means last word, -2 means match from !?string? */
342 		w[1] = -1; /* -1 means last word, -2 means suppress last word */
343 
344 		if(flag & HIST_QUICKSUBST) /* shortcut substitution */
345 			goto getsel;
346 
347 		n = 0;
348 		while(n < 2)
349 		{
350 			switch(c = *cp++) {
351 			case '^': /* first word */
352 				if(n == 0)
353 				{
354 					w[0] = w[1] = 1;
355 					goto skip;
356 				}
357 				else
358 					goto skip2;
359 			case '$': /* last word */
360 				w[n] = -1;
361 				goto skip;
362 			case '%': /* match from !?string? event designator */
363 				if(n == 0)
364 				{
365 					if(!str)
366 					{
367 						w[0] = 0;
368 						w[1] = -1;
369 						ref = wm;
370 					}
371 					else
372 					{
373 						w[0] = -2;
374 						w[1] = sftell(ref) + hl.hist_char;
375 					}
376 					sfseek(wm, 0, SEEK_SET);
377 					goto skip;
378 				}
379 			default:
380 			skip2:
381 				cp--;
382 				n = 2;
383 				break;
384 			case '*': /* until last word */
385 				if(n == 0)
386 					w[0] = 1;
387 				w[1] = -1;
388 			skip:
389 				flag |= HIST_WORDDSGN;
390 				n = 2;
391 				break;
392 			case '-': /* until last word or specified index */
393 				w[1] = -2;
394 				flag |= HIST_WORDDSGN;
395 				n = 1;
396 				break;
397 			case '0':
398 			case '1':
399 			case '2':
400 			case '3':
401 			case '4':
402 			case '5':
403 			case '6':
404 			case '7':
405 			case '8':
406 			case '9': /* specify index */
407 				if((*evp == ':') || w[1] == -2)
408 				{
409 					w[n] = c - '0';
410 					while(isdigit(c=*cp++))
411 						w[n] = w[n] * 10 + c - '0';
412 					flag |= HIST_WORDDSGN;
413 					if(n == 0)
414 						w[1] = w[0];
415 					n++;
416 				}
417 				else
418 					n = 2;
419 				cp--;
420 				break;
421 			}
422 		}
423 
424 		if(w[0] != -2 && w[1] > 0 && w[0] > w[1])
425 		{
426 			c = *cp;
427 			*cp = '\0';
428 			errormsg(SH_DICT, ERROR_ERROR, "%s: bad word specifier", evp);
429 			*cp = c;
430 			DONE();
431 		}
432 
433 		/* no valid word designator after colon, rewind */
434 		if(!(flag & HIST_WORDDSGN) && (*evp == ':'))
435 			cp = evp;
436 
437 getsel:
438 		/* open temp buffer, let sfio do the (re)allocation */
439 		tmp = sfopen(NULL, NULL, "swr");
440 
441 		/* push selected words into buffer, squash
442 		   whitespace into single blank or a newline */
443 		n = i = q = 0;
444 
445 		while((c = sfgetc(ref)) > 0)
446 		{
447 			if(isspace(c))
448 			{
449 				flag |= (c == '\n' ? HIST_NEWLINE : 0);
450 				continue;
451 			}
452 
453 			if(n >= w[0] && ((w[0] != -2) ? (w[1] < 0 || n <= w[1]) : 1))
454 			{
455 				if(w[0] < 0)
456 					sfseek(tmp, 0, SEEK_SET);
457 				else
458 					i = sftell(tmp);
459 
460 				if(i > 0)
461 					sfputc(tmp, flag & HIST_NEWLINE ? '\n' : ' ');
462 
463 				flag &= ~HIST_NEWLINE;
464 				p = 1;
465 			}
466 			else
467 				p = 0;
468 
469 			do
470 			{
471 				cc = strchr(qc, c);
472 				q ^= cc ? 1<<(int)(cc - qc) : 0;
473 				if(p)
474 					sfputc(tmp, c);
475 			}
476 			while((c = sfgetc(ref)) > 0  && (!isspace(c) || q));
477 
478 			if(w[0] == -2 && sftell(ref) > w[1])
479 				break;
480 
481 			flag |= (c == '\n' ? HIST_NEWLINE : 0);
482 			n++;
483 		}
484 		if(w[0] != -2 && w[1] >= 0 && w[1] >= n)
485 		{
486 			c = *cp;
487 			*cp = '\0';
488 			errormsg(SH_DICT, ERROR_ERROR, "%s: bad word specifier", evp);
489 			*cp = c;
490 			DONE();
491 		}
492 		else if(w[1] == -2)	/* skip last word */
493 			sfseek(tmp, i, SEEK_SET);
494 
495 		/* remove trailing newline */
496 		if(sftell(tmp))
497 		{
498 			sfseek(tmp, -1, SEEK_CUR);
499 			if(sfgetc(tmp) == '\n')
500 				sfungetc(tmp, '\n');
501 		}
502 
503 		sfputc(tmp, '\0');
504 
505 		if(str)
506 		{
507 			if(wm)
508 				sfclose(wm);
509 			wm = tmp;
510 		}
511 
512 		if(cc && (flag&HIST_HASH))
513 		{
514 			/* close !# temp file */
515 			sfclose(ref);
516 			flag &= ~HIST_HASH;
517 			free(cc);
518 			cc = 0;
519 		}
520 
521 		evp = cp;
522 
523 		/* selected line/words are now in buffer, now go for the modifiers */
524 		while(*cp == ':' || (flag & HIST_QUICKSUBST))
525 		{
526 			if(flag & HIST_QUICKSUBST)
527 			{
528 				flag &= ~HIST_QUICKSUBST;
529 				c = 's';
530 				cp--;
531 			}
532 			else
533 				c = *++cp;
534 
535 			sfseek(tmp, 0, SEEK_SET);
536 			tmp2 = sfopen(tmp2, NULL, "swr");
537 
538 			if(c == 'g') /* global substitution */
539 			{
540 				flag |= HIST_GLOBALSUBST;
541 				c = *++cp;
542 			}
543 
544 			if(cc = strchr(modifiers, c))
545 				flag |= mod_flags[cc - modifiers];
546 			else
547 			{
548 				errormsg(SH_DICT, ERROR_ERROR, "%c: unrecognized history modifier", c);
549 				DONE();
550 			}
551 
552 			if(c == 'h' || c == 'r') /* head or base */
553 			{
554 				n = -1;
555 				while((c = sfgetc(tmp)) > 0)
556 				{	/* remember position of / or . */
557 					if((c == '/' && *cp == 'h') || (c == '.' && *cp == 'r'))
558 						n = sftell(tmp2);
559 					sfputc(tmp2, c);
560 				}
561 				if(n > 0)
562 				{	 /* rewind to last / or . */
563 					sfseek(tmp2, n, SEEK_SET);
564 					/* end string there */
565 					sfputc(tmp2, '\0');
566 				}
567 			}
568 			else if(c == 't' || c == 'e') /* tail or suffix */
569 			{
570 				n = 0;
571 				while((c = sfgetc(tmp)) > 0)
572 				{	/* remember position of / or . */
573 					if((c == '/' && *cp == 't') || (c == '.' && *cp == 'e'))
574 						n = sftell(tmp);
575 				}
576 				/* rewind to last / or . */
577 				sfseek(tmp, n, SEEK_SET);
578 				/* copy from there on */
579 				while((c = sfgetc(tmp)) > 0)
580 					sfputc(tmp2, c);
581 			}
582 			else if(c == 's' || c == '&')
583 			{
584 				cp++;
585 
586 				if(c == 's')
587 				{
588 					/* preset old with match from !?string? */
589 					if(!sb.str[0] && wm)
590 						sb.str[0] = strdup(sfsetbuf(wm, (Void_t*)1, 0));
591 					cp = parse_subst(cp, &sb);
592 				}
593 
594 				if(!sb.str[0] || !sb.str[1])
595 				{
596 					c = *cp;
597 					*cp = '\0';
598 					errormsg(SH_DICT, ERROR_ERROR,
599 						 "%s%s: no previous substitution",
600 						(flag & HIST_QUICKSUBST) ? ":s" : "",
601 						evp);
602 					*cp = c;
603 					DONE();
604 				}
605 
606 				/* need pointer for strstr() */
607 				str = sfsetbuf(tmp, (Void_t*)1, 0);
608 
609 				flag |= HIST_SUBSTITUTE;
610 				while(flag & HIST_SUBSTITUTE)
611 				{
612 					/* find string */
613 					if(cc = strstr(str, sb.str[0]))
614 					{	/* replace it */
615 						c = *cc;
616 						*cc = '\0';
617 						sfputr(tmp2, str, -1);
618 						sfputr(tmp2, sb.str[1], -1);
619 						*cc = c;
620 						str = cc + strlen(sb.str[0]);
621 					}
622 					else if(!sftell(tmp2))
623 					{	/* not successfull */
624 						c = *cp;
625 						*cp = '\0';
626 						errormsg(SH_DICT, ERROR_ERROR,
627 							 "%s%s: substitution failed",
628 							(flag & HIST_QUICKSUBST) ? ":s" : "",
629 							evp);
630 						*cp = c;
631 						DONE();
632 					}
633 					/* loop if g modifier specified */
634 					if(!cc || !(flag & HIST_GLOBALSUBST))
635 						flag &= ~HIST_SUBSTITUTE;
636 				}
637 				/* output rest of line */
638 				sfputr(tmp2, str, -1);
639 				if(*cp)
640 					cp--;
641 			}
642 
643 			if(sftell(tmp2))
644 			{ /* if any substitions done, swap buffers */
645 				if(wm != tmp)
646 					sfclose(tmp);
647 				tmp = tmp2;
648 				tmp2 = 0;
649 			}
650 			cc = 0;
651 			if(*cp)
652 				cp++;
653 		}
654 
655 		/* flush temporary buffer to stack */
656 		if(tmp)
657 		{
658 			sfseek(tmp, 0, SEEK_SET);
659 
660 			if(flag & HIST_QUOTE)
661 				stakputc('\'');
662 
663 			while((c = sfgetc(tmp)) > 0)
664 			{
665 				if(isspace(c))
666 				{
667 					flag = flag & ~HIST_NEWLINE;
668 
669 					/* squash white space to either a
670 					   blank or a newline */
671 					do
672 						flag |= (c == '\n' ? HIST_NEWLINE : 0);
673 					while((c = sfgetc(tmp)) > 0 && isspace(c));
674 
675 					sfungetc(tmp, c);
676 
677 					c = (flag & HIST_NEWLINE) ? '\n' : ' ';
678 
679 					if(flag & HIST_QUOTE_BR)
680 					{
681 						stakputc('\'');
682 						stakputc(c);
683 						stakputc('\'');
684 					}
685 					else
686 						stakputc(c);
687 				}
688 				else if((c == '\'') && (flag & HIST_QUOTE))
689 				{
690 					stakputc('\'');
691 					stakputc('\\');
692 					stakputc(c);
693 					stakputc('\'');
694 				}
695 				else
696 					stakputc(c);
697 			}
698 			if(flag & HIST_QUOTE)
699 				stakputc('\'');
700 		}
701 	}
702 
703 	stakputc('\0');
704 
705 done:
706 	if(cc && (flag&HIST_HASH))
707 	{
708 		/* close !# temp file */
709 		sfclose(ref);
710 		free(cc);
711 		cc = 0;
712 	}
713 
714 	/* error? */
715 	if(staktell() && !(flag & HIST_ERROR))
716 		*xp = strdup(stakfreeze(1));
717 
718 	/* restore shell stack */
719 	if(off)
720 		stakset(sp,off);
721 	else
722 		stakseek(0);
723 
724 	/* drop temporary files */
725 
726 	if(tmp && tmp != wm)
727 		sfclose(tmp);
728 	if(tmp2)
729 		sfclose(tmp2);
730 
731 	return (flag & HIST_ERROR ? HIST_ERROR : flag & HIST_FLAG_RETURN_MASK);
732 }
733 
734 #endif
735