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