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