From 4742ebbd874786bd3aee6f5939081b8892a98905 Mon Sep 17 00:00:00 2001 From: Trent Huber Date: Tue, 11 Nov 2025 17:12:04 -0500 Subject: [PATCH] Introduce exec builtin, many bug fixes and updates --- INSTALL.md | 35 ++++++++++-------- src/builtins/README.md | 25 ++++++++++--- src/builtins/alias.c | 48 ++++++++++++++----------- src/builtins/bg.c | 8 ++--- src/builtins/build.c | 2 +- src/builtins/builtin.c | 11 ++---- src/builtins/builtin.h | 4 +-- src/builtins/cd.c | 10 +++--- src/builtins/exec.c | 44 +++++++++++++++++++++++ src/builtins/exec.h | 1 + src/builtins/exeunt.c | 4 +-- src/builtins/fg.c | 17 ++++----- src/builtins/fg.h | 1 + src/builtins/list.h | 2 +- src/builtins/mode.c | 10 +++--- src/builtins/pwd.c | 4 +-- src/builtins/set.c | 10 +++--- src/builtins/source.c | 34 +++++++++--------- src/builtins/unalias.c | 6 ++-- src/builtins/unset.c | 8 ++--- src/builtins/which.c | 41 +++++---------------- src/context.c | 2 +- src/context.h | 40 ++++++++++----------- src/input.c | 39 ++++++++++---------- src/main.c | 10 +++--- src/options.c | 41 +++++++++++---------- src/options.h | 4 ++- src/parse.c | 64 +++++++++++++++++++-------------- src/run.c | 82 +++++++++++++++++++++--------------------- src/run.h | 2 +- src/utils.c | 66 +++++++++++++++++++++++++++++++--- src/utils.h | 4 +-- tools/install.c | 7 ++-- tools/uninstall.c | 8 +++-- 34 files changed, 407 insertions(+), 287 deletions(-) create mode 100644 src/builtins/exec.c create mode 100644 src/builtins/exec.h diff --git a/INSTALL.md b/INSTALL.md index 977b572..c220c9a 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -37,19 +37,20 @@ interactive sessions, in this case, `.shrc`. # ~.shrc test -z "$ESCAPE" && echo $- | grep -q i && exec thus $THUSLOGIN -``` - -`THUSLOGIN` will be set to the `-l` flag only in the case when sh is running as -a login shell. Running thus with the `-l` flag starts *it* as a login shell. +unset ESCAPE -Notice if sh is called from within thus, then it will run *as* thus which makes -it impossible to open sh as an interactive subshell. This is why the `ESCAPE` -environment variable is used as a guard in the command list. Defining `ESCAPE` -will make all subsequent calls to sh open sh itself as an interactive shell, not -thus. +alias sh=~/.sh.sh +``` -Since aliases take precedent over executables in the path, you could even define -an alias to sh that runs a script that runs sh after defining `ESCAPE`. +There are two things to note. First, `THUSLOGIN` will be set to the `-l` flag +only in the case when sh is running as a login shell. Running thus with the `-l` +flag starts *it* as a login shell. Second, if `ESCAPE` isn't set in the +environment, then sh will just switch to running thus. This behavior is indeed +what we want when we're opening an interactive shell for the first time, but +when we're running sh as a subshell, it ends up running thus instead. This is +why the `ESCAPE` environment variable exists. Every time we want to run sh from +inside thus, we first need to set `ESCAPE`. This functionality can be automated +in a script, say `~.sh.sh`. ```sh # ~.sh.sh @@ -57,14 +58,18 @@ an alias to sh that runs a script that runs sh after defining `ESCAPE`. #! /usr/bin/env thus set ESCAPE escape -env sh -unset ESCAPE +exec sh ``` -And then the alias goes in the interactive config file for thus. +We can add an alias to this script in the interactive configuration file used by +thus, that is `~.thusrc`. Putting it inside `~.thusrc` ensures that the alias +only exists when running thus as an interactive session, which is exactly what +we want. Notice that this alias was also added to the sh interactive +configuration file earlier. This is to ensure you can still run sh as a subshell +inside itself. ```sh # ~.thusrc -alias sh "~.sh.sh" +alias sh ~.sh.sh ``` diff --git a/src/builtins/README.md b/src/builtins/README.md index ab2364f..0d77dbd 100644 --- a/src/builtins/README.md +++ b/src/builtins/README.md @@ -1,6 +1,9 @@ # Adding a built-in shell command -To add a built-in command to the shell, first make a source file in the `src/builtins/` directory with the same name as the built-in. The source file should contain at least the following code (where "foo" is the name of the built-in). +To add a built-in command to the shell, first make a source file in the +`src/builtins/` directory with the same name as the built-in. The source file +should contain at least the following code (where "foo" is the name of the +built-in). ```c /* foo.c */ @@ -10,13 +13,25 @@ To add a built-in command to the shell, first make a source file in the `src/bui #include "builtin.h" #include "utils.h" -BUILTIN(foo) { +int foo(char **args, size_t numargs) { + + /* ... */ + return EXIT_SUCCESS; } ``` -The `BUILTIN()` macro is defined in [`builtin.h`](builtin.h) and provides an interface similar to that of `main()`, passing the arguments from the user as an array of C strings (`argv`) along with a count (`argc`). This allows you to write code for built-ins exactly as you would write them in a regular C program. +The parameters `args` and `numargs` function essentially the same as the `argv` +and `argc` parameters found in the prototypical C `main()` function. Arguments +here are also passed as a NULL-terminated array of C strings. -Errors should be reported to the user using the `note()` function defined in [`utils.c`](../utils.c). +For a consistent user interface, usage messages can be shown with the `usage()` +function, defined in [`builtin.c`](builtin.c), and errors can be explained with +the `note()` function defined in [`utils.c`](../utils.c). Since built-ins are +usually run directly by the shell, calls to functions like `exit()` could cause +the shell itself to terminate, a behavior that isn't typically intended. Errors +should instead be reported by returning an error code from the built-in function +for the shell to handle. -Once the source is done being written, simply rebuild the shell and it will automatically incorporate the new built-in. +Once finished, simply rebuild the shell and it will automatically incorporate +the new built-in. diff --git a/src/builtins/alias.c b/src/builtins/alias.c index 3a1ea4c..62f3786 100644 --- a/src/builtins/alias.c +++ b/src/builtins/alias.c @@ -7,7 +7,7 @@ #include "parse.h" #include "utils.h" -#define MAXALIAS 25 +#define MAXALIAS 50 static struct { struct entry { @@ -35,22 +35,20 @@ char *getaliasvalue(char *name) { char **getalias(char *name) { char *value; size_t l; - static struct context context; + static struct context c; if (!(value = getaliasvalue(name))) return NULL; - while (*value == ' ') ++value; - strcpy(context.buffer, value); + strcpy(c.buffer, value); l = strlen(value); - context.buffer[l + 1] = '\0'; - context.buffer[l] = ';'; - context.alias = 1; - context.b = context.buffer; + c.buffer[l + 1] = '\0'; + c.buffer[l] = ';'; + c.b = c.buffer; + c.alias = 1; - if (!parse(&context)) return NULL; - if (!context.t) context.tokens[0] = NULL; + if (!parse(&c)) return NULL; - return context.tokens; + return c.tokens; } int removealias(char *name) { @@ -59,38 +57,46 @@ int removealias(char *name) { if ((i = getindex(name)) == aliases.size) return 0; entry = &aliases.entries[i]; - memmove(entry, entry + 1, (--aliases.size - i) * sizeof(*entry)); + memmove(entry, entry + 1, (--aliases.size - i) * sizeof*entry); for (; i < aliases.size; ++i, ++entry) - entry->value = (void *)entry->value - sizeof(*entry); + entry->value = (void *)entry->value - sizeof*entry; return 1; } -BUILTIN(alias) { +int alias(char **args, size_t numargs) { size_t i; + char *end; struct entry *entry; - switch (argc) { + switch (numargs) { case 1: for (i = 0; i < aliases.size; ++i) - printf("%s = \"%s\"\n", aliases.entries[i].name, aliases.entries[i].value); + printf("%s -> %s\n", quoted(aliases.entries[i].name), + aliases.entries[i].value); break; case 3: if (aliases.size == MAXALIAS) { - note("Unable to add alias `%s', maximum reached (%d)", argv[1], MAXALIAS); + note("Unable to add alias, maximum reached (%d)", MAXALIAS); return EXIT_FAILURE; } + for (i = 1; i <= 2; ++i) { + end = args[i] + strlen(args[i]); + while (*args[i] == ' ') ++args[i]; + if (end != args[i]) while (*(end - 1) == ' ') --end; + *end = '\0'; + } - entry = &aliases.entries[i = getindex(argv[1])]; + entry = &aliases.entries[i = getindex(args[1])]; if (i == aliases.size) { - strcpy(entry->name, argv[1]); + strcpy(entry->name, args[1]); ++aliases.size; } - strcpy(entry->value = entry->name + strlen(entry->name) + 1, argv[2]); + strcpy(entry->value = entry->name + strlen(entry->name) + 1, args[2]); break; default: - return usage(argv[0], "[name value]"); + return usage(args[0], "[name value]"); } return EXIT_SUCCESS; diff --git a/src/builtins/bg.c b/src/builtins/bg.c index eec6fbd..c9207ed 100644 --- a/src/builtins/bg.c +++ b/src/builtins/bg.c @@ -112,12 +112,12 @@ void deinitbg(void) { for (p = bgjobs.active; p; p = p->next) killpg(p->job.id, SIGKILL); } -BUILTIN(bg) { +int bg(char **args, size_t numargs) { struct bglink *p; struct bgjob job; long l; - switch (argc) { + switch (numargs) { case 1: for (p = bgjobs.active; p; p = p->next) if (p->job.suspended) { job = p->job; @@ -130,7 +130,7 @@ BUILTIN(bg) { break; case 2: errno = 0; - if ((l = strtol(argv[1], NULL, 10)) == LONG_MAX && errno || l <= 0) { + if ((l = strtol(args[1], NULL, 10)) == LONG_MAX && errno || l <= 0) { note("Invalid job id %ld", l); return EXIT_FAILURE; } @@ -144,7 +144,7 @@ BUILTIN(bg) { } break; default: - return usage(argv[0], "[pgid]"); + return usage(args[0], "[pgid]"); } if (killpg(job.id, SIGCONT) == -1) { diff --git a/src/builtins/build.c b/src/builtins/build.c index a032421..40596af 100644 --- a/src/builtins/build.c +++ b/src/builtins/build.c @@ -37,7 +37,7 @@ int main(void) { errx(EXIT_FAILURE, "Unable to add %s built-in, maximum reached (%d)", *src, MAXBUILTINS); if (strcmp(*src, "builtin") != 0 && strcmp(*src, "list") != 0) - dprintf(listfd, "BUILTIN(%s);\n", *src); + dprintf(listfd, "int %s(char **args, size_t numargs);\n", *src); ++src; } if (errno) err(EXIT_FAILURE, "Unable to read from current directory"); diff --git a/src/builtins/builtin.c b/src/builtins/builtin.c index ccb7832..cf2d914 100644 --- a/src/builtins/builtin.c +++ b/src/builtins/builtin.c @@ -6,18 +6,13 @@ #include "list.h" #include "utils.h" -int isbuiltin(char **args) { +int (*getbuiltin(char *name))(char **args, size_t numargs) { struct builtin *builtin; - size_t n; for (builtin = builtins; builtin->func; ++builtin) - if (strcmp(args[0], builtin->name) == 0) { - for (n = 1; args[n]; ++n); - status = builtin->func(n, args); - return 1; - } + if (strcmp(name, builtin->name) == 0) return builtin->func; - return 0; + return NULL; } int usage(char *program, char *options) { diff --git a/src/builtins/builtin.h b/src/builtins/builtin.h index 004f02f..768ba7e 100644 --- a/src/builtins/builtin.h +++ b/src/builtins/builtin.h @@ -1,4 +1,2 @@ -#define BUILTIN(name) int name(int argc, char **argv) - -int isbuiltin(char **args); +int (*getbuiltin(char *name))(char **args, size_t numargs); int usage(char *program, char *options); diff --git a/src/builtins/cd.c b/src/builtins/cd.c index 0d4d856..12451af 100644 --- a/src/builtins/cd.c +++ b/src/builtins/cd.c @@ -6,17 +6,17 @@ #include "builtin.h" #include "utils.h" -BUILTIN(cd) { +int cd(char **args, size_t numargs) { char *path, buffer[PATH_MAX + 1]; size_t l; - switch (argc) { + switch (numargs) { case 1: path = home; break; case 2: - if (!(path = realpath(argv[1], buffer))) { - note(argv[1]); + if (!(path = realpath(args[1], buffer))) { + note(args[1]); return EXIT_FAILURE; } l = strlen(buffer); @@ -24,7 +24,7 @@ BUILTIN(cd) { buffer[l] = '/'; break; default: - return usage(argv[0], "[directory]"); + return usage(args[0], "[directory]"); } if (chdir(path) == -1) { diff --git a/src/builtins/exec.c b/src/builtins/exec.c new file mode 100644 index 0000000..e786c96 --- /dev/null +++ b/src/builtins/exec.c @@ -0,0 +1,44 @@ +#include +#include +#include +#include +#include + +#include "builtin.h" +#include "context.h" +#include "fg.h" +#include "signals.h" +#include "utils.h" +#include "which.h" + +extern char **environ; + +void execute(struct context *c) { + if (sigprocmask(SIG_SETMASK, &childsigmask, NULL) == -1) + note("Unable to unblock TTY signals"); + + if (c->current.builtin) exit(c->current.builtin(c->tokens, c->numtokens)); + execve(c->current.path, c->tokens, environ); + fatal("Couldn't find `%s' command", c->current.name); +} + +int exec(char **args, size_t numargs) { + struct context c; + + if (numargs < 2) return usage(args[0], "command [args ...]"); + + clear(&c); + memcpy(c.tokens, &args[1], (numargs - 1) * sizeof*args); + strcpy(c.current.name, args[1]); + if (!(c.current.builtin = getbuiltin(args[1])) + && !(c.current.path = getpath(c.current.name))) { + note("Couldn't find `%s' command", args[1]); + return EXIT_FAILURE; + } + setconfig(&canonical); + execute(&c); + + /* execute() is guaranteed not to return, this statement just appeases the + * compiler */ + return EXIT_SUCCESS; +} diff --git a/src/builtins/exec.h b/src/builtins/exec.h new file mode 100644 index 0000000..6a30f53 --- /dev/null +++ b/src/builtins/exec.h @@ -0,0 +1 @@ +void execute(struct context *c); diff --git a/src/builtins/exeunt.c b/src/builtins/exeunt.c index ebb0463..877ea34 100644 --- a/src/builtins/exeunt.c +++ b/src/builtins/exeunt.c @@ -3,8 +3,8 @@ #include "builtin.h" #include "utils.h" -BUILTIN(exeunt) { - if (argc != 1) return usage(argv[0], NULL); +int exeunt(char **args, size_t numargs) { + if (numargs != 1) return usage(args[0], NULL); deinit(); exit(EXIT_SUCCESS); diff --git a/src/builtins/fg.c b/src/builtins/fg.c index dd65f23..9af1125 100644 --- a/src/builtins/fg.c +++ b/src/builtins/fg.c @@ -12,6 +12,7 @@ #include "builtin.h" #include "context.h" #include "options.h" +#include "run.h" #include "signals.h" #include "utils.h" @@ -42,7 +43,7 @@ static void sigchldfghandler(int sig) { errno = e; } -static int setconfig(struct termios *mode) { +int setconfig(struct termios *mode) { if (tcsetattr(STDIN_FILENO, TCSANOW, mode) == -1) { note("Unable to configure TTY"); return 0; @@ -86,7 +87,7 @@ int runfg(pid_t id) { /* The handler in `fgaction' is really what reaps the foreground process; the * `sigsuspend()' just blocks the current thread of execution until the - * foreground process has been reaped. */ + * foreground process has been reaped */ fgjob.id = id; setsig(SIGCHLD, &fgaction); while (!fgjob.done) { @@ -113,7 +114,7 @@ int runfg(pid_t id) { status = WSTOPSIG(fgjob.status); job.suspended = 1; if (!pushbg(job)) { - note("Unable to suspend current process, too many background jobs\n" + note("Unable to suspend current job; too many background jobs\n" "(Press any key to continue)"); getchar(); return runfg(id); @@ -127,12 +128,12 @@ void deinitfg(void) { setconfig(&canonical); } -BUILTIN(fg) { +int fg(char **args, size_t numargs) { struct bgjob job; long l; pid_t id; - switch (argc) { + switch (numargs) { case 1: if (!peekbg(&job)) { note("No job to bring into the foreground"); @@ -142,8 +143,8 @@ BUILTIN(fg) { break; case 2: errno = 0; - if ((l = strtol(argv[1], NULL, 10)) == LONG_MAX && errno || l <= 0) { - note("Invalid process group id %ld", l); + if ((l = strtol(args[1], NULL, 10)) == LONG_MAX && errno || l <= 0) { + note("Invalid job id %ld", l); return EXIT_FAILURE; } id = (pid_t)l; @@ -153,7 +154,7 @@ BUILTIN(fg) { } break; default: - return usage(argv[0], "[pgid]"); + return usage(args[0], "[pgid]"); } if (!runfg(id)) return EXIT_FAILURE; diff --git a/src/builtins/fg.h b/src/builtins/fg.h index f1122f9..f1dab38 100644 --- a/src/builtins/fg.h +++ b/src/builtins/fg.h @@ -1,5 +1,6 @@ extern struct termios canonical; +int setconfig(struct termios *mode); void initfg(void); int runfg(pid_t id); void deinitfg(void); diff --git a/src/builtins/list.h b/src/builtins/list.h index 911d534..0c4a403 100644 --- a/src/builtins/list.h +++ b/src/builtins/list.h @@ -1,6 +1,6 @@ struct builtin { char *name; - BUILTIN((*func)); + int (*func)(char **args, size_t numargs); }; extern struct builtin builtins[]; diff --git a/src/builtins/mode.c b/src/builtins/mode.c index 0d9a29d..fcbf67c 100644 --- a/src/builtins/mode.c +++ b/src/builtins/mode.c @@ -6,17 +6,17 @@ #include "context.h" #include "run.h" -BUILTIN(mode) { - switch (argc) { +int mode(char **args, size_t numargs) { + switch (numargs) { case 1: puts(verbose ? "verbose" : "quiet"); break; case 2: - if (strcmp(argv[1], "verbose") == 0) verbose = 1; - else if (strcmp(argv[1], "quiet") == 0) verbose = 0; + if (strcmp(args[1], "verbose") == 0) verbose = 1; + else if (strcmp(args[1], "quiet") == 0) verbose = 0; else default: - return usage(argv[0], "[verbose | quiet]"); + return usage(args[0], "[verbose | quiet]"); } return EXIT_SUCCESS; diff --git a/src/builtins/pwd.c b/src/builtins/pwd.c index bbde98d..c6a6a28 100644 --- a/src/builtins/pwd.c +++ b/src/builtins/pwd.c @@ -7,11 +7,11 @@ #include "builtin.h" #include "utils.h" -BUILTIN(pwd) { +int pwd(char **args, size_t numargs) { char *cwd, buffer[PATH_MAX]; size_t l; - if (argc != 1) return usage(argv[0], NULL); + if (numargs != 1) return usage(args[0], NULL); if (!(cwd = getenv("PWD"))) { if (!(cwd = getcwd(buffer, PATH_MAX))) { diff --git a/src/builtins/set.c b/src/builtins/set.c index f8df49e..56f780d 100644 --- a/src/builtins/set.c +++ b/src/builtins/set.c @@ -3,17 +3,17 @@ #include "builtin.h" #include "utils.h" -BUILTIN(set) { - switch (argc) { +int set(char **args, size_t numargs) { + switch (numargs) { case 3: - if (setenv(argv[1], argv[2], 1) == -1) { - note("Unable to set %s to %s", argv[1], argv[2]); + if (setenv(args[1], args[2], 1) == -1) { + note("Unable to set %s to %s", args[1], args[2]); return EXIT_FAILURE; } case 2: break; default: - return usage(argv[0], "name [value]"); + return usage(args[0], "name [value]"); } return EXIT_SUCCESS; diff --git a/src/builtins/source.c b/src/builtins/source.c index 676c414..7e2890f 100644 --- a/src/builtins/source.c +++ b/src/builtins/source.c @@ -6,29 +6,31 @@ #include "builtin.h" #include "context.h" #include "input.h" +#include "options.h" #include "run.h" #include "utils.h" -BUILTIN(source) { - struct context context; - int c; - char **v; +int source(char **args, size_t numargs) { + struct context c; + char **vector; + size_t count; - if (argc == 1) return usage(argv[0], "file [args ...]"); + if (numargs < 2) return usage(args[0], "file [args ...]"); - context = (struct context){0}; - context.script = argv[1]; - context.input = scriptinput; + c = (struct context){.script = args[1], .input = scriptinput}; - c = argcount; - v = arglist; - argcount = argc - 1; - arglist = argv + 1; + /* See comment in `src/options.c' */ + args[1] = argvector[0]; - while (run(&context)); + vector = argvector; + count = argcount; + argvector = args + 1; + argcount = numargs - 1; - argcount = c; - arglist = v; + while (run(&c)); + + argvector = vector; + argcount = count; return EXIT_SUCCESS; } @@ -48,5 +50,5 @@ void config(char *name) { return; } - source(2, (char *[]){"source", path, NULL}); + source((char *[]){"source", path, NULL}, 2); } diff --git a/src/builtins/unalias.c b/src/builtins/unalias.c index d906b8e..55581ba 100644 --- a/src/builtins/unalias.c +++ b/src/builtins/unalias.c @@ -3,8 +3,8 @@ #include "alias.h" #include "builtin.h" -BUILTIN(unalias) { - if (argc != 2) return usage(argv[0], "name"); +int unalias(char **args, size_t numargs) { + if (numargs != 2) return usage(args[0], "name"); - return removealias(argv[1]) ? EXIT_SUCCESS : EXIT_FAILURE; + return removealias(args[1]) ? EXIT_SUCCESS : EXIT_FAILURE; } diff --git a/src/builtins/unset.c b/src/builtins/unset.c index d7a5679..af62aeb 100644 --- a/src/builtins/unset.c +++ b/src/builtins/unset.c @@ -3,11 +3,11 @@ #include "builtin.h" #include "utils.h" -BUILTIN(unset) { - if (argc != 2) return usage(argv[0], "name"); +int unset(char **args, size_t numargs) { + if (numargs != 2) return usage(args[0], "name"); - if (unsetenv(argv[1]) != -1) return EXIT_SUCCESS; + if (unsetenv(args[1]) != -1) return EXIT_SUCCESS; - note("Unable to unset $%s$", argv[1]); + note("Unable to unset $%s$", args[1]); return EXIT_FAILURE; } diff --git a/src/builtins/which.c b/src/builtins/which.c index 841ca60..234a20c 100644 --- a/src/builtins/which.c +++ b/src/builtins/which.c @@ -7,17 +7,8 @@ #include "alias.h" #include "builtin.h" -#include "list.h" #include "utils.h" -enum { - ALIAS, - BUILTIN, - PATH, -}; - -static int type; - static int exists(char *path) { struct stat pstat; mode_t mask; @@ -33,23 +24,10 @@ static int exists(char *path) { char *getpath(char *file) { char *slash, *entry, *end, dir[PATH_MAX]; - struct builtin *builtin; size_t l; static char path[PATH_MAX]; - type = PATH; if (!(slash = strchr(file, '/'))) { - if ((entry = getaliasvalue(file))) { - type = ALIAS; - return entry; - } - - for (builtin = builtins; builtin->func; ++builtin) - if (strcmp(file, builtin->name) == 0) { - type = BUILTIN; - return file; - } - if (!(entry = getenv("PATH"))) { note("Unable to examine $PATH$"); return NULL; @@ -73,19 +51,18 @@ char *getpath(char *file) { return NULL; } -BUILTIN(which) { - char *result; +int which(char **args, size_t numargs) { + char *p; - if (argc != 2) return usage(argv[0], "name"); + if (numargs != 2) return usage(args[0], "name"); - if (!(result = getpath(argv[1]))) { - printf("%s not found\n", argv[1]); - return EXIT_SUCCESS; + if ((p = getaliasvalue(args[1]))) puts(p); + else if (getbuiltin(p = args[1])) printf("%s is a built-in\n", p); + else if ((p = getpath(args[1]))) puts(quoted(p)); + else { + printf("%s not found\n", quoted(args[1])); + return EXIT_FAILURE; } - - fputs(result, stdout); - if (type == BUILTIN) fputs(" is built-in", stdout); - putchar('\n'); return EXIT_SUCCESS; } diff --git a/src/context.c b/src/context.c index 372bf50..276458b 100644 --- a/src/context.c +++ b/src/context.c @@ -8,7 +8,7 @@ int clear(struct context *c) { c->t = NULL; c->r = NULL; c->current.name[0] = c->buffer[0] = '\0'; - c->prev.term = c->current.term = SEMI; + c->previous.term = c->current.term = SEMI; return 1; } diff --git a/src/context.h b/src/context.h index d69b98b..1e42574 100644 --- a/src/context.h +++ b/src/context.h @@ -2,39 +2,37 @@ #define MAXCOMMANDS (MAXCHARS + 1) / 2 #define MAXREDIRECTS (MAXCHARS / 3) -enum { - NONE, - READ = '<', - READWRITE, - WRITE = '>', - APPEND, -}; - struct redirect { - int mode, oldfd, newfd; + enum { + NONE, + READ = '<', + READWRITE, + WRITE = '>', + APPEND, + } mode; + int oldfd, newfd; char *oldname; }; -enum { - SEMI, - BG = '&', - AND, - PIPE = '|', - OR, -}; - struct command { - char name[MAXCHARS + 1]; - int term, pipe[2]; + char name[MAXCHARS + 1], *path; + int (*builtin)(char **args, size_t numargs), pipe[2]; + enum { + SEMI, + BG = '&', + AND, + PIPE = '|', + OR, + } term; }; struct context { char *string, *script, *map, buffer[MAXCHARS + 1 + 1], *b, *tokens[MAXCOMMANDS + 1], **t; - size_t maplen; + size_t maplen, numtokens; int (*input)(struct context *c), alias; struct redirect redirects[MAXREDIRECTS + 1], *r; - struct command current, prev; + struct command current, previous; }; int clear(struct context *c); diff --git a/src/input.c b/src/input.c index 0e5e906..e839978 100644 --- a/src/input.c +++ b/src/input.c @@ -10,28 +10,12 @@ #include "context.h" #include "history.h" +#include "options.h" #include "signals.h" #include "utils.h" #define OFFSET(x) ((promptlen + (x) - start) % window.ws_col) -enum { - CTRLD = '\004', - CLEAR = '\014', - ESCAPE = '\033', - - /* See `ESCAPE' case in `userinput()' */ - ALT = '2' + 1, - - UP = 'A', - DOWN, - RIGHT, - LEFT, - FORWARD = 'f', - BACKWARD = 'b', - DEL = '\177', -}; - static struct winsize window; static char *start, *cursor, *end; static size_t promptlen; @@ -94,8 +78,8 @@ int scriptinput(struct context *c) { c->input = stringinput; /* We want errors from this point forward to be reported by the script, not the - * shell. */ - arglist[0] = c->script; + * shell */ + argvector[0] = c->script; return c->input(c); } @@ -138,6 +122,23 @@ static void newline(void) { } int userinput(struct context *c) { + enum { + CTRLD = '\004', + CLEAR = '\014', + ESCAPE = '\033', + + /* See `ESCAPE' case */ + ALT = '2' + 1, + + UP = 'A', + DOWN, + RIGHT, + LEFT, + FORWARD = 'f', + BACKWARD = 'b', + DEL = '\177', + }; + int current; char *oldcursor, *oldend; diff --git a/src/main.c b/src/main.c index d7a8ffc..09d058c 100644 --- a/src/main.c +++ b/src/main.c @@ -7,20 +7,18 @@ #include "utils.h" int main(int argc, char **argv) { - struct context context; + struct context c; - argcount = argc; - arglist = argv; - context = (struct context){0}; + c = (struct context){0}; - options(&context); + options(argc, argv, &c); init(); if (login) config(".thuslogin"); if (interactive) config(".thusrc"); - while (run(&context)); + while (run(&c)); deinit(); diff --git a/src/options.c b/src/options.c index 44b8035..ea305e2 100644 --- a/src/options.c +++ b/src/options.c @@ -7,9 +7,11 @@ #include "input.h" #include "utils.h" +char **argvector; int login, interactive; +size_t argcount; -void options(struct context *context) { +void options(int argc, char **argv, struct context *c) { char *p, *message = "[file | -c string] [arg ...] [-hl]\n" " [arg ...] Run script\n" " -c [arg ...] Run string\n" @@ -17,48 +19,51 @@ void options(struct context *context) { " -l Run as a login shell"; int opt; - if (arglist[0][0] == '-') { - ++arglist[0]; + argvector = argv; + + if (argvector[0][0] == '-') { + ++argvector[0]; login = 1; } - if ((p = strrchr(arglist[0], '/'))) arglist[0] = p + 1; + if ((p = strrchr(argvector[0], '/'))) argvector[0] = p + 1; interactive = 1; - context->input = userinput; + c->input = userinput; opt = 0; - while (opt != 'c' && (opt = getopt(argcount, arglist, ":c:hl")) != -1) + argcount = argc; + while (opt != 'c' && (opt = getopt(argcount, argvector, ":c:hl")) != -1) switch (opt) { case 'c': interactive = 0; - context->string = optarg; - context->input = stringinput; - arglist[--optind] = arglist[0]; + c->string = optarg; + c->input = stringinput; + argvector[--optind] = argvector[0]; break; case 'h': - usage(arglist[0], message); + usage(argvector[0], message); exit(EXIT_SUCCESS); case 'l': login = 1; break; case ':': note("Expected argument following `-%c'\n", optopt); - exit(usage(arglist[0], message)); + exit(usage(argvector[0], message)); case '?': default: note("Unknown command line option `-%c'\n", optopt); - exit(usage(arglist[0], message)); + exit(usage(argvector[0], message)); } - if (!context->string && arglist[optind]) { + if (!c->string && argvector[optind]) { interactive = 0; - context->script = arglist[optind]; - context->input = scriptinput; + c->script = argvector[optind]; + c->input = scriptinput; /* Until we're able to load the script, errors need to be reported by the - * shell, not the script. */ - arglist[optind] = arglist[0]; + * shell, not the script */ + argvector[optind] = argvector[0]; } if (!interactive) { argcount -= optind; - arglist += optind; + argvector += optind; } } diff --git a/src/options.h b/src/options.h index 825670a..a14e372 100644 --- a/src/options.h +++ b/src/options.h @@ -1,3 +1,5 @@ +extern char **argvector; extern int login, interactive; +extern size_t argcount; -void options(struct context *context); +void options(int argc, char **argv, struct context *c); diff --git a/src/parse.c b/src/parse.c index 695246c..0ee28c4 100644 --- a/src/parse.c +++ b/src/parse.c @@ -7,12 +7,31 @@ #include "alias.h" #include "context.h" +#include "options.h" +#include "run.h" #include "utils.h" +static void sub(struct context *c, char **tokens, size_t numtokens) { + memcpy(c->t, tokens, numtokens * sizeof*c->t); + c->t += numtokens - 1; +} + +static int subalias(struct context *c) { + char **tokens; + size_t numtokens; + + if (c->alias || c->t != c->tokens || !(tokens = getalias(c->tokens[0]))) return 0; + + for (numtokens = 0; tokens[numtokens]; ++numtokens); + sub(c, tokens, numtokens); + + return 1; +} + int parse(struct context *c) { - int globbing, quote, v, offset, globflags; - size_t prevsublen, sublen; - char *stlend, *p, *end, *var, term, **sub; + char *end, *stlend, *p, *var, term; + int quote, globbing, v, offset, globflags; + size_t prevnumglobs; long l; static glob_t globs; @@ -23,11 +42,10 @@ int parse(struct context *c) { c->t = c->tokens; c->r = c->redirects; c->r->mode = NONE; - c->prev = c->current; + c->previous = c->current; for (end = c->b; *end; ++end); - prevsublen = globbing = quote = 0; - sub = NULL; + prevnumglobs = globbing = quote = 0; if (globs.gl_pathc) { globfree(&globs); globs.gl_pathc = 0; @@ -62,7 +80,8 @@ int parse(struct context *c) { l = strtol(p + 1, &stlend, 10); errno = 0; - if (stlend == c->b - 1) var = l >= 0 && l < argcount ? arglist[l] : c->b - 1; + if (stlend == c->b - 1) + var = l >= 0 && l < argcount ? argvector[l] : c->b - 1; else if (strcmp(p + 1, "^") == 0) { if (!sprintf(var = (char [12]){0}, "%d", status)) { note("Unable to get previous command status"); @@ -99,12 +118,14 @@ int parse(struct context *c) { } *c->b = '\0'; + subalias(c); ++c->t; *c->t = c->b + 1; break; case '"': *c->b = '\0'; + if (quote) subalias(c); if (quote || *c->t != c->b) ++c->t; *c->t = c->b + 1; @@ -115,6 +136,7 @@ int parse(struct context *c) { if (!quote) break; switch (*(c->b + 1)) { case '$': + case '~': case '"': case '\\': break; @@ -169,34 +191,28 @@ int parse(struct context *c) { } if (*c->t != c->b) { - if (!c->alias && c->t == c->tokens && (sub = getalias(c->tokens[0]))) - for (sublen = 0; sub[sublen]; ++sublen); - else if (globbing) { + if (!subalias(c) && globbing) { globflags = GLOB_MARK; - if (prevsublen) globflags |= GLOB_APPEND; + if (prevnumglobs) globflags |= GLOB_APPEND; switch (glob(*c->t, globflags, NULL, &globs)) { case GLOB_NOMATCH: - note("No matches found for %s", *c->t); + note("No matches found for `%s'", *c->t); return quit(c); case GLOB_NOSPACE: fatal("Memory allocation"); } - sublen = globs.gl_pathc - prevsublen; - sub = globs.gl_pathv + prevsublen; - prevsublen = globs.gl_pathc; + globbing = 0; - } - if (sub) { - memcpy(c->t, sub, sublen * sizeof*c->t); - c->t += sublen - 1; - sub = NULL; + sub(c, globs.gl_pathv + prevnumglobs, globs.gl_pathc - prevnumglobs); + prevnumglobs = globs.gl_pathc; } ++c->t; } if (term != ' ') { + *c->t = NULL; if (c->t != c->tokens) { - *c->t = NULL; + c->numtokens = c->t - c->tokens; strcpy(c->current.name, c->tokens[0]); } else c->t = NULL; if (c->r == c->redirects) c->r = NULL; @@ -225,9 +241,5 @@ int parse(struct context *c) { return quit(c); } - if (c->t == c->tokens) c->t = NULL; - if (c->r == c->redirects) c->r = NULL; - c->b = NULL; - - return 1; + return clear(c); } diff --git a/src/run.c b/src/run.c index 5ab8130..0255cda 100644 --- a/src/run.c +++ b/src/run.c @@ -10,22 +10,21 @@ #include "bg.h" #include "builtin.h" #include "context.h" +#include "exec.h" #include "fg.h" #include "parse.h" #include "signals.h" #include "utils.h" #include "which.h" -extern char **environ; +int verbose, status; -int verbose; - -static int closepipe(struct command c) { +static int closepipe(struct command command) { int result; - result = close(c.pipe[0]) == 0; - result &= close(c.pipe[1]) == 0; - if (!result) note("Unable to close `%s' pipe", c.name); + result = close(command.pipe[0]) == 0; + result &= close(command.pipe[1]) == 0; + if (!result) note("Unable to close `%s' pipe", command.name); return result; } @@ -55,25 +54,13 @@ static void redirectfiles(struct redirect *r) { r->oldfd = fd; } if (dup2(r->oldfd, r->newfd) == -1) - fatal("Unable to redirect %d to %d", r->newfd, r->oldfd); + fatal("Unable to redirect file descriptor %d to %d", r->newfd, r->oldfd); if (r->oldname && close(r->oldfd) == -1) fatal("Unable to close `%s'", r->oldname); } } -static void exec(char *path, struct context *c) { - redirectfiles(c->redirects); - - if (sigprocmask(SIG_SETMASK, &childsigmask, NULL) == -1) - note("Unable to unblock TTY signals"); - - if (isbuiltin(c->tokens)) exit(status); - execve(path, c->tokens, environ); - fatal("Couldn't find `%s' command", c->current.name); -} - int run(struct context *c) { - char *p; int islist, ispipe, ispipestart, ispipeend; pid_t cpid, jobid; static pid_t pipeid; @@ -85,10 +72,8 @@ int run(struct context *c) { if (verbose && (c->t || c->r)) { if (c->t) { for (c->t = c->tokens; *c->t; ++c->t) { - for (p = *c->t; *p && *p != ' '; ++p); - p = p == *c->t || *p == ' ' ? "'" : ""; if (c->t != c->tokens) putchar(' '); - printf("%s%s%s", p, *c->t, p); + fputs(quoted(*c->t), stdout); } if (c->r) putchar(' '); } @@ -98,30 +83,39 @@ int run(struct context *c) { if (c->r->oldname) printf("%s", c->r->oldname); else printf("&%d", c->r->oldfd); } - if (c->current.term == BG) putchar('&'); - fputs(c->current.term == PIPE ? " | " : "\n", stdout); + switch (c->current.term) { + case PIPE: + fputs(" | ", stdout); + fflush(stdout); + break; + case BG: + putchar('&'); + default: + putchar('\n'); + } } - islist = c->prev.term > BG || c->current.term > BG; + islist = c->previous.term > BG || c->current.term > BG; if (c->t) { if (c->current.term == BG && bgfull()) { - note("Unable to place job in background, too many background jobs"); + note("Unable to place job in background; too many background jobs"); return quit(c); } - if (!(p = getpath(c->current.name))) { + if (!(c->current.builtin = getbuiltin(c->current.name)) + && !(c->current.path = getpath(c->current.name))) { note("Couldn't find `%s' command", c->current.name); - if (c->prev.term == PIPE) killpg(pipeid, SIGKILL); + if (c->previous.term == PIPE) killpg(pipeid, SIGKILL); return quit(c); } - ispipe = c->prev.term == PIPE || c->current.term == PIPE; - ispipestart = ispipe && c->prev.term != PIPE; + ispipe = c->previous.term == PIPE || c->current.term == PIPE; + ispipestart = ispipe && c->previous.term != PIPE; ispipeend = ispipe && c->current.term != PIPE; if (ispipe) { if (!ispipeend && pipe(c->current.pipe) == -1) { note("Unable to create pipe"); - if (!ispipestart) closepipe(c->prev); + if (!ispipestart) closepipe(c->previous); return quit(c); } if ((cpid = fork()) == -1) { @@ -129,28 +123,34 @@ int run(struct context *c) { return quit(c); } else if (cpid == 0) { if (!ispipestart) { - if (dup2(c->prev.pipe[0], 0) == -1) - fatal("Unable to duplicate read end of `%s' pipe", c->prev.name); - if (!closepipe(c->prev)) exit(EXIT_FAILURE); + if (dup2(c->previous.pipe[0], 0) == -1) + fatal("Unable to duplicate read end of `%s' pipe", c->previous.name); + if (!closepipe(c->previous)) exit(EXIT_FAILURE); } if (!ispipeend) { if (dup2(c->current.pipe[1], 1) == -1) fatal("Unable to duplicate write end of `%s' pipe", c->current.name); if (!closepipe(c->current)) exit(EXIT_FAILURE); } - exec(p, c); + redirectfiles(c->redirects); + execute(c); } if (ispipestart) pipeid = cpid; - else if (!closepipe(c->prev)) { + else if (!closepipe(c->previous)) { killpg(pipeid, SIGKILL); return quit(c); } jobid = pipeid; - } else if (!c->r && isbuiltin(c->tokens)) cpid = 0; - else if ((jobid = cpid = fork()) == -1) { + } else if (!c->r && (c->current.builtin = getbuiltin(c->current.name))) { + status = c->current.builtin(c->tokens, c->numtokens); + cpid = 0; + } else if ((jobid = cpid = fork()) == -1) { note("Unable to fork child process"); return quit(c); - } else if (cpid == 0) exec(p, c); + } else if (cpid == 0) { + redirectfiles(c->redirects); + execute(c); + } if (cpid) { if (setpgid(cpid, jobid) == -1) { @@ -174,7 +174,7 @@ int run(struct context *c) { } else if (c->current.term == OR) return clear(c); } else { if (islist) { - if (c->prev.term == PIPE) { + if (c->previous.term == PIPE) { killpg(pipeid, SIGKILL); if (verbose) putchar('\n'); } diff --git a/src/run.h b/src/run.h index 9c5cbb8..810bc55 100644 --- a/src/run.h +++ b/src/run.h @@ -1,3 +1,3 @@ -extern int verbose; +extern int verbose, status; int run(struct context *c); diff --git a/src/utils.c b/src/utils.c index 3d01af2..cc3dc8e 100644 --- a/src/utils.c +++ b/src/utils.c @@ -13,15 +13,15 @@ #include "fg.h" #include "history.h" #include "input.h" +#include "options.h" #include "signals.h" -int argcount, status; -char **arglist, *home; +char *home; void note(char *fmt, ...) { va_list args; - fprintf(stderr, "%s: ", arglist[0]); + fprintf(stderr, "%s: ", argvector[0]); va_start(args, fmt); vfprintf(stderr, fmt, args); @@ -38,7 +38,7 @@ void note(char *fmt, ...) { void fatal(char *fmt, ...) { va_list args; - fprintf(stderr, "%s: ", arglist[0]); + fprintf(stderr, "%s: ", argvector[0]); va_start(args, fmt); vfprintf(stderr, fmt, args); @@ -110,6 +110,64 @@ char *catpath(char *dir, char *filename, char *buffer) { return buffer; } +char *quoted(char *token) { + char *p, *q, quote; + enum { + NONE, + DOUBLE, + SINGLE, + ESCAPEDOUBLE, + } degree; + static char buffer[MAXCHARS + 1]; + + if (!*token) return "\"\""; + + degree = NONE; + for (p = token; *p; ++p) switch(*p) { + case '[': + for (q = p; *q; ++q) if (*q == ']') break; + if (!*q) continue; + case '>': + case '<': + case '*': + case '?': + case '#': + case '&': + case '|': + case ';': + case ' ': + if (degree < DOUBLE) degree = DOUBLE; + break; + case '$': + case '~': + case '"': + if (degree < SINGLE) degree = SINGLE; + break; + case '\'': + degree |= DOUBLE; + } + if (degree == NONE) return token; + + quote = degree == SINGLE ? '\'' : '"'; + p = buffer; + *p++ = quote; + strcpy(p, token); + for (q = p; *q; ++q); + if (degree & DOUBLE) for (; *p; ++p) switch (*p) + case '$': + case '~': + case '"': + if (degree == ESCAPEDOUBLE) { + case '\\': + memmove(p + 1, p, ++q - p); + *p++ = '\\'; + } + *p++ = quote; + *p++ = '\0'; + + return buffer; +} + void deinit(void) { deinithistory(); deinitbg(); diff --git a/src/utils.h b/src/utils.h index 4302a00..e7b8a95 100644 --- a/src/utils.h +++ b/src/utils.h @@ -1,8 +1,8 @@ -extern int argcount, status; -extern char **arglist, *home; +extern char *home; void note(char *fmt, ...); void fatal(char *fmt, ...); void init(void); char *catpath(char *dir, char *filename, char *buffer); +char *quoted(char *token); void deinit(void); diff --git a/tools/install.c b/tools/install.c index e90ce07..883514c 100644 --- a/tools/install.c +++ b/tools/install.c @@ -3,14 +3,14 @@ #include "cbs.c" int main(int argc, char **argv) { - char define[7 + 1 + PATH_MAX + 1], *path; + char define[7 + PATH_MAX], *path; size_t l; int slash; pid_t cpid; if (argc > 2) errx(EXIT_FAILURE, "usage: %s [prefix]", argv[0]); - path = stpcpy(define, "-DPATH=\""); + path = stpcpy(define, "-DPATH="); if (argc == 2) { l = strlen(argv[1]); slash = argv[1][l - 1] == '/'; @@ -33,9 +33,6 @@ int main(int argc, char **argv) { run("/bin/cp", LIST("cp", "-f", "bin/thus", path), "copy", "bin/thus"); await(cpid, "copy", "bin/thus"); - l = strlen(define); - define[l] = '\"'; - define[l + 1] = '\0'; cflags = LIST(define, "-Iexternal/cbs"); compile("tools/uninstall"); load('x', "uninstall", LIST("tools/uninstall")); diff --git a/tools/uninstall.c b/tools/uninstall.c index 15ba8e4..cca930f 100644 --- a/tools/uninstall.c +++ b/tools/uninstall.c @@ -2,6 +2,10 @@ #include "cbs.c" +/* C preprocessor being finicky */ +#define STR(x) STRINGIFY(x) +#define STRINGIFY(x) #x + int main(int argc, char **argv) { pid_t cpid; @@ -9,8 +13,8 @@ int main(int argc, char **argv) { if ((cpid = fork()) == -1) err(EXIT_FAILURE, "Unable to fork"); else if (cpid == 0) - run("/bin/rm", LIST("rm", PATH, "uninstall"), "remove", PATH); - await(cpid, "remove", PATH); + run("/bin/rm", LIST("rm", STR(PATH), "uninstall"), "remove", STR(PATH)); + await(cpid, "remove", STR(PATH)); return EXIT_SUCCESS; } -- 2.51.0