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