history.c (6823B)
1 /* 2 history.c -- primitive history mechanism 3 4 Paul Haahr & Byron Rakitzis, July 1991. 5 6 This program mimics the att v8 = and == history programs. 7 The edit() algorithm was adapted from a similar program 8 that Boyd Roberts wrote, but otherwise all the code has 9 been written from scratch. 10 11 edit() was subsequently redone by Hugh Redelmeier in order 12 to correctly deal with tab characters in the source line. 13 14 BUGS: 15 There is an implicit assumption that commands are no 16 more than 1k characters long. 17 */ 18 19 #include "rc.h" 20 21 #include <stdio.h> 22 23 static const char id[] = "$Release: @(#)" PACKAGE " " VERSION " " DESCRIPTION " $"; 24 25 #define CHUNKSIZE 65536 26 27 static struct { 28 char *old, *new; 29 int reps; /* no. of repetitions. i.e. 1 means sub twice. */ 30 } *replace; 31 32 static char **search, *progname, *history; 33 static char me; /* typically ':' or '-' */ 34 static bool editit = FALSE, printit = FALSE; 35 static int nreplace = 0, nsearch = 0; 36 static FILE *histfile; 37 38 void *ealloc(size_t n) { 39 void *p = (void *) malloc(n); 40 if (p == NULL) { 41 perror("malloc"); 42 exit(1); 43 } 44 return p; 45 } 46 47 void *erealloc(void *p, size_t n) { 48 p = (void *) realloc(p, n); 49 if (p == NULL) { 50 perror("realloc"); 51 exit(1); 52 } 53 return p; 54 } 55 56 static char *newstr() { 57 return ealloc((size_t)1024); 58 } 59 60 static char *rc_basename(char *s) { 61 char *t = strrchr(s, '/'); 62 return (t == NULL) ? s : t + 1; 63 } 64 65 /* stupid O(n^2) substring matching routine */ 66 67 static char *isin(char *target, char *pattern) { 68 size_t plen = strlen(pattern); 69 size_t tlen = strlen(target); 70 for (; tlen >= plen; target++, --tlen) 71 if (strncmp(target, pattern, plen) == 0) 72 return target; 73 return NULL; 74 } 75 76 /* replace the first match in the string with "new" */ 77 static char *sub(char *s, char *old, char *new) { 78 char *t, *u; 79 80 t = isin(s, old); 81 if (!t) 82 return s; 83 u = newstr(); 84 85 *t = '\0'; 86 while (*old != '\0') 87 old++, t++; 88 strcpy(u, s); 89 strcat(u, new); 90 strcat(u, t); 91 return u; 92 } 93 94 static char *edit(char *s) { 95 char *final, *f, *end; 96 int col; 97 bool ins; 98 99 start: 100 fprintf(stderr, "%s\n", s); 101 f = final = newstr(); 102 end = s + strlen(s); 103 col = 0; 104 ins = FALSE; 105 106 for (;; col++) { 107 int c = getchar(); 108 109 if (c == me && col == 0) { 110 int peekc = getchar(); 111 if (peekc == '\n') 112 return NULL; 113 ungetc(peekc, stdin); 114 } 115 if (c == '\n') { 116 if (col == 0) 117 return s; 118 119 while (s < end) /* copy remainder of string */ 120 *f++ = *s++; 121 *f = '\0'; 122 s = final; 123 goto start; 124 } else if (ins || s>=end) { 125 /* col need not be accurate -- tabs need not be interpreted */ 126 *f++ = c; 127 } else { 128 switch (c) { 129 case '+': 130 while (s < end) 131 *f++ = *s++; 132 *f = '\0'; 133 continue; 134 case '%': 135 c = ' '; 136 /* FALLTHROUGH */ 137 default: 138 *f++ = c; 139 break; 140 case EOF: 141 exit(1); 142 /* NOTREACHED */ 143 case ' ': 144 if (*s == '\t') { 145 int oldcol = col; 146 147 for (;; col++) { 148 int peekc; 149 150 if ((col&07) == 07) { 151 *f++ = '\t'; /* we spaced past a tab */ 152 break; 153 } 154 peekc = getchar(); 155 if (peekc != ' ') { 156 ungetc(peekc, stdin); 157 if (peekc != '\n') { 158 /* we spaced partially into a tab */ 159 do { 160 *f++ = ' '; 161 oldcol++; 162 } while (oldcol <= col); 163 } 164 break; 165 } 166 } 167 } else { 168 *f++ = *s; 169 } 170 break; 171 case '#': 172 break; 173 case '$': 174 end = s; /* truncate s */ 175 continue; /* skip incrementing s */ 176 case '^': 177 ins = TRUE; 178 continue; /* skip incrementing s */ 179 case '\t': 180 for (;; col++) { 181 *f = s<end? *s++ : '\t'; 182 if (*f++ == '\t') { 183 col = col | 07; /* advance to before next tabstop */ 184 } 185 if ((col&07) == 07) /* stop before tabstop */ 186 break; 187 } 188 continue; /* skip incrementing s */ 189 } 190 if (s<end && (*s!='\t' || (col&07)==07)) 191 s++; 192 } 193 } 194 } 195 196 static char *readhistoryfile(char **last) { 197 char *buf; 198 size_t count, size; 199 long nread; 200 201 if ((history = getenv("history")) == NULL) { 202 fprintf(stderr, "$history not set\n"); 203 exit(1); 204 } 205 histfile = fopen(history, "r+"); 206 if (histfile == NULL) { 207 perror(history); 208 exit(1); 209 } 210 211 size = CHUNKSIZE; 212 buf = ealloc(size); 213 buf[0] = '\0'; count = 1; /* sentinel */ 214 while ((nread = fread(buf + count, sizeof (char), size - count, histfile)) > 0) { 215 count += nread; 216 if (size - count == 0) 217 buf = erealloc(buf, size *= 4); 218 } 219 if (nread == -1) { 220 perror(history); 221 exit(1); 222 } 223 *last = buf + count; 224 return buf; 225 } 226 227 static char *getcommand(void) { 228 char *s, *t; 229 static char *hist = NULL, *last; 230 231 if (hist == NULL) { 232 hist = readhistoryfile(&last); 233 *--last = '\0'; /* replaces final newline */ 234 ++hist; /* start beyond sentinel */ 235 } 236 237 again: s = last; 238 if (s < hist) 239 return NULL; 240 while (s >= hist && *s != '\n') 241 --s; 242 *s = '\0'; 243 last = s++; 244 245 /* 246 * if the command contains the "me" character at the start of the line 247 * or after any of [`{|()@/] then try again 248 */ 249 250 for (t = s; *t != '\0'; ++t) 251 if (*t == me) { 252 char *u = t - 1; 253 while (u >= s && (*u == ' ' || *u == '\t')) 254 --u; 255 if (u < s) 256 goto again; 257 switch (*u) { 258 case '`': case '@': 259 case '(': case ')': 260 case '{': case '|': 261 case '/': 262 goto again; 263 default: 264 break; 265 } 266 } 267 return s; 268 } 269 270 int main(int argc, char **argv) { 271 int i; 272 char *s; 273 274 s = progname = rc_basename(argv[0]); 275 me = *s++; 276 if (*s == me) { 277 s++; 278 editit = TRUE; 279 } 280 if (*s == 'p') { 281 s++; 282 printit = TRUE; 283 } 284 /* Nahh... 285 if (*s != '\0') { 286 fprintf(stderr, "\"%s\": bad name for history program\n", progname); 287 exit(1); 288 } 289 */ 290 291 if (argc > 1) { 292 replace = ealloc((argc - 1) * sizeof *replace); 293 search = ealloc((argc - 1) * sizeof *search); 294 } 295 for (i = 1; i < argc; i++) 296 if ((s = strchr(argv[i], ':')) == NULL) 297 search[nsearch++] = argv[i]; 298 else { 299 *(char *)s = '\0'; /* do we confuse ps too much? */ 300 replace[nreplace].reps = 0; 301 while(*(++s) == ':') { 302 replace[nreplace].reps++; 303 } 304 replace[nreplace].old = argv[i]; 305 replace[nreplace].new = s; 306 nreplace++; 307 } 308 309 next: s = getcommand(); 310 if (s == NULL) { 311 fprintf(stderr, "command not matched\n"); 312 return 1; 313 } 314 for (i = 0; i < nsearch; i++) 315 if (!isin(s, search[i])) 316 goto next; 317 for (i = 0; i < nreplace; i++) 318 if (!isin(s, replace[i].old)) 319 goto next; 320 else { 321 int j; 322 for (j = 0; j <= replace[i].reps; j++) 323 s = sub(s, replace[i].old, replace[i].new); 324 } 325 if (editit) { 326 s = edit(s); 327 if (s == NULL) 328 goto next; 329 } 330 fseek(histfile, 0, 2); /* 2 == end of file. i.e., append command to $history */ 331 fprintf(histfile, "%s\n", s); 332 fclose(histfile); 333 if (printit) 334 printf("%s\n", s); 335 else { 336 char *shell = getenv("SHELL"); 337 338 if (!editit) 339 fprintf(stderr, "%s\n", s); 340 if (shell == NULL) 341 shell = "/bin/sh"; 342 execl(shell, rc_basename(shell), "-c", s, NULL); 343 perror(shell); 344 exit(1); 345 } 346 return 0; 347 }