1 1.20 rillig /* $NetBSD: history.c,v 1.20 2024/09/08 17:28:36 rillig Exp $ */ 2 1.2 tls 3 1.1 jtc /* 4 1.1 jtc * command history 5 1.1 jtc * 6 1.1 jtc * only implements in-memory history. 7 1.1 jtc */ 8 1.1 jtc 9 1.1 jtc /* 10 1.1 jtc * This file contains 11 1.1 jtc * a) the original in-memory history mechanism 12 1.1 jtc * b) a simple file saving history mechanism done by sjg@zen 13 1.1 jtc * define EASY_HISTORY to get this 14 1.1 jtc * c) a more complicated mechanism done by pc (at) hillside.co.uk 15 1.1 jtc * that more closely follows the real ksh way of doing 16 1.1 jtc * things. You need to have the mmap system call for this 17 1.1 jtc * to work on your system 18 1.1 jtc */ 19 1.5 agc #include <sys/cdefs.h> 20 1.5 agc 21 1.5 agc #ifndef lint 22 1.20 rillig __RCSID("$NetBSD: history.c,v 1.20 2024/09/08 17:28:36 rillig Exp $"); 23 1.5 agc #endif 24 1.5 agc 25 1.16 kamil #include <sys/stat.h> 26 1.1 jtc 27 1.1 jtc #include "sh.h" 28 1.1 jtc 29 1.1 jtc #ifdef HISTORY 30 1.1 jtc # ifdef EASY_HISTORY 31 1.1 jtc 32 1.1 jtc # ifndef HISTFILE 33 1.4 hubertf # define HISTFILE ".pdksh_history" 34 1.1 jtc # endif 35 1.1 jtc 36 1.1 jtc # else 37 1.1 jtc /* Defines and includes for the complicated case */ 38 1.1 jtc 39 1.1 jtc # include <sys/file.h> 40 1.1 jtc # include <sys/mman.h> 41 1.1 jtc 42 1.1 jtc /* 43 1.1 jtc * variables for handling the data file 44 1.1 jtc */ 45 1.1 jtc static int histfd; 46 1.1 jtc static int hsize; 47 1.1 jtc 48 1.1 jtc static int hist_count_lines ARGS((unsigned char *, int)); 49 1.1 jtc static int hist_shrink ARGS((unsigned char *, int)); 50 1.1 jtc static unsigned char *hist_skip_back ARGS((unsigned char *,int *,int)); 51 1.1 jtc static void histload ARGS((Source *, unsigned char *, int)); 52 1.1 jtc static void histinsert ARGS((Source *, int, unsigned char *)); 53 1.1 jtc static void writehistfile ARGS((int, char *)); 54 1.1 jtc static int sprinkle ARGS((int)); 55 1.1 jtc 56 1.1 jtc # ifdef MAP_FILE 57 1.1 jtc # define MAP_FLAGS (MAP_FILE|MAP_PRIVATE) 58 1.1 jtc # else 59 1.1 jtc # define MAP_FLAGS MAP_PRIVATE 60 1.1 jtc # endif 61 1.1 jtc 62 1.1 jtc # endif /* of EASY_HISTORY */ 63 1.1 jtc 64 1.9 christos static int hist_execute ARGS((char *)); 65 1.9 christos static int hist_replace ARGS((char **, const char *, const char *, int)); 66 1.9 christos static char **hist_get ARGS((const char *, int, int)); 67 1.9 christos static char **hist_get_newest ARGS((int)); 68 1.3 christos static char **hist_get_oldest ARGS((void)); 69 1.1 jtc static void histbackup ARGS((void)); 70 1.1 jtc 71 1.7 mycroft static char **current; /* current position in history[] */ 72 1.1 jtc static int curpos; /* current index in history[] */ 73 1.1 jtc static char *hname; /* current name of history file */ 74 1.1 jtc static int hstarted; /* set after hist_init() called */ 75 1.1 jtc static Source *hist_source; 76 1.1 jtc 77 1.1 jtc 78 1.1 jtc int 79 1.1 jtc c_fc(wp) 80 1.1 jtc char **wp; 81 1.1 jtc { 82 1.1 jtc struct shf *shf; 83 1.1 jtc struct temp UNINITIALIZED(*tf); 84 1.1 jtc char *p, *editor = (char *) 0; 85 1.1 jtc int gflag = 0, lflag = 0, nflag = 0, sflag = 0, rflag = 0; 86 1.1 jtc int optc; 87 1.1 jtc char *first = (char *) 0, *last = (char *) 0; 88 1.1 jtc char **hfirst, **hlast, **hp; 89 1.1 jtc 90 1.8 christos if (hist_source == NULL) { 91 1.8 christos bi_errorf("not interactive"); 92 1.8 christos return 1; 93 1.8 christos } 94 1.8 christos 95 1.1 jtc while ((optc = ksh_getopt(wp, &builtin_opt, "e:glnrs0,1,2,3,4,5,6,7,8,9,")) != EOF) 96 1.1 jtc switch (optc) { 97 1.1 jtc case 'e': 98 1.1 jtc p = builtin_opt.optarg; 99 1.1 jtc if (strcmp(p, "-") == 0) 100 1.1 jtc sflag++; 101 1.1 jtc else { 102 1.7 mycroft size_t len = strlen(p) + 4; 103 1.7 mycroft editor = str_nsave(p, len, ATEMP); 104 1.7 mycroft strlcat(editor, " $_", len); 105 1.1 jtc } 106 1.1 jtc break; 107 1.1 jtc case 'g': /* non-at&t ksh */ 108 1.1 jtc gflag++; 109 1.1 jtc break; 110 1.1 jtc case 'l': 111 1.1 jtc lflag++; 112 1.1 jtc break; 113 1.1 jtc case 'n': 114 1.1 jtc nflag++; 115 1.1 jtc break; 116 1.1 jtc case 'r': 117 1.1 jtc rflag++; 118 1.1 jtc break; 119 1.1 jtc case 's': /* posix version of -e - */ 120 1.1 jtc sflag++; 121 1.1 jtc break; 122 1.1 jtc /* kludge city - accept -num as -- -num (kind of) */ 123 1.1 jtc case '0': case '1': case '2': case '3': case '4': 124 1.1 jtc case '5': case '6': case '7': case '8': case '9': 125 1.1 jtc p = shf_smprintf("-%c%s", 126 1.1 jtc optc, builtin_opt.optarg); 127 1.1 jtc if (!first) 128 1.1 jtc first = p; 129 1.1 jtc else if (!last) 130 1.1 jtc last = p; 131 1.1 jtc else { 132 1.1 jtc bi_errorf("too many arguments"); 133 1.1 jtc return 1; 134 1.1 jtc } 135 1.1 jtc break; 136 1.1 jtc case '?': 137 1.1 jtc return 1; 138 1.1 jtc } 139 1.1 jtc wp += builtin_opt.optind; 140 1.1 jtc 141 1.1 jtc /* Substitute and execute command */ 142 1.1 jtc if (sflag) { 143 1.1 jtc char *pat = (char *) 0, *rep = (char *) 0; 144 1.1 jtc 145 1.1 jtc if (editor || lflag || nflag || rflag) { 146 1.1 jtc bi_errorf("can't use -e, -l, -n, -r with -s (-e -)"); 147 1.1 jtc return 1; 148 1.1 jtc } 149 1.1 jtc 150 1.1 jtc /* Check for pattern replacement argument */ 151 1.1 jtc if (*wp && **wp && (p = strchr(*wp + 1, '='))) { 152 1.1 jtc pat = str_save(*wp, ATEMP); 153 1.1 jtc p = pat + (p - *wp); 154 1.1 jtc *p++ = '\0'; 155 1.1 jtc rep = p; 156 1.1 jtc wp++; 157 1.1 jtc } 158 1.1 jtc /* Check for search prefix */ 159 1.1 jtc if (!first && (first = *wp)) 160 1.1 jtc wp++; 161 1.1 jtc if (last || *wp) { 162 1.1 jtc bi_errorf("too many arguments"); 163 1.1 jtc return 1; 164 1.1 jtc } 165 1.1 jtc 166 1.17 kamil hp = first ? hist_get(first, false, false) 167 1.17 kamil : hist_get_newest(false); 168 1.1 jtc if (!hp) 169 1.1 jtc return 1; 170 1.1 jtc return hist_replace(hp, pat, rep, gflag); 171 1.1 jtc } 172 1.1 jtc 173 1.1 jtc if (editor && (lflag || nflag)) { 174 1.1 jtc bi_errorf("can't use -l, -n with -e"); 175 1.1 jtc return 1; 176 1.1 jtc } 177 1.1 jtc 178 1.1 jtc if (!first && (first = *wp)) 179 1.1 jtc wp++; 180 1.1 jtc if (!last && (last = *wp)) 181 1.1 jtc wp++; 182 1.1 jtc if (*wp) { 183 1.1 jtc bi_errorf("too many arguments"); 184 1.1 jtc return 1; 185 1.1 jtc } 186 1.1 jtc if (!first) { 187 1.17 kamil hfirst = lflag ? hist_get("-16", true, true) 188 1.17 kamil : hist_get_newest(false); 189 1.1 jtc if (!hfirst) 190 1.1 jtc return 1; 191 1.1 jtc /* can't fail if hfirst didn't fail */ 192 1.17 kamil hlast = hist_get_newest(false); 193 1.1 jtc } else { 194 1.1 jtc /* POSIX says not an error if first/last out of bounds 195 1.1 jtc * when range is specified; at&t ksh and pdksh allow out of 196 1.1 jtc * bounds for -l as well. 197 1.1 jtc */ 198 1.17 kamil hfirst = hist_get(first, (lflag || last) ? true : false, 199 1.17 kamil lflag ? true : false); 200 1.1 jtc if (!hfirst) 201 1.1 jtc return 1; 202 1.17 kamil hlast = last ? hist_get(last, true, lflag ? true : false) 203 1.17 kamil : (lflag ? hist_get_newest(false) : hfirst); 204 1.1 jtc if (!hlast) 205 1.1 jtc return 1; 206 1.1 jtc } 207 1.1 jtc if (hfirst > hlast) { 208 1.1 jtc char **temp; 209 1.1 jtc 210 1.1 jtc temp = hfirst; hfirst = hlast; hlast = temp; 211 1.1 jtc rflag = !rflag; /* POSIX */ 212 1.1 jtc } 213 1.1 jtc 214 1.1 jtc /* List history */ 215 1.1 jtc if (lflag) { 216 1.1 jtc char *s, *t; 217 1.1 jtc const char *nfmt = nflag ? "\t" : "%d\t"; 218 1.1 jtc 219 1.1 jtc for (hp = rflag ? hlast : hfirst; 220 1.1 jtc hp >= hfirst && hp <= hlast; hp += rflag ? -1 : 1) 221 1.1 jtc { 222 1.1 jtc shf_fprintf(shl_stdout, nfmt, 223 1.1 jtc hist_source->line - (int) (histptr - hp)); 224 1.1 jtc /* print multi-line commands correctly */ 225 1.1 jtc for (s = *hp; (t = strchr(s, '\n')); s = t) 226 1.1 jtc shf_fprintf(shl_stdout, "%.*s\t", ++t - s, s); 227 1.1 jtc shf_fprintf(shl_stdout, "%s\n", s); 228 1.1 jtc } 229 1.1 jtc shf_flush(shl_stdout); 230 1.1 jtc return 0; 231 1.1 jtc } 232 1.1 jtc 233 1.1 jtc /* Run editor on selected lines, then run resulting commands */ 234 1.1 jtc 235 1.4 hubertf tf = maketemp(ATEMP, TT_HIST_EDIT, &e->temps); 236 1.1 jtc if (!(shf = tf->shf)) { 237 1.1 jtc bi_errorf("cannot create temp file %s - %s", 238 1.1 jtc tf->name, strerror(errno)); 239 1.1 jtc return 1; 240 1.1 jtc } 241 1.1 jtc for (hp = rflag ? hlast : hfirst; 242 1.1 jtc hp >= hfirst && hp <= hlast; hp += rflag ? -1 : 1) 243 1.1 jtc shf_fprintf(shf, "%s\n", *hp); 244 1.1 jtc if (shf_close(shf) == EOF) { 245 1.1 jtc bi_errorf("error writing temporary file - %s", strerror(errno)); 246 1.1 jtc return 1; 247 1.1 jtc } 248 1.1 jtc 249 1.4 hubertf /* Ignore setstr errors here (arbitrary) */ 250 1.17 kamil setstr(local("_", false), tf->name, KSH_RETURN_ERROR); 251 1.1 jtc 252 1.1 jtc /* XXX: source should not get trashed by this.. */ 253 1.1 jtc { 254 1.1 jtc Source *sold = source; 255 1.1 jtc int ret; 256 1.1 jtc 257 1.1 jtc ret = command(editor ? editor : "${FCEDIT:-/bin/ed} $_"); 258 1.1 jtc source = sold; 259 1.1 jtc if (ret) 260 1.1 jtc return ret; 261 1.1 jtc } 262 1.1 jtc 263 1.1 jtc { 264 1.1 jtc struct stat statb; 265 1.1 jtc XString xs; 266 1.1 jtc char *xp; 267 1.1 jtc int n; 268 1.1 jtc 269 1.1 jtc if (!(shf = shf_open(tf->name, O_RDONLY, 0, 0))) { 270 1.1 jtc bi_errorf("cannot open temp file %s", tf->name); 271 1.1 jtc return 1; 272 1.1 jtc } 273 1.1 jtc 274 1.1 jtc n = fstat(shf_fileno(shf), &statb) < 0 ? 128 275 1.1 jtc : statb.st_size + 1; 276 1.1 jtc Xinit(xs, xp, n, hist_source->areap); 277 1.1 jtc while ((n = shf_read(xp, Xnleft(xs, xp), shf)) > 0) { 278 1.1 jtc xp += n; 279 1.1 jtc if (Xnleft(xs, xp) <= 0) 280 1.1 jtc XcheckN(xs, xp, Xlength(xs, xp)); 281 1.1 jtc } 282 1.1 jtc if (n < 0) { 283 1.1 jtc bi_errorf("error reading temp file %s - %s", 284 1.1 jtc tf->name, strerror(shf_errno(shf))); 285 1.1 jtc shf_close(shf); 286 1.1 jtc return 1; 287 1.1 jtc } 288 1.1 jtc shf_close(shf); 289 1.1 jtc *xp = '\0'; 290 1.1 jtc strip_nuls(Xstring(xs, xp), Xlength(xs, xp)); 291 1.1 jtc return hist_execute(Xstring(xs, xp)); 292 1.1 jtc } 293 1.1 jtc } 294 1.1 jtc 295 1.1 jtc /* Save cmd in history, execute cmd (cmd gets trashed) */ 296 1.1 jtc static int 297 1.1 jtc hist_execute(cmd) 298 1.1 jtc char *cmd; 299 1.1 jtc { 300 1.1 jtc Source *sold; 301 1.1 jtc int ret; 302 1.1 jtc char *p, *q; 303 1.1 jtc 304 1.1 jtc histbackup(); 305 1.1 jtc 306 1.1 jtc for (p = cmd; p; p = q) { 307 1.1 jtc if ((q = strchr(p, '\n'))) { 308 1.1 jtc *q++ = '\0'; /* kill the newline */ 309 1.1 jtc if (!*q) /* ignore trailing newline */ 310 1.1 jtc q = (char *) 0; 311 1.1 jtc } 312 1.1 jtc #ifdef EASY_HISTORY 313 1.1 jtc if (p != cmd) 314 1.17 kamil histappend(p, true); 315 1.1 jtc else 316 1.1 jtc #endif /* EASY_HISTORY */ 317 1.1 jtc histsave(++(hist_source->line), p, 1); 318 1.1 jtc 319 1.1 jtc shellf("%s\n", p); /* POSIX doesn't say this is done... */ 320 1.1 jtc if ((p = q)) /* restore \n (trailing \n not restored) */ 321 1.1 jtc q[-1] = '\n'; 322 1.1 jtc } 323 1.1 jtc 324 1.1 jtc /* Commands are executed here instead of pushing them onto the 325 1.1 jtc * input 'cause posix says the redirection and variable assignments 326 1.1 jtc * in 327 1.1 jtc * X=y fc -e - 42 2> /dev/null 328 1.20 rillig * are to affect the repeated commands environment. 329 1.1 jtc */ 330 1.1 jtc /* XXX: source should not get trashed by this.. */ 331 1.1 jtc sold = source; 332 1.1 jtc ret = command(cmd); 333 1.1 jtc source = sold; 334 1.1 jtc return ret; 335 1.1 jtc } 336 1.1 jtc 337 1.1 jtc static int 338 1.9 christos hist_replace(hp, pat, rep, globalv) 339 1.1 jtc char **hp; 340 1.1 jtc const char *pat; 341 1.1 jtc const char *rep; 342 1.9 christos int globalv; 343 1.1 jtc { 344 1.1 jtc char *line; 345 1.1 jtc 346 1.1 jtc if (!pat) 347 1.1 jtc line = str_save(*hp, ATEMP); 348 1.1 jtc else { 349 1.1 jtc char *s, *s1; 350 1.1 jtc int pat_len = strlen(pat); 351 1.1 jtc int rep_len = strlen(rep); 352 1.1 jtc int len; 353 1.1 jtc XString xs; 354 1.1 jtc char *xp; 355 1.1 jtc int any_subst = 0; 356 1.1 jtc 357 1.1 jtc Xinit(xs, xp, 128, ATEMP); 358 1.1 jtc for (s = *hp; (s1 = strstr(s, pat)) 359 1.9 christos && (!any_subst || globalv) ; s = s1 + pat_len) 360 1.1 jtc { 361 1.1 jtc any_subst = 1; 362 1.1 jtc len = s1 - s; 363 1.1 jtc XcheckN(xs, xp, len + rep_len); 364 1.1 jtc memcpy(xp, s, len); /* first part */ 365 1.1 jtc xp += len; 366 1.1 jtc memcpy(xp, rep, rep_len); /* replacement */ 367 1.1 jtc xp += rep_len; 368 1.1 jtc } 369 1.1 jtc if (!any_subst) { 370 1.1 jtc bi_errorf("substitution failed"); 371 1.1 jtc return 1; 372 1.1 jtc } 373 1.1 jtc len = strlen(s) + 1; 374 1.1 jtc XcheckN(xs, xp, len); 375 1.1 jtc memcpy(xp, s, len); 376 1.1 jtc xp += len; 377 1.1 jtc line = Xclose(xs, xp); 378 1.1 jtc } 379 1.1 jtc return hist_execute(line); 380 1.1 jtc } 381 1.1 jtc 382 1.1 jtc /* 383 1.1 jtc * get pointer to history given pattern 384 1.1 jtc * pattern is a number or string 385 1.1 jtc */ 386 1.1 jtc static char ** 387 1.1 jtc hist_get(str, approx, allow_cur) 388 1.1 jtc const char *str; 389 1.1 jtc int approx; 390 1.1 jtc int allow_cur; 391 1.1 jtc { 392 1.1 jtc char **hp = (char **) 0; 393 1.1 jtc int n; 394 1.1 jtc 395 1.1 jtc if (getn(str, &n)) { 396 1.1 jtc hp = histptr + (n < 0 ? n : (n - hist_source->line)); 397 1.6 jdolecek if (hp < histlist) { 398 1.1 jtc if (approx) 399 1.1 jtc hp = hist_get_oldest(); 400 1.1 jtc else { 401 1.1 jtc bi_errorf("%s: not in history", str); 402 1.1 jtc hp = (char **) 0; 403 1.1 jtc } 404 1.1 jtc } else if (hp > histptr) { 405 1.1 jtc if (approx) 406 1.1 jtc hp = hist_get_newest(allow_cur); 407 1.1 jtc else { 408 1.1 jtc bi_errorf("%s: not in history", str); 409 1.1 jtc hp = (char **) 0; 410 1.1 jtc } 411 1.1 jtc } else if (!allow_cur && hp == histptr) { 412 1.1 jtc bi_errorf("%s: invalid range", str); 413 1.1 jtc hp = (char **) 0; 414 1.1 jtc } 415 1.1 jtc } else { 416 1.1 jtc int anchored = *str == '?' ? (++str, 0) : 1; 417 1.1 jtc 418 1.1 jtc /* the -1 is to avoid the current fc command */ 419 1.6 jdolecek n = findhist(histptr - histlist - 1, 0, str, anchored); 420 1.1 jtc if (n < 0) { 421 1.1 jtc bi_errorf("%s: not in history", str); 422 1.1 jtc hp = (char **) 0; 423 1.1 jtc } else 424 1.6 jdolecek hp = &histlist[n]; 425 1.1 jtc } 426 1.1 jtc return hp; 427 1.1 jtc } 428 1.1 jtc 429 1.1 jtc /* Return a pointer to the newest command in the history */ 430 1.1 jtc static char ** 431 1.1 jtc hist_get_newest(allow_cur) 432 1.1 jtc int allow_cur; 433 1.1 jtc { 434 1.6 jdolecek if (histptr < histlist || (!allow_cur && histptr == histlist)) { 435 1.1 jtc bi_errorf("no history (yet)"); 436 1.1 jtc return (char **) 0; 437 1.1 jtc } 438 1.1 jtc if (allow_cur) 439 1.1 jtc return histptr; 440 1.1 jtc return histptr - 1; 441 1.1 jtc } 442 1.1 jtc 443 1.1 jtc /* Return a pointer to the newest command in the history */ 444 1.1 jtc static char ** 445 1.1 jtc hist_get_oldest() 446 1.1 jtc { 447 1.6 jdolecek if (histptr <= histlist) { 448 1.1 jtc bi_errorf("no history (yet)"); 449 1.1 jtc return (char **) 0; 450 1.1 jtc } 451 1.6 jdolecek return histlist; 452 1.1 jtc } 453 1.1 jtc 454 1.1 jtc /******************************/ 455 1.1 jtc /* Back up over last histsave */ 456 1.1 jtc /******************************/ 457 1.1 jtc static void 458 1.1 jtc histbackup() 459 1.1 jtc { 460 1.1 jtc static int last_line = -1; 461 1.1 jtc 462 1.6 jdolecek if (histptr >= histlist && last_line != hist_source->line) { 463 1.1 jtc hist_source->line--; 464 1.1 jtc afree((void*)*histptr, APERM); 465 1.1 jtc histptr--; 466 1.1 jtc last_line = hist_source->line; 467 1.1 jtc } 468 1.1 jtc } 469 1.1 jtc 470 1.1 jtc /* 471 1.1 jtc * Return the current position. 472 1.1 jtc */ 473 1.1 jtc char ** 474 1.1 jtc histpos() 475 1.1 jtc { 476 1.1 jtc return current; 477 1.1 jtc } 478 1.1 jtc 479 1.1 jtc int 480 1.1 jtc histN() 481 1.1 jtc { 482 1.1 jtc return curpos; 483 1.1 jtc } 484 1.1 jtc 485 1.1 jtc int 486 1.1 jtc histnum(n) 487 1.1 jtc int n; 488 1.1 jtc { 489 1.6 jdolecek int last = histptr - histlist; 490 1.1 jtc 491 1.1 jtc if (n < 0 || n >= last) { 492 1.1 jtc current = histptr; 493 1.1 jtc curpos = last; 494 1.1 jtc return last; 495 1.4 hubertf } else { 496 1.6 jdolecek current = &histlist[n]; 497 1.1 jtc curpos = n; 498 1.1 jtc return n; 499 1.1 jtc } 500 1.1 jtc } 501 1.1 jtc 502 1.1 jtc /* 503 1.7 mycroft * This will become unnecessary if hist_get is modified to allow 504 1.1 jtc * searching from positions other than the end, and in either 505 1.1 jtc * direction. 506 1.1 jtc */ 507 1.1 jtc int 508 1.1 jtc findhist(start, fwd, str, anchored) 509 1.1 jtc int start; 510 1.1 jtc int fwd; 511 1.1 jtc const char *str; 512 1.1 jtc int anchored; 513 1.1 jtc { 514 1.1 jtc char **hp; 515 1.6 jdolecek int maxhist = histptr - histlist; 516 1.1 jtc int incr = fwd ? 1 : -1; 517 1.1 jtc int len = strlen(str); 518 1.1 jtc 519 1.1 jtc if (start < 0 || start >= maxhist) 520 1.1 jtc start = maxhist; 521 1.1 jtc 522 1.6 jdolecek hp = &histlist[start]; 523 1.6 jdolecek for (; hp >= histlist && hp <= histptr; hp += incr) 524 1.1 jtc if ((anchored && strncmp(*hp, str, len) == 0) 525 1.1 jtc || (!anchored && strstr(*hp, str))) 526 1.6 jdolecek return hp - histlist; 527 1.1 jtc 528 1.1 jtc return -1; 529 1.1 jtc } 530 1.1 jtc 531 1.1 jtc /* 532 1.1 jtc * set history 533 1.1 jtc * this means reallocating the dataspace 534 1.1 jtc */ 535 1.1 jtc void 536 1.1 jtc sethistsize(n) 537 1.1 jtc int n; 538 1.1 jtc { 539 1.1 jtc if (n > 0 && n != histsize) { 540 1.6 jdolecek int cursize = histptr - histlist; 541 1.1 jtc 542 1.1 jtc /* save most recent history */ 543 1.1 jtc if (n < cursize) { 544 1.6 jdolecek memmove(histlist, histptr - n, n * sizeof(char *)); 545 1.1 jtc cursize = n; 546 1.1 jtc } 547 1.1 jtc 548 1.6 jdolecek histlist = (char **)aresize(histlist, n*sizeof(char *), APERM); 549 1.1 jtc 550 1.1 jtc histsize = n; 551 1.6 jdolecek histptr = histlist + cursize; 552 1.1 jtc } 553 1.1 jtc } 554 1.1 jtc 555 1.1 jtc /* 556 1.1 jtc * set history file 557 1.1 jtc * This can mean reloading/resetting/starting history file 558 1.1 jtc * maintenance 559 1.1 jtc */ 560 1.1 jtc void 561 1.1 jtc sethistfile(name) 562 1.1 jtc const char *name; 563 1.1 jtc { 564 1.1 jtc /* if not started then nothing to do */ 565 1.1 jtc if (hstarted == 0) 566 1.1 jtc return; 567 1.1 jtc 568 1.1 jtc /* if the name is the same as the name we have */ 569 1.1 jtc if (hname && strcmp(hname, name) == 0) 570 1.1 jtc return; 571 1.1 jtc 572 1.1 jtc /* 573 1.1 jtc * its a new name - possibly 574 1.1 jtc */ 575 1.1 jtc # ifdef EASY_HISTORY 576 1.1 jtc if (hname) { 577 1.1 jtc afree(hname, APERM); 578 1.1 jtc hname = NULL; 579 1.1 jtc } 580 1.1 jtc # else 581 1.1 jtc if (histfd) { 582 1.1 jtc /* yes the file is open */ 583 1.1 jtc (void) close(histfd); 584 1.1 jtc histfd = 0; 585 1.1 jtc hsize = 0; 586 1.1 jtc afree(hname, APERM); 587 1.1 jtc hname = NULL; 588 1.1 jtc /* let's reset the history */ 589 1.6 jdolecek histptr = histlist - 1; 590 1.1 jtc hist_source->line = 0; 591 1.1 jtc } 592 1.1 jtc # endif 593 1.1 jtc 594 1.1 jtc hist_init(hist_source); 595 1.1 jtc } 596 1.1 jtc 597 1.1 jtc /* 598 1.1 jtc * initialise the history vector 599 1.1 jtc */ 600 1.1 jtc void 601 1.1 jtc init_histvec() 602 1.1 jtc { 603 1.11 plunky if (histlist == NULL) { 604 1.1 jtc histsize = HISTORYSIZE; 605 1.6 jdolecek histlist = (char **)alloc(histsize*sizeof (char *), APERM); 606 1.6 jdolecek histptr = histlist - 1; 607 1.1 jtc } 608 1.1 jtc } 609 1.1 jtc 610 1.1 jtc # ifdef EASY_HISTORY 611 1.1 jtc /* 612 1.1 jtc * save command in history 613 1.1 jtc */ 614 1.1 jtc void 615 1.1 jtc histsave(lno, cmd, dowrite) 616 1.1 jtc int lno; /* ignored (compatibility with COMPLEX_HISTORY) */ 617 1.1 jtc const char *cmd; 618 1.1 jtc int dowrite; /* ignored (compatibility with COMPLEX_HISTORY) */ 619 1.1 jtc { 620 1.19 kamil char **hp = histptr; 621 1.1 jtc char *cp; 622 1.1 jtc 623 1.6 jdolecek if (++hp >= histlist + histsize) { /* remove oldest command */ 624 1.6 jdolecek afree((void*)histlist[0], APERM); 625 1.6 jdolecek memmove(histlist, histlist + 1, 626 1.6 jdolecek sizeof(histlist[0]) * (histsize - 1)); 627 1.6 jdolecek hp = &histlist[histsize - 1]; 628 1.1 jtc } 629 1.1 jtc *hp = str_save(cmd, APERM); 630 1.1 jtc /* trash trailing newline but allow imbedded newlines */ 631 1.1 jtc cp = *hp + strlen(*hp); 632 1.1 jtc if (cp > *hp && cp[-1] == '\n') 633 1.1 jtc cp[-1] = '\0'; 634 1.1 jtc histptr = hp; 635 1.1 jtc } 636 1.1 jtc 637 1.1 jtc /* 638 1.1 jtc * Append an entry to the last saved command. Used for multiline 639 1.1 jtc * commands 640 1.1 jtc */ 641 1.1 jtc void 642 1.4 hubertf histappend(cmd, nl_separate) 643 1.1 jtc const char *cmd; 644 1.4 hubertf int nl_separate; 645 1.1 jtc { 646 1.1 jtc int hlen, clen; 647 1.1 jtc char *p; 648 1.1 jtc 649 1.1 jtc hlen = strlen(*histptr); 650 1.1 jtc clen = strlen(cmd); 651 1.1 jtc if (clen > 0 && cmd[clen-1] == '\n') 652 1.1 jtc clen--; 653 1.1 jtc p = *histptr = (char *) aresize(*histptr, hlen + clen + 2, APERM); 654 1.1 jtc p += hlen; 655 1.4 hubertf if (nl_separate) 656 1.1 jtc *p++ = '\n'; 657 1.1 jtc memcpy(p, cmd, clen); 658 1.1 jtc p[clen] = '\0'; 659 1.1 jtc } 660 1.1 jtc 661 1.1 jtc /* 662 1.1 jtc * 92-04-25 <sjg@zen> 663 1.1 jtc * A simple history file implementation. 664 1.1 jtc * At present we only save the history when we exit. 665 1.1 jtc * This can cause problems when there are multiple shells are 666 1.1 jtc * running under the same user-id. The last shell to exit gets 667 1.1 jtc * to save its history. 668 1.1 jtc */ 669 1.1 jtc void 670 1.1 jtc hist_init(s) 671 1.1 jtc Source *s; 672 1.1 jtc { 673 1.1 jtc char *f; 674 1.1 jtc FILE *fh; 675 1.1 jtc 676 1.1 jtc if (Flag(FTALKING) == 0) 677 1.1 jtc return; 678 1.1 jtc 679 1.1 jtc hstarted = 1; 680 1.1 jtc 681 1.1 jtc hist_source = s; 682 1.1 jtc 683 1.1 jtc if ((f = str_val(global("HISTFILE"))) == NULL || *f == '\0') { 684 1.1 jtc # if 1 /* Don't use history file unless the user asks for it */ 685 1.1 jtc hname = NULL; 686 1.1 jtc return; 687 1.1 jtc # else 688 1.1 jtc char *home = str_val(global("HOME")); 689 1.1 jtc int len; 690 1.1 jtc 691 1.1 jtc if (home == NULL) 692 1.1 jtc home = null; 693 1.1 jtc f = HISTFILE; 694 1.1 jtc hname = alloc(len = strlen(home) + strlen(f) + 2, APERM); 695 1.1 jtc shf_snprintf(hname, len, "%s/%s", home, f); 696 1.1 jtc # endif 697 1.1 jtc } else 698 1.1 jtc hname = str_save(f, APERM); 699 1.1 jtc 700 1.1 jtc if ((fh = fopen(hname, "r"))) { 701 1.1 jtc int pos = 0, nread = 0; 702 1.1 jtc int contin = 0; /* continuation of previous command */ 703 1.1 jtc char *end; 704 1.1 jtc char hline[LINE + 1]; 705 1.1 jtc 706 1.1 jtc while (1) { 707 1.1 jtc if (pos >= nread) { 708 1.1 jtc pos = 0; 709 1.1 jtc nread = fread(hline, 1, LINE, fh); 710 1.1 jtc if (nread <= 0) 711 1.1 jtc break; 712 1.1 jtc hline[nread] = '\0'; 713 1.1 jtc } 714 1.1 jtc end = strchr(hline + pos, 0); /* will always succeed */ 715 1.1 jtc if (contin) 716 1.1 jtc histappend(hline + pos, 0); 717 1.1 jtc else { 718 1.1 jtc hist_source->line++; 719 1.1 jtc histsave(0, hline + pos, 0); 720 1.1 jtc } 721 1.1 jtc pos = end - hline + 1; 722 1.1 jtc contin = end == &hline[nread]; 723 1.1 jtc } 724 1.1 jtc fclose(fh); 725 1.1 jtc } 726 1.1 jtc } 727 1.1 jtc 728 1.1 jtc /* 729 1.1 jtc * save our history. 730 1.1 jtc * We check that we do not have more than we are allowed. 731 1.1 jtc * If the history file is read-only we do nothing. 732 1.1 jtc * Handy for having all shells start with a useful history set. 733 1.1 jtc */ 734 1.1 jtc 735 1.1 jtc void 736 1.1 jtc hist_finish() 737 1.1 jtc { 738 1.1 jtc static int once; 739 1.10 dsl int fd; 740 1.1 jtc FILE *fh; 741 1.19 kamil int i; 742 1.19 kamil char **hp; 743 1.1 jtc 744 1.1 jtc if (once++) 745 1.1 jtc return; 746 1.10 dsl if (hname == NULL || hname[0] == 0) 747 1.10 dsl return; 748 1.10 dsl 749 1.1 jtc /* check how many we have */ 750 1.6 jdolecek i = histptr - histlist; 751 1.1 jtc if (i >= histsize) 752 1.1 jtc hp = &histptr[-histsize]; 753 1.1 jtc else 754 1.6 jdolecek hp = histlist; 755 1.10 dsl 756 1.18 maya if ((fd = open(hname, O_WRONLY | O_CREAT | O_TRUNC | O_EXLOCK, 0600)) != -1) { 757 1.12 maya /* Remove anything written before we got the lock */ 758 1.12 maya ftruncate(fd, 0); 759 1.12 maya if ((fh = fdopen(fd, "w")) != NULL) { 760 1.12 maya for (i = 0; hp + i <= histptr && hp[i]; i++) 761 1.12 maya fprintf(fh, "%s%c", hp[i], '\0'); 762 1.12 maya fclose(fh); 763 1.12 maya } 764 1.1 jtc } 765 1.1 jtc } 766 1.1 jtc 767 1.1 jtc # else /* EASY_HISTORY */ 768 1.1 jtc 769 1.1 jtc /* 770 1.1 jtc * Routines added by Peter Collinson BSDI(Europe)/Hillside Systems to 771 1.1 jtc * a) permit HISTSIZE to control number of lines of history stored 772 1.1 jtc * b) maintain a physical history file 773 1.1 jtc * 774 1.1 jtc * It turns out that there is a lot of ghastly hackery here 775 1.1 jtc */ 776 1.1 jtc 777 1.1 jtc 778 1.1 jtc /* 779 1.1 jtc * save command in history 780 1.1 jtc */ 781 1.1 jtc void 782 1.1 jtc histsave(lno, cmd, dowrite) 783 1.1 jtc int lno; 784 1.1 jtc const char *cmd; 785 1.1 jtc int dowrite; 786 1.1 jtc { 787 1.19 kamil char **hp; 788 1.1 jtc char *c, *cp; 789 1.1 jtc 790 1.1 jtc c = str_save(cmd, APERM); 791 1.1 jtc if ((cp = strchr(c, '\n')) != NULL) 792 1.1 jtc *cp = '\0'; 793 1.1 jtc 794 1.1 jtc if (histfd && dowrite) 795 1.1 jtc writehistfile(lno, c); 796 1.1 jtc 797 1.1 jtc hp = histptr; 798 1.1 jtc 799 1.6 jdolecek if (++hp >= histlist + histsize) { /* remove oldest command */ 800 1.6 jdolecek afree((void*)*histlist, APERM); 801 1.6 jdolecek for (hp = histlist; hp < histlist + histsize - 1; hp++) 802 1.1 jtc hp[0] = hp[1]; 803 1.1 jtc } 804 1.1 jtc *hp = c; 805 1.1 jtc histptr = hp; 806 1.1 jtc } 807 1.1 jtc 808 1.1 jtc /* 809 1.1 jtc * Write history data to a file nominated by HISTFILE 810 1.1 jtc * if HISTFILE is unset then history still happens, but 811 1.1 jtc * the data is not written to a file 812 1.1 jtc * All copies of ksh looking at the file will maintain the 813 1.1 jtc * same history. This is ksh behaviour. 814 1.1 jtc * 815 1.1 jtc * This stuff uses mmap() 816 1.1 jtc * if your system ain't got it - then you'll have to undef HISTORYFILE 817 1.1 jtc */ 818 1.1 jtc 819 1.1 jtc /* 820 1.1 jtc * Open a history file 821 1.1 jtc * Format is: 822 1.1 jtc * Bytes 1, 2: HMAGIC - just to check that we are dealing with 823 1.1 jtc * the correct object 824 1.1 jtc * Then follows a number of stored commands 825 1.1 jtc * Each command is 826 1.1 jtc * <command byte><command number(4 bytes)><bytes><null> 827 1.1 jtc */ 828 1.1 jtc # define HMAGIC1 0xab 829 1.1 jtc # define HMAGIC2 0xcd 830 1.1 jtc # define COMMAND 0xff 831 1.1 jtc 832 1.1 jtc void 833 1.1 jtc hist_init(s) 834 1.1 jtc Source *s; 835 1.1 jtc { 836 1.1 jtc unsigned char *base; 837 1.1 jtc int lines; 838 1.1 jtc int fd; 839 1.1 jtc 840 1.1 jtc if (Flag(FTALKING) == 0) 841 1.1 jtc return; 842 1.1 jtc 843 1.1 jtc hstarted = 1; 844 1.1 jtc 845 1.1 jtc hist_source = s; 846 1.1 jtc 847 1.1 jtc hname = str_val(global("HISTFILE")); 848 1.1 jtc if (hname == NULL) 849 1.1 jtc return; 850 1.1 jtc hname = str_save(hname, APERM); 851 1.1 jtc 852 1.1 jtc retry: 853 1.1 jtc /* we have a file and are interactive */ 854 1.1 jtc if ((fd = open(hname, O_RDWR|O_CREAT|O_APPEND, 0600)) < 0) 855 1.1 jtc return; 856 1.1 jtc 857 1.1 jtc histfd = savefd(fd, 0); 858 1.1 jtc 859 1.1 jtc (void) flock(histfd, LOCK_EX); 860 1.1 jtc 861 1.1 jtc hsize = lseek(histfd, 0L, SEEK_END); 862 1.1 jtc 863 1.1 jtc if (hsize == 0) { 864 1.1 jtc /* add magic */ 865 1.1 jtc if (sprinkle(histfd)) { 866 1.1 jtc hist_finish(); 867 1.1 jtc return; 868 1.1 jtc } 869 1.1 jtc } 870 1.1 jtc else if (hsize > 0) { 871 1.1 jtc /* 872 1.1 jtc * we have some data 873 1.1 jtc */ 874 1.1 jtc base = (unsigned char *)mmap(0, hsize, PROT_READ, MAP_FLAGS, histfd, 0); 875 1.1 jtc /* 876 1.1 jtc * check on its validity 877 1.1 jtc */ 878 1.7 mycroft if (base == MAP_FAILED || *base != HMAGIC1 || base[1] != HMAGIC2) { 879 1.7 mycroft if (base != MAP_FAILED) 880 1.1 jtc munmap((caddr_t)base, hsize); 881 1.1 jtc hist_finish(); 882 1.1 jtc unlink(hname); 883 1.1 jtc goto retry; 884 1.1 jtc } 885 1.1 jtc if (hsize > 2) { 886 1.1 jtc lines = hist_count_lines(base+2, hsize-2); 887 1.1 jtc if (lines > histsize) { 888 1.1 jtc /* we need to make the file smaller */ 889 1.1 jtc if (hist_shrink(base, hsize)) 890 1.1 jtc unlink(hname); 891 1.1 jtc munmap((caddr_t)base, hsize); 892 1.1 jtc hist_finish(); 893 1.1 jtc goto retry; 894 1.1 jtc } 895 1.1 jtc } 896 1.1 jtc histload(hist_source, base+2, hsize-2); 897 1.1 jtc munmap((caddr_t)base, hsize); 898 1.1 jtc } 899 1.1 jtc (void) flock(histfd, LOCK_UN); 900 1.1 jtc hsize = lseek(histfd, 0L, SEEK_END); 901 1.1 jtc } 902 1.1 jtc 903 1.1 jtc typedef enum state { 904 1.1 jtc shdr, /* expecting a header */ 905 1.1 jtc sline, /* looking for a null byte to end the line */ 906 1.1 jtc sn1, /* bytes 1 to 4 of a line no */ 907 1.7 mycroft sn2, sn3, sn4 908 1.1 jtc } State; 909 1.1 jtc 910 1.1 jtc static int 911 1.1 jtc hist_count_lines(base, bytes) 912 1.19 kamil unsigned char *base; 913 1.19 kamil int bytes; 914 1.1 jtc { 915 1.1 jtc State state = shdr; 916 1.7 mycroft int lines = 0; 917 1.1 jtc 918 1.1 jtc while (bytes--) { 919 1.1 jtc switch (state) 920 1.1 jtc { 921 1.1 jtc case shdr: 922 1.1 jtc if (*base == COMMAND) 923 1.1 jtc state = sn1; 924 1.1 jtc break; 925 1.1 jtc case sn1: 926 1.1 jtc state = sn2; break; 927 1.1 jtc case sn2: 928 1.1 jtc state = sn3; break; 929 1.1 jtc case sn3: 930 1.1 jtc state = sn4; break; 931 1.1 jtc case sn4: 932 1.1 jtc state = sline; break; 933 1.1 jtc case sline: 934 1.1 jtc if (*base == '\0') 935 1.1 jtc lines++, state = shdr; 936 1.1 jtc } 937 1.1 jtc base++; 938 1.1 jtc } 939 1.1 jtc return lines; 940 1.1 jtc } 941 1.1 jtc 942 1.1 jtc /* 943 1.1 jtc * Shrink the history file to histsize lines 944 1.1 jtc */ 945 1.1 jtc static int 946 1.1 jtc hist_shrink(oldbase, oldbytes) 947 1.1 jtc unsigned char *oldbase; 948 1.1 jtc int oldbytes; 949 1.1 jtc { 950 1.1 jtc int fd; 951 1.1 jtc char nfile[1024]; 952 1.1 jtc struct stat statb; 953 1.1 jtc unsigned char *nbase = oldbase; 954 1.1 jtc int nbytes = oldbytes; 955 1.1 jtc 956 1.1 jtc nbase = hist_skip_back(nbase, &nbytes, histsize); 957 1.1 jtc if (nbase == NULL) 958 1.1 jtc return 1; 959 1.1 jtc if (nbase == oldbase) 960 1.1 jtc return 0; 961 1.1 jtc 962 1.1 jtc /* 963 1.1 jtc * create temp file 964 1.1 jtc */ 965 1.1 jtc (void) shf_snprintf(nfile, sizeof(nfile), "%s.%d", hname, procpid); 966 1.1 jtc if ((fd = creat(nfile, 0600)) < 0) 967 1.1 jtc return 1; 968 1.1 jtc 969 1.1 jtc if (sprinkle(fd)) { 970 1.1 jtc close(fd); 971 1.1 jtc unlink(nfile); 972 1.1 jtc return 1; 973 1.1 jtc } 974 1.1 jtc if (write(fd, nbase, nbytes) != nbytes) { 975 1.1 jtc close(fd); 976 1.1 jtc unlink(nfile); 977 1.1 jtc return 1; 978 1.1 jtc } 979 1.1 jtc /* 980 1.1 jtc * worry about who owns this file 981 1.1 jtc */ 982 1.1 jtc if (fstat(histfd, &statb) >= 0) 983 1.1 jtc fchown(fd, statb.st_uid, statb.st_gid); 984 1.1 jtc close(fd); 985 1.1 jtc 986 1.1 jtc /* 987 1.1 jtc * rename 988 1.1 jtc */ 989 1.1 jtc if (rename(nfile, hname) < 0) 990 1.1 jtc return 1; 991 1.1 jtc return 0; 992 1.1 jtc } 993 1.1 jtc 994 1.1 jtc 995 1.1 jtc /* 996 1.1 jtc * find a pointer to the data `no' back from the end of the file 997 1.1 jtc * return the pointer and the number of bytes left 998 1.1 jtc */ 999 1.1 jtc static unsigned char * 1000 1.1 jtc hist_skip_back(base, bytes, no) 1001 1.1 jtc unsigned char *base; 1002 1.1 jtc int *bytes; 1003 1.1 jtc int no; 1004 1.1 jtc { 1005 1.19 kamil int lines = 0; 1006 1.19 kamil unsigned char *ep; 1007 1.1 jtc 1008 1.1 jtc for (ep = base + *bytes; --ep > base; ) { 1009 1.1 jtc /* this doesn't really work: the 4 byte line number that is 1010 1.1 jtc * encoded after the COMMAND byte can itself contain the 1011 1.1 jtc * COMMAND byte.... 1012 1.1 jtc */ 1013 1.1 jtc for (; ep > base && *ep != COMMAND; ep--) 1014 1.1 jtc ; 1015 1.1 jtc if (ep == base) 1016 1.1 jtc break; 1017 1.1 jtc if (++lines == no) { 1018 1.1 jtc *bytes = *bytes - ((char *)ep - (char *)base); 1019 1.1 jtc return ep; 1020 1.1 jtc } 1021 1.1 jtc } 1022 1.1 jtc return NULL; 1023 1.1 jtc } 1024 1.1 jtc 1025 1.1 jtc /* 1026 1.1 jtc * load the history structure from the stored data 1027 1.1 jtc */ 1028 1.1 jtc static void 1029 1.1 jtc histload(s, base, bytes) 1030 1.1 jtc Source *s; 1031 1.19 kamil unsigned char *base; 1032 1.19 kamil int bytes; 1033 1.1 jtc { 1034 1.1 jtc State state; 1035 1.7 mycroft int lno = 0; 1036 1.7 mycroft unsigned char *line = NULL; 1037 1.1 jtc 1038 1.1 jtc for (state = shdr; bytes-- > 0; base++) { 1039 1.1 jtc switch (state) { 1040 1.1 jtc case shdr: 1041 1.1 jtc if (*base == COMMAND) 1042 1.1 jtc state = sn1; 1043 1.1 jtc break; 1044 1.1 jtc case sn1: 1045 1.1 jtc lno = (((*base)&0xff)<<24); 1046 1.1 jtc state = sn2; 1047 1.1 jtc break; 1048 1.1 jtc case sn2: 1049 1.1 jtc lno |= (((*base)&0xff)<<16); 1050 1.1 jtc state = sn3; 1051 1.1 jtc break; 1052 1.1 jtc case sn3: 1053 1.1 jtc lno |= (((*base)&0xff)<<8); 1054 1.1 jtc state = sn4; 1055 1.1 jtc break; 1056 1.1 jtc case sn4: 1057 1.1 jtc lno |= (*base)&0xff; 1058 1.1 jtc line = base+1; 1059 1.1 jtc state = sline; 1060 1.1 jtc break; 1061 1.1 jtc case sline: 1062 1.1 jtc if (*base == '\0') { 1063 1.1 jtc /* worry about line numbers */ 1064 1.6 jdolecek if (histptr >= histlist && lno-1 != s->line) { 1065 1.1 jtc /* a replacement ? */ 1066 1.1 jtc histinsert(s, lno, line); 1067 1.1 jtc } 1068 1.1 jtc else { 1069 1.1 jtc s->line = lno; 1070 1.1 jtc histsave(lno, (char *)line, 0); 1071 1.1 jtc } 1072 1.1 jtc state = shdr; 1073 1.1 jtc } 1074 1.1 jtc } 1075 1.1 jtc } 1076 1.1 jtc } 1077 1.1 jtc 1078 1.1 jtc /* 1079 1.1 jtc * Insert a line into the history at a specified number 1080 1.1 jtc */ 1081 1.1 jtc static void 1082 1.1 jtc histinsert(s, lno, line) 1083 1.1 jtc Source *s; 1084 1.1 jtc int lno; 1085 1.1 jtc unsigned char *line; 1086 1.1 jtc { 1087 1.19 kamil char **hp; 1088 1.1 jtc 1089 1.6 jdolecek if (lno >= s->line-(histptr-histlist) && lno <= s->line) { 1090 1.1 jtc hp = &histptr[lno-s->line]; 1091 1.1 jtc if (*hp) 1092 1.1 jtc afree((void*)*hp, APERM); 1093 1.1 jtc *hp = str_save((char *)line, APERM); 1094 1.1 jtc } 1095 1.1 jtc } 1096 1.1 jtc 1097 1.1 jtc /* 1098 1.1 jtc * write a command to the end of the history file 1099 1.1 jtc * This *MAY* seem easy but it's also necessary to check 1100 1.1 jtc * that the history file has not changed in size. 1101 1.1 jtc * If it has - then some other shell has written to it 1102 1.1 jtc * and we should read those commands to update our history 1103 1.1 jtc */ 1104 1.1 jtc static void 1105 1.1 jtc writehistfile(lno, cmd) 1106 1.1 jtc int lno; 1107 1.1 jtc char *cmd; 1108 1.1 jtc { 1109 1.1 jtc int sizenow; 1110 1.1 jtc unsigned char *base; 1111 1.1 jtc unsigned char *new; 1112 1.1 jtc int bytes; 1113 1.7 mycroft unsigned char hdr[5]; 1114 1.1 jtc 1115 1.1 jtc (void) flock(histfd, LOCK_EX); 1116 1.1 jtc sizenow = lseek(histfd, 0L, SEEK_END); 1117 1.1 jtc if (sizenow != hsize) { 1118 1.1 jtc /* 1119 1.1 jtc * Things have changed 1120 1.1 jtc */ 1121 1.1 jtc if (sizenow > hsize) { 1122 1.1 jtc /* someone has added some lines */ 1123 1.1 jtc bytes = sizenow - hsize; 1124 1.1 jtc base = (unsigned char *)mmap(0, sizenow, PROT_READ, MAP_FLAGS, histfd, 0); 1125 1.7 mycroft if (base == MAP_FAILED) 1126 1.1 jtc goto bad; 1127 1.1 jtc new = base + hsize; 1128 1.1 jtc if (*new != COMMAND) { 1129 1.1 jtc munmap((caddr_t)base, sizenow); 1130 1.1 jtc goto bad; 1131 1.1 jtc } 1132 1.1 jtc hist_source->line--; 1133 1.1 jtc histload(hist_source, new, bytes); 1134 1.1 jtc hist_source->line++; 1135 1.1 jtc lno = hist_source->line; 1136 1.1 jtc munmap((caddr_t)base, sizenow); 1137 1.1 jtc hsize = sizenow; 1138 1.1 jtc } else { 1139 1.1 jtc /* it has shrunk */ 1140 1.1 jtc /* but to what? */ 1141 1.1 jtc /* we'll give up for now */ 1142 1.1 jtc goto bad; 1143 1.1 jtc } 1144 1.1 jtc } 1145 1.1 jtc /* 1146 1.1 jtc * we can write our bit now 1147 1.1 jtc */ 1148 1.1 jtc hdr[0] = COMMAND; 1149 1.1 jtc hdr[1] = (lno>>24)&0xff; 1150 1.1 jtc hdr[2] = (lno>>16)&0xff; 1151 1.1 jtc hdr[3] = (lno>>8)&0xff; 1152 1.1 jtc hdr[4] = lno&0xff; 1153 1.1 jtc (void) write(histfd, hdr, 5); 1154 1.1 jtc (void) write(histfd, cmd, strlen(cmd)+1); 1155 1.1 jtc hsize = lseek(histfd, 0L, SEEK_END); 1156 1.1 jtc (void) flock(histfd, LOCK_UN); 1157 1.1 jtc return; 1158 1.1 jtc bad: 1159 1.1 jtc hist_finish(); 1160 1.1 jtc } 1161 1.1 jtc 1162 1.1 jtc void 1163 1.1 jtc hist_finish() 1164 1.1 jtc { 1165 1.1 jtc (void) flock(histfd, LOCK_UN); 1166 1.1 jtc (void) close(histfd); 1167 1.1 jtc histfd = 0; 1168 1.1 jtc } 1169 1.1 jtc 1170 1.1 jtc /* 1171 1.1 jtc * add magic to the history file 1172 1.1 jtc */ 1173 1.1 jtc static int 1174 1.1 jtc sprinkle(fd) 1175 1.1 jtc int fd; 1176 1.1 jtc { 1177 1.7 mycroft static unsigned char mag[] = { HMAGIC1, HMAGIC2 }; 1178 1.1 jtc 1179 1.1 jtc return(write(fd, mag, 2) != 2); 1180 1.1 jtc } 1181 1.1 jtc 1182 1.1 jtc # endif 1183 1.1 jtc #else /* HISTORY */ 1184 1.1 jtc 1185 1.1 jtc /* No history to be compiled in: dummy routines to avoid lots more ifdefs */ 1186 1.1 jtc void 1187 1.1 jtc init_histvec() 1188 1.1 jtc { 1189 1.1 jtc } 1190 1.1 jtc void 1191 1.1 jtc hist_init(s) 1192 1.1 jtc Source *s; 1193 1.1 jtc { 1194 1.1 jtc } 1195 1.1 jtc void 1196 1.1 jtc hist_finish() 1197 1.1 jtc { 1198 1.1 jtc } 1199 1.1 jtc void 1200 1.1 jtc histsave(lno, cmd, dowrite) 1201 1.1 jtc int lno; 1202 1.1 jtc const char *cmd; 1203 1.1 jtc int dowrite; 1204 1.1 jtc { 1205 1.1 jtc errorf("history not enabled"); 1206 1.1 jtc } 1207 1.1 jtc #endif /* HISTORY */ 1208