1 /* $NetBSD: histedit.c,v 1.55 2019/02/10 19:21:52 kre Exp $ */ 2 3 /*- 4 * Copyright (c) 1993 5 * The Regents of the University of California. All rights reserved. 6 * 7 * This code is derived from software contributed to Berkeley by 8 * Kenneth Almquist. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 3. Neither the name of the University nor the names of its contributors 19 * may be used to endorse or promote products derived from this software 20 * without specific prior written permission. 21 * 22 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 23 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 25 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 26 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 27 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 28 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 29 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 31 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 32 * SUCH DAMAGE. 33 */ 34 35 #include <sys/cdefs.h> 36 #ifndef lint 37 #if 0 38 static char sccsid[] = "@(#)histedit.c 8.2 (Berkeley) 5/4/95"; 39 #else 40 __RCSID("$NetBSD: histedit.c,v 1.55 2019/02/10 19:21:52 kre Exp $"); 41 #endif 42 #endif /* not lint */ 43 44 #include <sys/param.h> 45 #include <paths.h> 46 #include <stdio.h> 47 #include <stdlib.h> 48 #include <unistd.h> 49 /* 50 * Editline and history functions (and glue). 51 */ 52 #include "shell.h" 53 #include "parser.h" 54 #include "var.h" 55 #include "options.h" 56 #include "builtins.h" 57 #include "main.h" 58 #include "output.h" 59 #include "mystring.h" 60 #include "myhistedit.h" 61 #include "error.h" 62 #include "alias.h" 63 #ifndef SMALL 64 #include "eval.h" 65 #include "memalloc.h" 66 67 #define MAXHISTLOOPS 4 /* max recursions through fc */ 68 #define DEFEDITOR "ed" /* default editor *should* be $EDITOR */ 69 70 History *hist; /* history cookie */ 71 EditLine *el; /* editline cookie */ 72 int displayhist; 73 static FILE *el_in, *el_out; 74 75 STATIC const char *fc_replace(const char *, char *, char *); 76 77 #ifdef DEBUG 78 extern FILE *tracefile; 79 #endif 80 81 /* 82 * Set history and editing status. Called whenever the status may 83 * have changed (figures out what to do). 84 */ 85 void 86 histedit(void) 87 { 88 FILE *el_err; 89 90 #define editing (Eflag || Vflag) 91 92 if (iflag == 1) { 93 if (!hist) { 94 /* 95 * turn history on 96 */ 97 INTOFF; 98 hist = history_init(); 99 INTON; 100 101 if (hist != NULL) 102 sethistsize(histsizeval()); 103 else 104 out2str("sh: can't initialize history\n"); 105 } 106 if (editing && !el && isatty(0)) { /* && isatty(2) ??? */ 107 /* 108 * turn editing on 109 */ 110 char *term, *shname; 111 112 INTOFF; 113 if (el_in == NULL) 114 el_in = fdopen(0, "r"); 115 if (el_out == NULL) 116 el_out = fdopen(2, "w"); 117 if (el_in == NULL || el_out == NULL) 118 goto bad; 119 el_err = el_out; 120 #if DEBUG 121 if (tracefile) 122 el_err = tracefile; 123 #endif 124 term = lookupvar("TERM"); 125 if (term) 126 setenv("TERM", term, 1); 127 else 128 unsetenv("TERM"); 129 shname = arg0; 130 if (shname[0] == '-') 131 shname++; 132 el = el_init(shname, el_in, el_out, el_err); 133 if (el != NULL) { 134 if (hist) 135 el_set(el, EL_HIST, history, hist); 136 137 set_prompt_lit(lookupvar("PSlit")); 138 el_set(el, EL_SIGNAL, 1); 139 el_set(el, EL_ALIAS_TEXT, alias_text, NULL); 140 el_set(el, EL_ADDFN, "rl-complete", 141 "ReadLine compatible completion function", 142 _el_fn_complete); 143 } else { 144 bad: 145 out2str("sh: can't initialize editing\n"); 146 } 147 INTON; 148 } else if (!editing && el) { 149 INTOFF; 150 el_end(el); 151 el = NULL; 152 INTON; 153 } 154 if (el) { 155 INTOFF; 156 if (Vflag) 157 el_set(el, EL_EDITOR, "vi"); 158 else if (Eflag) 159 el_set(el, EL_EDITOR, "emacs"); 160 el_set(el, EL_BIND, "^I", 161 tabcomplete ? "rl-complete" : "ed-insert", NULL); 162 el_source(el, lookupvar("EDITRC")); 163 INTON; 164 } 165 } else { 166 INTOFF; 167 if (el) { /* no editing if not interactive */ 168 el_end(el); 169 el = NULL; 170 } 171 if (hist) { 172 history_end(hist); 173 hist = NULL; 174 } 175 INTON; 176 } 177 } 178 179 void 180 set_prompt_lit(const char *lit_ch) 181 { 182 wchar_t wc; 183 184 if (!(iflag && editing && el)) 185 return; 186 187 if (lit_ch == NULL) { 188 el_set(el, EL_PROMPT, getprompt); 189 return; 190 } 191 192 mbtowc(&wc, NULL, 1); /* state init */ 193 194 INTOFF; 195 if (mbtowc(&wc, lit_ch, strlen(lit_ch)) <= 0) 196 el_set(el, EL_PROMPT, getprompt); 197 else 198 el_set(el, EL_PROMPT_ESC, getprompt, (int)wc); 199 INTON; 200 } 201 202 void 203 set_editrc(const char *fname) 204 { 205 INTOFF; 206 if (iflag && editing && el) 207 el_source(el, fname); 208 INTON; 209 } 210 211 void 212 sethistsize(const char *hs) 213 { 214 int histsize; 215 HistEvent he; 216 217 if (hist != NULL) { 218 if (hs == NULL || *hs == '\0' || *hs == '-' || 219 (histsize = number(hs)) < 0) 220 histsize = 100; 221 INTOFF; 222 history(hist, &he, H_SETSIZE, histsize); 223 history(hist, &he, H_SETUNIQUE, 1); 224 INTON; 225 } 226 } 227 228 void 229 setterm(const char *term) 230 { 231 INTOFF; 232 if (el != NULL && term != NULL) 233 if (el_set(el, EL_TERMINAL, term) != 0) { 234 outfmt(out2, "sh: Can't set terminal type %s\n", term); 235 outfmt(out2, "sh: Using dumb terminal settings.\n"); 236 } 237 INTON; 238 } 239 240 int 241 inputrc(int argc, char **argv) 242 { 243 if (argc != 2) { 244 out2str("usage: inputrc file\n"); 245 return 1; 246 } 247 if (el != NULL) { 248 INTOFF; 249 if (el_source(el, argv[1])) { 250 INTON; 251 out2str("inputrc: failed\n"); 252 return 1; 253 } 254 INTON; 255 return 0; 256 } else { 257 out2str("sh: inputrc ignored, not editing\n"); 258 return 1; 259 } 260 } 261 262 /* 263 * This command is provided since POSIX decided to standardize 264 * the Korn shell fc command. Oh well... 265 */ 266 int 267 histcmd(volatile int argc, char ** volatile argv) 268 { 269 int ch; 270 const char * volatile editor = NULL; 271 HistEvent he; 272 volatile int lflg = 0, nflg = 0, rflg = 0, sflg = 0; 273 int i, retval; 274 const char *firststr, *laststr; 275 int first, last, direction; 276 char * volatile pat = NULL, * volatile repl; /* ksh "fc old=new" crap */ 277 static int active = 0; 278 struct jmploc jmploc; 279 struct jmploc *volatile savehandler; 280 char editfile[MAXPATHLEN + 1]; 281 FILE * volatile efp; 282 #ifdef __GNUC__ 283 repl = NULL; /* XXX gcc4 */ 284 efp = NULL; /* XXX gcc4 */ 285 #endif 286 287 if (hist == NULL) 288 error("history not active"); 289 290 if (argc == 1) 291 error("missing history argument"); 292 293 optreset = 1; optind = 1; /* initialize getopt */ 294 while (not_fcnumber(argv[optind]) && 295 (ch = getopt(argc, argv, ":e:lnrs")) != -1) 296 switch ((char)ch) { 297 case 'e': 298 editor = optionarg; 299 break; 300 case 'l': 301 lflg = 1; 302 break; 303 case 'n': 304 nflg = 1; 305 break; 306 case 'r': 307 rflg = 1; 308 break; 309 case 's': 310 sflg = 1; 311 break; 312 case ':': 313 error("option -%c expects argument", optopt); 314 /* NOTREACHED */ 315 case '?': 316 default: 317 error("unknown option: -%c", optopt); 318 /* NOTREACHED */ 319 } 320 argc -= optind, argv += optind; 321 322 /* 323 * If executing... 324 */ 325 if (lflg == 0 || editor || sflg) { 326 lflg = 0; /* ignore */ 327 editfile[0] = '\0'; 328 /* 329 * Catch interrupts to reset active counter and 330 * cleanup temp files. 331 */ 332 savehandler = handler; 333 if (setjmp(jmploc.loc)) { 334 active = 0; 335 if (*editfile) 336 unlink(editfile); 337 handler = savehandler; 338 longjmp(handler->loc, 1); 339 } 340 handler = &jmploc; 341 if (++active > MAXHISTLOOPS) { 342 active = 0; 343 displayhist = 0; 344 error("called recursively too many times"); 345 } 346 /* 347 * Set editor. 348 */ 349 if (sflg == 0) { 350 if (editor == NULL && 351 (editor = bltinlookup("FCEDIT", 1)) == NULL && 352 (editor = bltinlookup("EDITOR", 1)) == NULL) 353 editor = DEFEDITOR; 354 if (editor[0] == '-' && editor[1] == '\0') { 355 sflg = 1; /* no edit */ 356 editor = NULL; 357 } 358 } 359 } 360 361 /* 362 * If executing, parse [old=new] now 363 */ 364 if (lflg == 0 && argc > 0 && 365 ((repl = strchr(argv[0], '=')) != NULL)) { 366 pat = argv[0]; 367 *repl++ = '\0'; 368 argc--, argv++; 369 } 370 371 /* 372 * If -s is specified, accept only one operand 373 */ 374 if (sflg && argc >= 2) 375 error("too many args"); 376 377 /* 378 * determine [first] and [last] 379 */ 380 switch (argc) { 381 case 0: 382 firststr = lflg ? "-16" : "-1"; 383 laststr = "-1"; 384 break; 385 case 1: 386 firststr = argv[0]; 387 laststr = lflg ? "-1" : argv[0]; 388 break; 389 case 2: 390 firststr = argv[0]; 391 laststr = argv[1]; 392 break; 393 default: 394 error("too many args"); 395 /* NOTREACHED */ 396 } 397 /* 398 * Turn into event numbers. 399 */ 400 first = str_to_event(firststr, 0); 401 last = str_to_event(laststr, 1); 402 403 if (rflg) { 404 i = last; 405 last = first; 406 first = i; 407 } 408 /* 409 * XXX - this should not depend on the event numbers 410 * always increasing. Add sequence numbers or offset 411 * to the history element in next (diskbased) release. 412 */ 413 direction = first < last ? H_PREV : H_NEXT; 414 415 /* 416 * If editing, grab a temp file. 417 */ 418 if (editor) { 419 int fd; 420 INTOFF; /* easier */ 421 snprintf(editfile, sizeof(editfile), "%s_shXXXXXX", _PATH_TMP); 422 if ((fd = mkstemp(editfile)) < 0) 423 error("can't create temporary file %s", editfile); 424 if ((efp = fdopen(fd, "w")) == NULL) { 425 close(fd); 426 error("can't allocate stdio buffer for temp"); 427 } 428 } 429 430 /* 431 * Loop through selected history events. If listing or executing, 432 * do it now. Otherwise, put into temp file and call the editor 433 * after. 434 * 435 * The history interface needs rethinking, as the following 436 * convolutions will demonstrate. 437 */ 438 history(hist, &he, H_FIRST); 439 retval = history(hist, &he, H_NEXT_EVENT, first); 440 for (;retval != -1; retval = history(hist, &he, direction)) { 441 if (lflg) { 442 if (!nflg) 443 out1fmt("%5d ", he.num); 444 out1str(he.str); 445 } else { 446 const char *s = pat ? 447 fc_replace(he.str, pat, repl) : he.str; 448 449 if (sflg) { 450 if (displayhist) { 451 out2str(s); 452 } 453 454 evalstring(strcpy(stalloc(strlen(s) + 1), s), 0); 455 if (displayhist && hist) { 456 /* 457 * XXX what about recursive and 458 * relative histnums. 459 */ 460 history(hist, &he, H_ENTER, s); 461 } 462 463 break; 464 } else 465 fputs(s, efp); 466 } 467 /* 468 * At end? (if we were to lose last, we'd sure be 469 * messed up). 470 */ 471 if (he.num == last) 472 break; 473 } 474 if (editor) { 475 char *editcmd; 476 size_t cmdlen; 477 478 fclose(efp); 479 cmdlen = strlen(editor) + strlen(editfile) + 2; 480 editcmd = stalloc(cmdlen); 481 snprintf(editcmd, cmdlen, "%s %s", editor, editfile); 482 evalstring(editcmd, 0); /* XXX - should use no JC command */ 483 stunalloc(editcmd); 484 readcmdfile(editfile); /* XXX - should read back - quick tst */ 485 unlink(editfile); 486 INTON; 487 } 488 489 if (lflg == 0 && active > 0) 490 --active; 491 if (displayhist) 492 displayhist = 0; 493 return 0; 494 } 495 496 STATIC const char * 497 fc_replace(const char *s, char *p, char *r) 498 { 499 char *dest; 500 int plen = strlen(p); 501 502 STARTSTACKSTR(dest); 503 while (*s) { 504 if (*s == *p && strncmp(s, p, plen) == 0) { 505 while (*r) 506 STPUTC(*r++, dest); 507 s += plen; 508 *p = '\0'; /* so no more matches */ 509 } else 510 STPUTC(*s++, dest); 511 } 512 STACKSTRNUL(dest); 513 dest = grabstackstr(dest); 514 515 return (dest); 516 } 517 518 int 519 not_fcnumber(char *s) 520 { 521 if (s == NULL) 522 return 0; 523 if (*s == '-') 524 s++; 525 return (!is_number(s)); 526 } 527 528 int 529 str_to_event(const char *str, int last) 530 { 531 HistEvent he; 532 const char *s = str; 533 int relative = 0; 534 int i, retval; 535 536 retval = history(hist, &he, H_FIRST); 537 switch (*s) { 538 case '-': 539 relative = 1; 540 /*FALLTHROUGH*/ 541 case '+': 542 s++; 543 } 544 if (is_number(s)) { 545 i = number(s); 546 if (relative) { 547 while (retval != -1 && i--) { 548 retval = history(hist, &he, H_NEXT); 549 } 550 if (retval == -1) 551 retval = history(hist, &he, H_LAST); 552 } else { 553 retval = history(hist, &he, H_NEXT_EVENT, i); 554 if (retval == -1) { 555 /* 556 * the notion of first and last is 557 * backwards to that of the history package 558 */ 559 retval = history(hist, &he, 560 last ? H_FIRST : H_LAST); 561 } 562 } 563 if (retval == -1) 564 error("history number %s not found (internal error)", 565 str); 566 } else { 567 /* 568 * pattern 569 */ 570 retval = history(hist, &he, H_PREV_STR, str); 571 if (retval == -1) 572 error("history pattern not found: %s", str); 573 } 574 return (he.num); 575 } 576 #else 577 int 578 histcmd(int argc, char **argv) 579 { 580 error("not compiled with history support"); 581 /* NOTREACHED */ 582 } 583 int 584 inputrc(int argc, char **argv) 585 { 586 error("not compiled with history support"); 587 /* NOTREACHED */ 588 } 589 #endif 590