rc

[fork] interactive rc shell
git clone https://hhvn.uk/rc
git clone git://hhvn.uk/rc
Log | Files | Refs | README | LICENSE

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 }