Home | History | Annotate | Line # | Download | only in sh
      1 /*	$NetBSD: histedit.c,v 1.74 2026/05/28 10:07:58 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.74 2026/05/28 10:07:58 kre Exp $");
     41 #endif
     42 #endif /* not lint */
     43 
     44 #include <sys/param.h>
     45 #include <sys/stat.h>
     46 #include <dirent.h>
     47 #include <errno.h>
     48 #include <fcntl.h>
     49 #include <paths.h>
     50 #include <stdio.h>
     51 #include <stdlib.h>
     52 #include <unistd.h>
     53 /*
     54  * Editline and history functions (and glue).
     55  */
     56 #include "shell.h"
     57 #include "parser.h"
     58 #include "var.h"
     59 #include "options.h"
     60 #include "builtins.h"
     61 #include "main.h"
     62 #include "output.h"
     63 #include "mystring.h"
     64 #include "myhistedit.h"
     65 #include "error.h"
     66 #include "alias.h"
     67 #include "redir.h"
     68 
     69 #ifndef SMALL		/* almost all the rest of this file */
     70 
     71 #include "eval.h"
     72 #include "memalloc.h"
     73 #include "show.h"
     74 
     75 #define MAXHISTLOOPS	4	/* max recursions through fc */
     76 #define DEFEDITOR	"ed"	/* default editor *should* be $EDITOR */
     77 
     78 History *hist;	/* history cookie */
     79 EditLine *el;	/* editline cookie */
     80 int displayhist;
     81 static FILE *el_in, *el_out;
     82 static int curpos;
     83 
     84 static char *HistFile = NULL;
     85 static const char *HistFileOpen = NULL;
     86 FILE *HistFP = NULL;
     87 static int History_fd;
     88 
     89 #ifdef DEBUG
     90 extern FILE *tracefile;
     91 #endif
     92 
     93 static const char *fc_replace(const char *, char *, char *);
     94 static int not_fcnumber(const char *);
     95 static int str_to_event(const char *, int);
     96 static int comparator(const void *, const void *);
     97 static char **sh_matches(const char *, int, int);
     98 static unsigned char sh_complete(EditLine *, int);
     99 static FILE *Hist_File_Open(const char *);
    100 
    101 /*
    102  * a getenv(3) lookalike function for libedit to
    103  * use so it can access current values of sh variables
    104  * so there is no need to keep doing setenv() of anything
    105  * it might want to lookup.
    106  */
    107 static char *
    108 el_getenv(const char *name)
    109 {
    110 	return bltinlookup(name, 1);
    111 }
    112 
    113 /*
    114  * Set history and editing status.  Called whenever the status may
    115  * have changed (figures out what to do).
    116  */
    117 void
    118 histedit(void)
    119 {
    120 	FILE *el_err;
    121 
    122 #define editing (Eflag || Vflag)
    123 
    124 	CTRACE(DBG_HISTORY, ("histedit: %cE%cV %sinteractive\n",
    125 	    Eflag ? '-' : '+',  Vflag ? '-' : '+', iflag ? "" : "not "));
    126 
    127 	if (iflag == 1) {
    128 		if (!hist) {
    129 			/*
    130 			 * turn history on
    131 			 */
    132 			INTOFF;
    133 			hist = history_init();
    134 			INTON;
    135 
    136 			if (hist != NULL) {
    137 				sethistsize(histsizeval(),
    138 				    histsizeflags(), NULL);
    139 				sethistfile(histfileval(),
    140 				    histfileflags(), NULL);
    141 			} else
    142 				out2str("sh: can't initialize history\n");
    143 		}
    144 		if (editing && !el && isatty(0)) { /* && isatty(2) ??? */
    145 			/*
    146 			 * turn editing on
    147 			 */
    148 			INTOFF;
    149 			if (el_in == NULL)
    150 				el_in = fdopen(0, "r");
    151 			if (el_out == NULL)
    152 				el_out = fdopen(2, "w");
    153 			if (el_in == NULL || el_out == NULL)
    154 				goto bad;
    155 			el_err = el_out;
    156 #if DEBUG
    157 			if (tracefile)
    158 				el_err = tracefile;
    159 #endif
    160 			el = el_init("sh", el_in, el_out, el_err);
    161 			VTRACE(DBG_HISTORY, ("el_init() %sed\n",
    162 			    el != NULL ? "succeed" : "fail"));
    163 			if (el != NULL) {
    164 				if (hist)
    165 					el_set(el, EL_HIST, history, hist);
    166 
    167 				set_prompt_lit(lookupvar("PSlit"), 0, NULL);
    168 
    169 				el_set(el, EL_GETENV, el_getenv);
    170 				el_set(el, EL_SIGNAL, 1);
    171 				el_set(el, EL_SAFEREAD, 1);
    172 				el_set(el, EL_ALIAS_TEXT, alias_text, NULL);
    173 				el_set(el, EL_ADDFN, "rl-complete",
    174 				    "ReadLine compatible completion function",
    175 				    sh_complete);
    176 			} else {
    177  bad:;
    178 				out2str("sh: can't initialize editing\n");
    179 			}
    180 			INTON;
    181 		} else if (!editing && el) {
    182 			INTOFF;
    183 			el_end(el);
    184 			el = NULL;
    185 			VTRACE(DBG_HISTORY, ("line editing disabled\n"));
    186 			INTON;
    187 		}
    188 		if (el) {
    189 			INTOFF;
    190 			if (Vflag)
    191 				el_set(el, EL_EDITOR, "vi");
    192 			else if (Eflag)
    193 				el_set(el, EL_EDITOR, "emacs");
    194 			VTRACE(DBG_HISTORY, ("reading $EDITRC\n"));
    195 			el_source(el, lookupvar("EDITRC"));
    196 			el_set(el, EL_BIND, "^I",
    197 			    tabcomplete ? "rl-complete" : "ed-insert", NULL);
    198 			INTON;
    199 		}
    200 	} else {
    201 		INTOFF;
    202 		if (el) {	/* no editing if not interactive */
    203 			el_end(el);
    204 			el = NULL;
    205 		}
    206 		if (hist) {
    207 			history_end(hist);
    208 			hist = NULL;
    209 		}
    210 		INTON;
    211 		VTRACE(DBG_HISTORY, ("line editing & history disabled\n"));
    212 	}
    213 }
    214 
    215 void
    216 set_prompt_lit(const char *lit_ch, int flags, struct var *vp __unused)
    217 {
    218 	wchar_t wc;
    219 
    220 	if (!(iflag && editing && el))
    221 		return;
    222 
    223 	if (lit_ch == NULL) {
    224 		el_set(el, EL_PROMPT, getprompt);
    225 		return;
    226 	}
    227 
    228 	mbtowc(&wc, NULL, 1);		/* state init */
    229 
    230 	INTOFF;
    231 	if ((flags & VUNSET) || mbtowc(&wc, lit_ch, strlen(lit_ch)) <= 0)
    232 		el_set(el, EL_PROMPT, getprompt);
    233 	else
    234 		el_set(el, EL_PROMPT_ESC, getprompt, (int)wc);
    235 	INTON;
    236 }
    237 
    238 void
    239 set_editrc(const char *fname, int flags, struct var *vp __unused)
    240 {
    241 	INTOFF;
    242 	if (iflag && editing && el && !(flags & VUNSET))
    243 		el_source(el, fname);
    244 	INTON;
    245 }
    246 
    247 void
    248 sethistsize(const char *hs, int flags, struct var *vp __unused)
    249 {
    250 	int histsize;
    251 	HistEvent he;
    252 
    253 	CTRACE(DBG_HISTORY, ("Set HISTSIZE=%s [%x] %s\n",
    254 	    (hs == NULL ? "''" : hs), flags, "!hist" + (hist != NULL)));
    255 
    256 	if (hs != NULL && *hs != '\0' && (flags & VUNSAFE) && !is_number(hs))
    257 		hs = NULL;
    258 
    259 	if (hs == NULL || *hs == '\0' || (flags & VUNSET) ||
    260 	    (histsize = number(hs)) < 0)
    261 		histsize = 100;
    262 
    263 	if (hist != NULL) {
    264 		INTOFF;
    265 		/* H_SETSIZE actually sets n-1 as the limit */
    266 		history(hist, &he, H_SETSIZE, histsize + 1);
    267 		history(hist, &he, H_SETUNIQUE, 1);
    268 		INTON;
    269 	}
    270 }
    271 
    272 void
    273 sethistfile(const char *hs, int flags, struct var *vp __unused)
    274 {
    275 	const char *file;
    276 	HistEvent he;
    277 
    278 	CTRACE(DBG_HISTORY, ("Set HISTFILE=%s [%x] %s\n",
    279 	    (hs == NULL ? "''" : hs), flags, "!hist" + (hist != NULL)));
    280 
    281 	if (hs == NULL || *hs == '\0' || (flags & VUNSET)) {
    282 		if (HistFP != NULL) {
    283 			fclose(HistFP);
    284 			HistFP = NULL;
    285 		}
    286 		if (HistFile != NULL) {
    287 			free(HistFile);
    288 			HistFile = NULL;
    289 			HistFileOpen = NULL;
    290 		}
    291 		return;
    292 	}
    293 
    294 	if (hist != NULL) {
    295 		file = expandvar(hs, flags);
    296 		if (file == NULL || *file == '\0')
    297 			return;
    298 
    299 		INTOFF;
    300 
    301 		history(hist, &he, H_LOAD, file);
    302 
    303 		/*
    304 		 * This is needed so sethistappend() can work
    305 		 * on the current (new) filename, not the previous one.
    306 		 */
    307 		if (HistFile != NULL)
    308 			free(HistFile);
    309 
    310 		HistFile = strdup(hs);
    311 		/*
    312 		 * We need to ensure that HistFile & HistFileOpen
    313 		 * are not equal .. we know HistFile has just altered.
    314 		 * If they happen to be equal (both NULL perhaps, or
    315 		 * the strdup() just above happned to return the same
    316 		 * buffer as was freed the line before) then simply
    317 		 * set HistFileOpen to something which cannot be the
    318 		 * same as anything allocated, or NULL.   Its only
    319 		 * use is to compare against HistFile.
    320 		 */
    321 		if (HistFile == HistFileOpen)
    322 			HistFileOpen = "";
    323 
    324 		sethistappend((histappflags() & VUNSET) ? NULL : histappval(),
    325 			~VUNSET & 0xFFFF, NULL);
    326 
    327 		INTON;
    328 	}
    329 }
    330 
    331 void
    332 sethistappend(const char *s, int flags, struct var *vp __unused)
    333 {
    334 	CTRACE(DBG_HISTORY, ("Set HISTAPPEND=%s [%x] %s ",
    335 	    (s == NULL ? "''" : s), flags, "!hist" + (hist != NULL)));
    336 
    337 	INTOFF;
    338 	if (flags & VUNSET || !boolstr(s)) {
    339 		CTRACE(DBG_HISTORY, ("off"));
    340 
    341 		if (HistFP != NULL) {
    342 			CTRACE(DBG_HISTORY, (" closing"));
    343 
    344 			fclose(HistFP);
    345 			HistFP = NULL;
    346 			HistFileOpen = NULL;
    347 		}
    348 	} else {
    349 		CTRACE(DBG_HISTORY, ("on"));
    350 
    351 		if (HistFileOpen != HistFile || HistFP == NULL) {
    352 			if (HistFP != NULL) {
    353 				CTRACE(DBG_HISTORY, (" closing prev"));
    354 				fclose(HistFP);
    355 				HistFP = NULL;
    356 			}
    357 			if (hist != NULL &&
    358 			    HistFile != NULL &&
    359 			    HistFP == NULL) {
    360 				CTRACE(DBG_HISTORY, ("\n"));
    361 
    362 				save_sh_history();
    363 
    364 				CTRACE(DBG_HISTORY, ("opening: "));
    365 
    366 				HistFP = Hist_File_Open(HistFile);
    367 				if (HistFP != NULL)
    368 					HistFileOpen = HistFile;
    369 				else {
    370 					CTRACE(DBG_HISTORY, ("open failed"));
    371 				}
    372 			}
    373 		}
    374 	}
    375 	INTON;
    376 
    377 	CTRACE(DBG_HISTORY, ("\n"));
    378 }
    379 
    380 static void
    381 History_FD_Renumbered(int from, int to)
    382 {
    383 	if (History_fd == from)
    384 		History_fd = to;
    385 
    386 	VTRACE(DBG_HISTORY, ("History_FD_Renumbered(%d,%d)-> %d\n",
    387 	    from, to, History_fd));
    388 }
    389 
    390 /*
    391  * The callback functions for the FILE* returned by funopen2()
    392  */
    393 static ssize_t
    394 Hist_Write(void *cookie, const void *buf, size_t len)
    395 {
    396 	if (cookie != (void *)&History_fd) {
    397 		errno = EINVAL;
    398 		return -1;
    399 	}
    400 
    401 	return write(History_fd, buf, len);
    402 }
    403 
    404 static int
    405 Hist_Close(void *cookie)
    406 {
    407 	if (cookie == (void *)&History_fd) {
    408 		sh_close(History_fd);
    409 		History_fd = -1;
    410 		return 0;
    411 	}
    412 
    413 	VTRACE(DBG_HISTORY, ("HistClose(%p) != %p\n", cookie, &History_fd));
    414 
    415 	errno = EINVAL;
    416 	return -1;
    417 }
    418 
    419 static off_t
    420 Hist_Seek(void *cookie, off_t pos, int whence)
    421 {
    422 	if (cookie != (void *)&History_fd) {
    423 		errno = EINVAL;
    424 		return -1;
    425 	}
    426 
    427 	return lseek(History_fd, pos, whence);
    428 }
    429 
    430 /*
    431  * a variant of open() for history files.
    432  */
    433 static int
    434 open_history_file(const char *name, int mode)
    435 {
    436 	int fd;
    437 	struct stat statb;
    438 
    439 	fd = open(name, mode, S_IWUSR|S_IRUSR);
    440 
    441 	VTRACE(DBG_HISTORY, ("open_history_file(\"%s\", %#x) -> %d\n",
    442 	    name, mode, fd));
    443 
    444 	if (fd == -1)
    445 		return -1;
    446 
    447 	if (fstat(fd, &statb) == -1) {
    448 		VTRACE(DBG_HISTORY, ("history file fstat(%d) failed [%d]\n",
    449 		    fd, errno));
    450 		close(fd);
    451 		return -1;
    452 	}
    453 
    454 	if (statb.st_uid != getuid()) {
    455 		VTRACE(DBG_HISTORY,
    456 		    ("history file wrong user (uid=%d file=%d)\n",
    457 			getuid(), statb.st_uid));
    458 		close(fd);
    459 		return -1;
    460 	}
    461 
    462 	return fd;
    463 }
    464 
    465 static FILE *
    466 Hist_File_Open(const char *name)
    467 {
    468 	FILE *fd;
    469 	int n;
    470 
    471 	INTOFF;
    472 
    473 	n = open_history_file(name, O_WRONLY|O_CREAT|O_APPEND|O_CLOEXEC);
    474 
    475 	VTRACE(DBG_HISTORY, ("History_File_Open(\"%s\") -> %d", name, n));
    476 	if (n == -1) {
    477 		VTRACE(DBG_HISTORY, (" [%d]\n", errno));
    478 		INTON;
    479 		return NULL;
    480 	}
    481 
    482 	n = to_upper_fd(n);
    483 	(void) lseek(n, 0, SEEK_END);
    484 	VTRACE(DBG_HISTORY, (" -> %d", n));
    485 
    486 	History_fd = n;
    487 	register_sh_fd(n, History_FD_Renumbered);
    488 
    489 	if ((fd =
    490 	    funopen2(&History_fd, NULL, Hist_Write, Hist_Seek, NULL,
    491 		     Hist_Close)) == NULL) {
    492 
    493 		VTRACE(DBG_HISTORY, ("; funopen2 failed[%d]\n", errno));
    494 
    495 		sh_close(n);
    496 		History_fd = -1;
    497 		INTON;
    498 		return NULL;
    499 	}
    500 	setlinebuf(fd);
    501 
    502 	VTRACE(DBG_HISTORY, (" fd:%p\n", fd));
    503 
    504 	INTON;
    505 
    506 	return fd;
    507 }
    508 
    509 void
    510 save_sh_history(void)
    511 {
    512 	char *var;
    513 	const char *file;
    514 	int fd;
    515 	FILE *fp;
    516 	HistEvent he;
    517 
    518 	if (HistFP != NULL) {
    519 		/* don't close, just make sure nothing in buffer */
    520 		(void) fflush(HistFP);
    521 		return;
    522 	}
    523 
    524 	if (hist == NULL)
    525 		return;
    526 
    527 	var = histfileval();
    528 	if ((histfileflags() & VUNSET) || *var == '\0')
    529 		return;
    530 
    531 	file = expandvar(var, histfileflags());
    532 
    533 	VTRACE(DBG_HISTORY,
    534 	    ("save_sh_history('%s')\n", file == NULL ? "" : file));
    535 
    536 	if (file == NULL || *file == '\0')
    537 		return;
    538 
    539 	INTOFF;
    540 	fd = open_history_file(file, O_WRONLY|O_CREAT|O_TRUNC);
    541 	if (fd != -1) {
    542 		fp = fdopen(fd, "w");
    543 		if (fp != NULL) {
    544 			(void) history(hist, &he, H_SAVE_FP, fp);
    545 			fclose(fp);
    546 		} else
    547 			close(fd);
    548 	}
    549 	INTON;
    550 }
    551 
    552 void
    553 setterm(const char *term, int flags __unused, struct var *vp __unused)
    554 {
    555 	INTOFF;
    556 	if (el != NULL && term != NULL && *term != '\0')
    557 		if (el_set(el, EL_TERMINAL, term) != 0) {
    558 			outfmt(out2, "sh: Can't set terminal type %s\n", term);
    559 			outfmt(out2, "sh: Using dumb terminal settings.\n");
    560 		}
    561 	INTON;
    562 }
    563 
    564 /*
    565  * The built-in sh commands supported by this file
    566  */
    567 int
    568 inputrc(int argc, char **argv)
    569 {
    570 	CTRACE(DBG_HISTORY, ("inputrc (%d arg%s)", argc-1, argc==2?"":"s"));
    571 	if (argc != 2) {
    572 		CTRACE(DBG_HISTORY, (" -- bad\n"));
    573 		out2str("usage: inputrc file\n");
    574 		return 1;
    575 	}
    576 	CTRACE(DBG_HISTORY, (" file: \"%s\"\n", argv[1]));
    577 	if (el != NULL) {
    578 		INTOFF;
    579 		if (el_source(el, argv[1])) {
    580 			INTON;
    581 			out2str("inputrc: failed\n");
    582 			return 1;
    583 		}
    584 		INTON;
    585 		return 0;
    586 	} else {
    587 		out2str("sh: inputrc ignored, not editing\n");
    588 		return 1;
    589 	}
    590 }
    591 
    592 /*
    593  *  This command is provided since POSIX decided to standardize
    594  *  the Korn shell fc command.  Oh well...
    595  */
    596 int
    597 histcmd(volatile int argc, char ** volatile argv)
    598 {
    599 	int ch;
    600 	const char * volatile editor = NULL;
    601 	HistEvent he;
    602 	volatile int lflg = 0, nflg = 0, rflg = 0, sflg = 0, zflg = 0;
    603 	int i, retval;
    604 	const char *firststr, *laststr;
    605 	int first, last, direction;
    606 
    607 	char * volatile pat = NULL;	/* ksh "fc old=new" crap */
    608 	char * volatile repl;
    609 
    610 	static int active = 0;
    611 	struct jmploc jmploc;
    612 	struct jmploc *volatile savehandler;
    613 	char editfile[MAXPATHLEN + 1];
    614 	FILE * volatile efp;
    615 
    616 #ifdef __GNUC__
    617 	repl = NULL;	/* XXX gcc4 */
    618 	efp = NULL;	/* XXX gcc4 */
    619 #endif
    620 
    621 	if (hist == NULL)
    622 		error("history not active");
    623 
    624 	CTRACE(DBG_HISTORY, ("histcmd (fc) %d arg%s\n", argc, argc==1?"":"s"));
    625 	if (argc == 1)
    626 		error("missing history argument");
    627 
    628 	optreset = 1; optind = 1; /* initialize getopt */
    629 	while (not_fcnumber(argv[optind]) &&
    630 	      (ch = getopt(argc, argv, ":e:lnrsz")) != -1)
    631 		switch ((char)ch) {
    632 		case 'e':
    633 			editor = optarg;
    634 			VTRACE(DBG_HISTORY, ("histcmd -e %s\n", editor));
    635 			break;
    636 		case 'l':
    637 			lflg = 1;
    638 			VTRACE(DBG_HISTORY, ("histcmd -l\n"));
    639 			break;
    640 		case 'n':
    641 			nflg = 1;
    642 			VTRACE(DBG_HISTORY, ("histcmd -n\n"));
    643 			break;
    644 		case 'r':
    645 			rflg = 1;
    646 			VTRACE(DBG_HISTORY, ("histcmd -r\n"));
    647 			break;
    648 		case 's':
    649 			sflg = 1;
    650 			VTRACE(DBG_HISTORY, ("histcmd -s\n"));
    651 			break;
    652 		case 'z':
    653 			zflg = 1;
    654 			VTRACE(DBG_HISTORY, ("histcmd -z\n"));
    655 			break;
    656 		case ':':
    657 			error("option -%c expects argument", optopt);
    658 			/* NOTREACHED */
    659 		case '?':
    660 		default:
    661 			error("unknown option: -%c", optopt);
    662 			/* NOTREACHED */
    663 		}
    664 	argc -= optind, argv += optind;
    665 
    666 	if (zflg) {
    667 		if (argc != 0 || (lflg|nflg|rflg|sflg) != 0)
    668 			error("Usage: fc -z");
    669 
    670 		history(hist, &he, H_CLEAR);
    671 		return 0;
    672 	}
    673 
    674 
    675 	/*
    676 	 * If executing...
    677 	 */
    678 	if (lflg == 0 || editor || sflg) {
    679 		lflg = 0;	/* ignore */
    680 		editfile[0] = '\0';
    681 		/*
    682 		 * Catch interrupts to reset active counter and
    683 		 * cleanup temp files.
    684 		 */
    685 		savehandler = handler;
    686 		if (setjmp(jmploc.loc)) {
    687 			active = 0;
    688 			if (*editfile) {
    689 				VTRACE(DBG_HISTORY,
    690 				    ("histcmd err jump unlink temp \"%s\"\n",
    691 				    editfile));
    692 				unlink(editfile);
    693 			}
    694 			handler = savehandler;
    695 			longjmp(handler->loc, 1);
    696 		}
    697 		handler = &jmploc;
    698 		VTRACE(DBG_HISTORY, ("histcmd is active %d(++)\n", active));
    699 		if (++active > MAXHISTLOOPS) {
    700 			active = 0;
    701 			displayhist = 0;
    702 			error("called recursively too many times");
    703 		}
    704 		/*
    705 		 * Set editor.
    706 		 */
    707 		if (sflg == 0) {
    708 			if (editor == NULL &&
    709 			    (editor = bltinlookup("FCEDIT", 1)) == NULL &&
    710 			    (editor = bltinlookup("EDITOR", 1)) == NULL)
    711 				editor = DEFEDITOR;
    712 			if (editor[0] == '-' && editor[1] == '\0') {
    713 				sflg = 1;	/* no edit */
    714 				editor = NULL;
    715 			}
    716 			VTRACE(DBG_HISTORY, ("histcmd using %s as editor\n",
    717 			    editor == NULL ? "-nothing-" : editor));
    718 		}
    719 	}
    720 
    721 	/*
    722 	 * If executing, parse [old=new] now
    723 	 */
    724 	if (lflg == 0 && argc > 0 &&
    725 	     ((repl = strchr(argv[0], '=')) != NULL)) {
    726 		pat = argv[0];
    727 		*repl++ = '\0';
    728 		argc--, argv++;
    729 		VTRACE(DBG_HISTORY, ("histcmd replace old=\"%s\" new=\"%s\""
    730 		    " (%d args)\n", pat, repl, argc));
    731 	}
    732 
    733 	/*
    734 	 * If -s is specified, accept only one operand
    735 	 */
    736 	if (sflg && argc >= 2)
    737 		error("too many args");
    738 
    739 	/*
    740 	 * determine [first] and [last]
    741 	 */
    742 	switch (argc) {
    743 	case 0:
    744 		if (lflg) {
    745 			firststr = "-16";
    746 			laststr = "-1";
    747 		} else
    748 			firststr = laststr = "-1";	/* the exact same str */
    749 		break;
    750 	case 1:
    751 		firststr = argv[0];
    752 		laststr = lflg ? "-1" : argv[0];
    753 		break;
    754 	case 2:
    755 		firststr = argv[0];
    756 		laststr = argv[1];
    757 		break;
    758 	default:
    759 		error("too many args");
    760 		/* NOTREACHED */
    761 	}
    762 	/*
    763 	 * Turn into event numbers.
    764 	 */
    765 	first = str_to_event(firststr, 0);
    766 	last = str_to_event(laststr, 1);
    767 
    768 	if (first == -1 || last == -1) {
    769 		if (lflg)   /* no history exists, that's OK */
    770 			return 0;
    771 		if (first == -1 && last == -1) {
    772 			if (firststr != laststr)
    773 				error("history events %s to %s do not exist",
    774 				    firststr, laststr);
    775 			else
    776 				error("history event %s does not exist",
    777 				    firststr);
    778 		} else {
    779 			error("history event %s does not exist",
    780 			    first == -1 ? firststr : laststr);
    781 		}
    782 	}
    783 
    784 	if (rflg) {
    785 		i = last;
    786 		last = first;
    787 		first = i;
    788 	}
    789 	VTRACE(DBG_HISTORY, ("histcmd%s first=\"%s\" (#%d) last=\"%s\" (#%d)\n",
    790 	    rflg ? " reversed" : "", rflg ? laststr : firststr, first,
    791 	    rflg ? firststr : laststr, last));
    792 
    793 	/*
    794 	 * XXX - this should not depend on the event numbers
    795 	 * always increasing.  Add sequence numbers or offset
    796 	 * to the history element in next (diskbased) release.
    797 	 */
    798 	direction = first < last ? H_PREV : H_NEXT;
    799 
    800 	/*
    801 	 * If editing, grab a temp file.
    802 	 */
    803 	if (editor) {
    804 		int fd;
    805 
    806 		INTOFF;		/* easier */
    807 		snprintf(editfile, sizeof(editfile),
    808 		    "%s_shXXXXXX", _PATH_TMP);
    809 		if ((fd = mkstemp(editfile)) < 0)
    810 			error("can't create temporary file %s", editfile);
    811 		if ((efp = fdopen(fd, "w")) == NULL) {
    812 			close(fd);
    813 			error("can't allocate stdio buffer for temp");
    814 		}
    815 		VTRACE(DBG_HISTORY, ("histcmd created \"%s\" for edit buffer"
    816 		    " fd=%d\n", editfile, fd));
    817 	}
    818 
    819 	/*
    820 	 * Loop through selected history events.  If listing or executing,
    821 	 * do it now.  Otherwise, put into temp file and call the editor
    822 	 * after.
    823 	 *
    824 	 * The history interface needs rethinking, as the following
    825 	 * convolutions will demonstrate.
    826 	 */
    827 	history(hist, &he, H_FIRST);
    828 	retval = history(hist, &he, H_NEXT_EVENT, first);
    829 	for ( ; retval != -1; retval = history(hist, &he, direction)) {
    830 		if (lflg) {
    831 			if (!nflg)
    832 				out1fmt("%5d ", he.num);
    833 			out1str(he.str);
    834 		} else {
    835 			const char *s = pat ?
    836 			   fc_replace(he.str, pat, repl) : he.str;
    837 
    838 			if (sflg) {
    839 				VTRACE(DBG_HISTORY, ("histcmd -s \"%s\"\n", s));
    840 				if (displayhist) {
    841 					out2str(s);
    842 				}
    843 
    844 				evalstring(s, 0);
    845 
    846 				if (displayhist && hist) {
    847 					/*
    848 					 *  XXX what about recursive and
    849 					 *  relative histnums.
    850 					 */
    851 					history(hist, &he, H_ENTER, s);
    852 				}
    853 
    854 				break;
    855 			} else
    856 				fputs(s, efp);
    857 		}
    858 		/*
    859 		 * At end?  (if we were to lose last, we'd sure be
    860 		 * messed up).
    861 		 */
    862 		if (he.num == last)
    863 			break;
    864 	}
    865 	if (editor) {
    866 		char *editcmd;
    867 		size_t cmdlen;
    868 
    869 		fclose(efp);
    870 		cmdlen = strlen(editor) + strlen(editfile) + 2;
    871 		editcmd = stalloc(cmdlen);
    872 		snprintf(editcmd, cmdlen, "%s %s", editor, editfile);
    873 		VTRACE(DBG_HISTORY, ("histcmd editing: \"%s\"\n", editcmd));
    874 		evalstring(editcmd, 0);	/* XXX - should use no JC command */
    875 		stunalloc(editcmd);
    876 		VTRACE(DBG_HISTORY, ("histcmd read cmds from %s\n", editfile));
    877 		readcmdfile(editfile);	/* XXX - should read back - quick tst */
    878 		VTRACE(DBG_HISTORY, ("histcmd unlink %s\n", editfile));
    879 		unlink(editfile);
    880 		editfile[0] = '\0';
    881 		INTON;
    882 	}
    883 
    884 	if (lflg == 0 && active > 0)
    885 		--active;
    886 	if (displayhist)
    887 		displayhist = 0;
    888 	return 0;
    889 }
    890 
    891 /*
    892  * and finally worker functions for those built-ins
    893  */
    894 
    895 static const char *
    896 fc_replace(const char *s, char *p, char *r)
    897 {
    898 	char *dest;
    899 	int plen = strlen(p);
    900 
    901 	VTRACE(DBG_HISTORY, ("histcmd s/%s/%s/ in \"%s\" -> ", p, r, s));
    902 	STARTSTACKSTR(dest);
    903 	while (*s) {
    904 		if (*s == *p && strncmp(s, p, plen) == 0) {
    905 			while (*r)
    906 				STPUTC(*r++, dest);
    907 			s += plen;
    908 			*p = '\0';	/* so no more matches */
    909 		} else
    910 			STPUTC(*s++, dest);
    911 	}
    912 	STPUTC('\0', dest);
    913 	dest = grabstackstr(dest);
    914 	VTRACE(DBG_HISTORY, ("\"%s\"\n", dest));
    915 
    916 	return dest;
    917 }
    918 
    919 
    920 /*
    921  * Comparator function for qsort(). The use of curpos here is to skip
    922  * characters that we already know to compare equal (common prefix).
    923  */
    924 static int
    925 comparator(const void *a, const void *b)
    926 {
    927 	return strcmp(*(char *const *)a + curpos,
    928 		*(char *const *)b + curpos);
    929 }
    930 
    931 /*
    932  * This function is passed to libedit's fn_complete(). The library will
    933  * use it instead of its standard function to find matches, which
    934  * searches for files in current directory. If we're at the start of the
    935  * line, we want to look for available commands from all paths in $PATH.
    936  */
    937 static char **
    938 sh_matches(const char *text, int start, int end)
    939 {
    940 	char *free_path = NULL, *dirname, *path;
    941 	char **matches = NULL;
    942 	size_t i = 0, size = 16;
    943 
    944 	if (start > 0)
    945 		return NULL;
    946 	curpos = end - start;
    947 	if ((free_path = path = strdup(pathval())) == NULL)
    948 		goto out;
    949 	if ((matches = malloc(size * sizeof(matches[0]))) == NULL)
    950 		goto out;
    951 	while ((dirname = strsep(&path, ":")) != NULL) {
    952 		struct dirent *entry;
    953 		DIR *dir;
    954 		int dfd;
    955 
    956 		if ((dir = opendir(dirname)) == NULL)
    957 			continue;
    958 		if ((dfd = dirfd(dir)) == -1)
    959 			continue;
    960 		while ((entry = readdir(dir)) != NULL) {
    961 			struct stat statb;
    962 
    963 			if (strncmp(entry->d_name, text, curpos) != 0)
    964 				continue;
    965 			if (entry->d_type == DT_UNKNOWN ||
    966 			    entry->d_type == DT_LNK) {
    967 				if (fstatat(dfd, entry->d_name, &statb, 0)
    968 				    == -1)
    969 					continue;
    970 				if (!S_ISREG(statb.st_mode))
    971 					continue;
    972 			} else if (entry->d_type != DT_REG)
    973 				continue;
    974 			if (++i >= size - 1) {
    975 				size *= 2;
    976 				if (reallocarr(&matches, size,
    977 				    sizeof(*matches)))
    978 				{
    979 					closedir(dir);
    980 					goto out;
    981 				}
    982 			}
    983 			matches[i] = strdup(entry->d_name);
    984 		}
    985 		closedir(dir);
    986 	}
    987  out:;
    988 	free(free_path);
    989 	if (i == 0) {
    990 		free(matches);
    991 		return NULL;
    992 	}
    993 	if (i == 1) {
    994 		matches[0] = strdup(matches[1]);
    995 		matches[i + 1] = NULL;
    996 	} else {
    997 		size_t j, k;
    998 
    999 		qsort(matches + 1, i, sizeof(matches[0]), comparator);
   1000 		for (j = 1, k = 2; k <= i; k++)
   1001 			if (strcmp(matches[j] + curpos, matches[k] + curpos)
   1002 			    == 0)
   1003 				free(matches[k]);
   1004 			else
   1005 				matches[++j] = matches[k];
   1006 		matches[0] = strdup(text);
   1007 		matches[j + 1] = NULL;
   1008 	}
   1009 	return matches;
   1010 }
   1011 
   1012 /*
   1013  * This is passed to el_set(el, EL_ADDFN, ...) so that it's possible to
   1014  * bind a key (tab by default) to execute the function.
   1015  */
   1016 unsigned char
   1017 sh_complete(EditLine *sel, int ch __unused)
   1018 {
   1019 	return (unsigned char)fn_complete2(sel, NULL, sh_matches,
   1020 		L" \t\n\"\\'`@$><=;|&{(", NULL, NULL, (size_t)100,
   1021 		NULL, &((int) {0}), NULL, NULL, FN_QUOTE_MATCH);
   1022 }
   1023 
   1024 static int
   1025 not_fcnumber(const char *s)
   1026 {
   1027 	if (s == NULL)
   1028 		return 0;
   1029 	if (*s == '-')
   1030 		s++;
   1031 	return !is_number(s);
   1032 }
   1033 
   1034 static int
   1035 str_to_event(const char *str, int last)
   1036 {
   1037 	HistEvent he;
   1038 	const char *s = str;
   1039 	int relative = 0;
   1040 	int i, retval;
   1041 
   1042 	retval = history(hist, &he, H_FIRST);
   1043 	switch (*s) {
   1044 	case '-':
   1045 		relative = 1;
   1046 		/*FALLTHROUGH*/
   1047 	case '+':
   1048 		s++;
   1049 	}
   1050 	if (is_number(s)) {
   1051 		i = number(s);
   1052 		if (relative) {
   1053 			while (retval != -1 && i--) {
   1054 				retval = history(hist, &he, H_NEXT);
   1055 			}
   1056 			if (retval == -1)
   1057 				retval = history(hist, &he, H_LAST);
   1058 		} else {
   1059 			retval = history(hist, &he, H_NEXT_EVENT, i);
   1060 			if (retval == -1) {
   1061 				/*
   1062 				 * the notion of first and last is
   1063 				 * backwards to that of the history package
   1064 				 */
   1065 				retval = history(hist, &he,
   1066 						last ? H_FIRST : H_LAST);
   1067 			}
   1068 		}
   1069 		if (retval == -1)
   1070 			return -1;
   1071 	} else {
   1072 		/*
   1073 		 * pattern
   1074 		 */
   1075 		retval = history(hist, &he, H_PREV_STR, str);
   1076 		if (retval == -1)
   1077 			error("history pattern not found: %s", str);
   1078 	}
   1079 	return he.num;
   1080 }
   1081 
   1082 #else	/* defined(SMALL) */
   1083 
   1084 int
   1085 histcmd(int argc, char **argv)
   1086 {
   1087 	error("not compiled with history support");
   1088 	/* NOTREACHED */
   1089 }
   1090 
   1091 int
   1092 inputrc(int argc, char **argv)
   1093 {
   1094 	error("not compiled with history support");
   1095 	/* NOTREACHED */
   1096 }
   1097 
   1098 #endif	/* SMALL */
   1099