From 8d92d0f8bd2b6ea4b6d1b10bb6ced3b933c6ac18 Mon Sep 17 00:00:00 2001 From: Trent Huber Date: Wed, 14 May 2025 00:51:40 -0400 Subject: [PATCH] History/arrow keys, improved tokenizer --- main.c | 141 ++++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 125 insertions(+), 16 deletions(-) diff --git a/main.c b/main.c index f833d41..66ff91e 100644 --- a/main.c +++ b/main.c @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -15,9 +16,81 @@ void ashexit(void) { warn("Unable to return to initial terminal settings"); } +void *allocate(size_t s) { + void *r; + + if (!(r = malloc(s))) err(EXIT_FAILURE, "Memory allocation"); + + return memset(r, 0, s); +} + #define BUFLEN 1024 -#define HISTLEN 1000 +#define HISTLEN 100 #define PROMPT "% " +#define HISTNAME ".ashhistory" + +char history[HISTLEN][BUFLEN], (*hb)[BUFLEN], (*hc)[BUFLEN], (*ht)[BUFLEN]; +static char *histpath; + +static void freehistpath(void) { + free(histpath); +} + +void readhist(void) { + char *homepath; + FILE *histfile; + int e; + + if (!(homepath = getenv("HOME"))) + errx(EXIT_FAILURE, "HOME environment variable does not exist"); + histpath = allocate(strlen(homepath) + 1 + strlen(HISTNAME) + 1); + if (atexit(freehistpath) == -1) + err(EXIT_FAILURE, "Unable to add `freehistpath()' to `atexit()'"); + strcpy(histpath, homepath); + strcat(histpath, "/"); + strcat(histpath, HISTNAME); + + ht = hc = hb = history; + if (!(histfile = fopen(histpath, "r"))) { + if (errno == ENOENT) return; + err(EXIT_FAILURE, "Unable to open history file for reading"); + } + while (fgets(*ht, BUFLEN, histfile)) { + *(*ht + strlen(*ht) - 1) = '\0'; // Get rid of newline + ht = history + (ht - history + 1) % HISTLEN; + if (ht == hb) hb = NULL; + } + + e = ferror(histfile) || !feof(histfile); + if (fclose(histfile) == EOF) err(EXIT_FAILURE, "Unable to close history file"); + if (e) err(EXIT_FAILURE, "Unable to read from history file"); + + if (!hb) hb = history + (ht - history + 1) % HISTLEN; + hc = ht; +} + +void writehist(void) { + FILE *histfile; + + if (!(histfile = fopen(histpath, "w"))) { + warn("Unable to open history file for writing"); + return; + } + + while (hb != ht) { + if (fputs(*hb, histfile) == EOF) { + warn("Unable to write to history file"); + break; + } + if (fputc('\n', histfile) == EOF) { + warn("Unable to write newline to history file"); + break; + } + hb = history + (hb - history + 1) % HISTLEN; + } + + if (fclose(histfile) == EOF) warn("Unable to close history stream"); +} enum { CTRLC = '\003', @@ -31,9 +104,7 @@ enum { }; char *getinput(void) { - static char history[HISTLEN][BUFLEN], result[BUFLEN], - (*hb)[BUFLEN] = history, (*hc)[BUFLEN] = history, - (*ht)[BUFLEN] = history; + static char result[BUFLEN]; char *cursor, *end; int c, i; @@ -112,19 +183,50 @@ char *getinput(void) { return end > result ? result : NULL; } +int delimiter(char c) { + return c == ' ' || c == '&' || c == '|' || c == ';' || c == '`' || c == '\0'; +} + char **tokenize(char *p) { size_t i; - static char *result[BUFLEN / 2]; + static char *result[BUFLEN]; while (*p == ' ') ++p; for (i = 0; *p; ++i) { - result[i] = p; - while (*p != ' ' && *p != '\0') ++p; - if (*p == '\0') { - ++i; + switch (*p) { + case ' ': + --i; + *p++ = '\0'; + while (*p == ' ') ++p; + break; + case '&': + *p++ = '\0'; + if (*p == '&') { + ++p; + result[i] = "&&"; + } else result[i] = "&"; + break; + case '|': + *p++ = '\0'; + if (*p == '|') { + ++p; + result[i] = "||"; + } else result[i] = "|"; break; + case ';': + *p++ = '\0'; + result[i] = ";"; + break; + case '`': + *p++ = '\0'; + result[i] = p; + while (*p != '\'') ++p; + *p++ = '\0'; + break; + default: + result[i] = p++; + while (!delimiter(*p)) ++p; } - while (*p == ' ') ++p; } result[i] = NULL; return result; @@ -153,10 +255,10 @@ void await(pid_t cpgid) { if (waitpid(cpgid, &status, WUNTRACED) != -1 && !WIFSTOPPED(status)) { while (waitpid(-cpgid, NULL, 0) != -1); - if (errno != ECHILD && killpg(cpgid, SIGKILL) == -1) exit(EXIT_FAILURE); + if (errno != ECHILD && killpg(cpgid, SIGKILL) == -1) _Exit(EXIT_FAILURE); } - if (signal(SIGTTOU, SIG_IGN) == SIG_ERR) exit(EXIT_FAILURE); - if (tcsetpgrp(STDIN_FILENO, self) == -1) exit(EXIT_FAILURE); + if (signal(SIGTTOU, SIG_IGN) == SIG_ERR) _Exit(EXIT_FAILURE); + if (tcsetpgrp(STDIN_FILENO, self) == -1) _Exit(EXIT_FAILURE); if (signal(SIGTTOU, SIG_DFL) == SIG_ERR) warn("Ignoring signal SIGTTOU"); if (WIFSIGNALED(status)) putchar('\n'); @@ -179,7 +281,7 @@ int builtin(char **tokens) { return 1; } if (killpg(fgpgid, SIGCONT) == -1) { - if (tcsetpgrp(STDIN_FILENO, self) == -1) exit(EXIT_FAILURE); + if (tcsetpgrp(STDIN_FILENO, self) == -1) _Exit(EXIT_FAILURE); warn("Unable to wake up process group %d", fgpgid); return 1; } @@ -194,6 +296,10 @@ int main(void) { pid_t cpgid; struct termios raw; + readhist(); + if (atexit(writehist) == -1) + err(EXIT_FAILURE, "Unable to add `writehist()' to `atexit()'"); + if (tcgetattr(STDIN_FILENO, &canonical) == -1) err(EXIT_FAILURE, "Unable to get termios structure"); raw = canonical; @@ -202,13 +308,16 @@ int main(void) { if (tcsetattr(STDIN_FILENO, TCSANOW, &raw) == -1) err(EXIT_FAILURE, "Unable to set termios structure"); if (atexit(ashexit) == -1) - err(EXIT_FAILURE, "Unable to set `atexit()'"); + err(EXIT_FAILURE, "Unable to add `ashexit()' to `atexit()'"); if ((self = getpgid(0)) == -1) err(EXIT_FAILURE, "Unable to get pgid of self"); for (;;) { if (!(input = getinput())) continue; tokens = tokenize(input); +// for (size_t i = 0; tokens[i]; ++i) printf("|%s|, ", tokens[i]); +// putchar('\n'); + if (!*tokens || builtin(tokens)) continue; if ((cpgid = fork()) == 0 && execvp(tokens[0], tokens) == -1) { @@ -228,7 +337,7 @@ int main(void) { continue; } if (killpg(cpgid, SIGCONT) == -1) { - if (tcsetpgrp(STDIN_FILENO, self) == -1) exit(EXIT_FAILURE); + if (tcsetpgrp(STDIN_FILENO, self) == -1) _Exit(EXIT_FAILURE); warn("Process group %d may be blocked; killing it", cpgid); if (killpg(cpgid, SIGKILL) == -1) warn("Unable to kill process group %d; manual termination required", cpgid); -- 2.51.0