## Example configuration
Here is a minimal example which assumes sh as the user's default shell (a
-similar approach can be used for other shells). We'll need to modify the scripts
+similar approach is feasible for other shells). We'll need to modify the scripts
that are executed at login and during an interactive session. For sh, the login
configuration file is `.profile`.
MIT License
-Copyright (c) 2025 Trent Huber
+Copyright (c) 2026 Trent Huber
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
- Job control (`fg`, `bg`, `&`, `^Z`)
- Pipelines
-- Conditional execution (`&&`, `||`)
- File redirection (`<file`, `2>&1`, etc.)
- Globbing (`*`, `?`, `[...]`)
-- Quoting (`'...', "..."`)
+- Quotes (`'...', "..."`)
- Variables (`set`, `unset`, `$VAR$`, etc.)
- Aliasing (`alias`, `unalias`)
- Configuration files (`~.thuslogin`, `~.thusrc`)
While thus operates for the most part like Bourne shell, there are a few places
where it takes a subtly different approach.
+### Sequential command execution
+
+Most other shells by default will execute all the commands in a line regardless
+of the previous one's exit status. Typically, in order to control whether a
+command runs based on the exit status of the last, these shells provide list
+operators like `&&` and `||`. Neither of the these operators are included in
+this shell. Instead, the `&&` operator is the default behavior between commands,
+while the `||` operator is forgone entirely. If any command fails, the whole
+string of commands fail whether from a script or from an interactive session.
+
### Quotes
-Like most other shells, variables, tildes, and escape sequences will be expanded
+Like other shells, variables, tildes, and escape sequences will be expanded
inside of double quotes, but not single quotes. *Unlike* other shells however,
quotes do not concatenate with other arguments that are not separated from the
quote by whitespace. For example, the command `echo "foo"bar` would print
-`foo bar` whereas other shells would combine them into a single argument and
-print `foobar`.
+`foo bar` whereas other shells would print `foobar`.
### Variables and aliases
-Variables are referred to by strings of characters that begin and end with a
+Variables are referred to by strings of characters that begin *and end* with a
`$`. For example, evaluating the path variable would look like `$PATH$`. Setting
-variables is done with the `set` built-in command, not with the `name=value`
+variables is done with the `set` built-in command, not with a `name=value`
syntax. This syntax is similarly avoided when declaring aliases with the `alias`
built-in command.
If there is whitespace between a file redirection operator and a filename
following it, then it is *not* parsed as a file redirection, but instead as two
separate arguments. Something like `ls >file` would redirect the output of the
-`ls` command to `file`, whereas `ls > file` would list any files named `>` and
-`file`.
+`ls` command to `file`, whereas `ls > file` would list any files that might be
+named `>` or `file`.
## Resources
- [TTY Demystified](http://www.linusakesson.net/programming/tty/)
- [Process Groups and Terminal Signaling](
https://cs162.org/static/readings/ic221_s16_lec17.html)
+
+## References
+
- [Terminal Input Sequences](
https://en.wikipedia.org/wiki/ANSI_escape_code#Terminal_input_sequences)
+- [C Preprocessor Stringification](
+https://gcc.gnu.org/onlinedocs/gcc-6.5.0/cpp/Stringification.html)
-Subproject commit 8ca54de4dab957a5cb5889c9b84ec41a00840e10
+Subproject commit a2755058e34465134d49bf8b75ec2ea939158418
#include "../external/cbs/cbs.c"
-#define SRC1 "context", "history", "input", "signals"
-#define SRC2 "main", "options", "parse", "run", "utils"
+#define SRC1 "context", "history", "input", "options", "signals"
+#define SRC2 "main", "parse", "run", "utils"
int main(void) {
char **src;
#include <stdlib.h>
-#include "builtin.h"
#include "utils.h"
int foo(char **args, size_t numargs) {
here are also passed as a NULL-terminated array of C strings.
For a consistent user interface, usage messages can be shown with the `usage()`
-function, defined in [`src/builtins/builtin.c`](builtin.c), and errors can be
-explained with the `note()` function defined in [`src/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 finished, simply rebuild the shell and it will automatically incorporate
-the new built-in.
+function and errors can be explained with the `note()` or `fatal()` functions,
+all of which are defined in [`src/utils.c`](../utils.c). It should be noted that
+calling functions like `fatal()` or `exit()` will cause the *shell itself* to
+terminate since these functions are run directly inside the shell, not in a
+separate process. The value returned from the built-in's function will be used
+as the exit status for that built-in in the shell. Of course, the shell comes
+with several examples.
+
+Because this is running in the same environment as the shell, certain aspects of
+signals are configured differently than would normally be the case. The signals
+SIGTSTP, SIGTTIN, and SIGTTOU are blocked in the signal mask, and the signals
+SIGHUP, SIGINT, SIGQUIT, SIGTERM, and SIGWINCH have custom handlers defined in
+[`src/signals.c`](../signals.c).
+
+Once finished writing the function, simply rebuild the shell and it will
+automatically incorporate it as a new built-in.
#include <stdlib.h>
#include <string.h>
-#include "builtin.h"
#include "context.h"
#include "parse.h"
#include "utils.h"
#define MAXALIAS 50
-static struct {
- struct entry {
- char name[MAXCHARS - 5], *value;
- } entries[MAXALIAS + 1];
- size_t size;
-} aliases;
+static struct alias {
+ char name[MAXCHARS - 5], *value;
+} aliases[MAXALIAS + 1];
+static size_t numaliases;
static size_t getindex(char *name) {
size_t i;
- for (i = 0; i < aliases.size; ++i)
- if (strcmp(aliases.entries[i].name, name) == 0) break;
+ for (i = 0; i < numaliases; ++i) if (strcmp(aliases[i].name, name) == 0) break;
return i;
}
char *getalias(char *name) {
size_t i;
- if ((i = getindex(name)) == aliases.size) return NULL;
+ if ((i = getindex(name)) == numaliases) return NULL;
- return aliases.entries[i].value;
+ return aliases[i].value;
}
char **parsealias(char *value) {
int removealias(char *name) {
size_t i;
- struct entry *entry;
+ struct alias *a;
- if ((i = getindex(name)) == aliases.size) return 0;
+ if ((i = getindex(name)) == numaliases) return 0;
- entry = aliases.entries + i;
- memmove(entry, entry + 1, (--aliases.size - i) * sizeof*entry);
- for (; i < aliases.size; ++i, ++entry)
- entry->value = (void *)entry->value - sizeof*entry;
+ a = aliases + i;
+ memmove(a, a + 1, (--numaliases - i) * sizeof*a);
+ for (; i < numaliases; ++i, ++a) a->value = (void *)a->value - sizeof*a;
return 1;
}
int alias(char **args, size_t numargs) {
size_t i;
char *end;
- struct entry *entry;
+ struct alias *a;
switch (numargs) {
case 1:
- for (i = 0; i < aliases.size; ++i)
- printf("%s = %s\n", quoted(aliases.entries[i].name),
- aliases.entries[i].value);
+ for (i = 0; i < numaliases; ++i)
+ printf("%s = %s\n", quoted(aliases[i].name), aliases[i].value);
break;
case 3:
- if (aliases.size == MAXALIAS) {
- note("Unable to add `%s' alias, maximum reached (%d)", args[1], MAXALIAS);
- return EXIT_FAILURE;
- }
+ if (numaliases == MAXALIAS)
+ return note("Unable to add `%s' alias, maximum reached (%d)",
+ args[1], MAXALIAS);
if (!parsealias(args[2])) return EXIT_FAILURE;
for (i = 1; i <= 2; ++i) {
*end = '\0';
}
- entry = aliases.entries + (i = getindex(args[1]));
- if (i == aliases.size) {
- strcpy(entry->name, args[1]);
- ++aliases.size;
+ a = aliases + (i = getindex(args[1]));
+ if (i == numaliases) {
+ strcpy(a->name, args[1]);
+ ++numaliases;
}
- strcpy(entry->value = entry->name + strlen(entry->name) + 1, args[2]);
+ strcpy(a->value = a->name + strlen(a->name) + 1, args[2]);
break;
default:
#include <termios.h>
#include "bg.h"
-#include "builtin.h"
-#include "fg.h"
#include "utils.h"
-#define MAXBG 100
-
-static struct {
- struct bglink {
- struct bgjob job;
- struct bglink *next;
- } entries[MAXBG], *active, *free;
-} bgjobs;
-struct sigaction bgaction;
-
-void removebg(pid_t id) {
- struct bglink *p, *prev;
-
- for (prev = NULL, p = bgjobs.active; p; prev = p, p = p->next)
- if (p->job.id == id) break;
- if (!p) return;
-
- if (prev) prev->next = p->next; else bgjobs.active = p->next;
- p->next = bgjobs.free;
- bgjobs.free = p;
+#define MAXBGS 100
+
+static struct bgjob {
+ struct job job;
+ struct bgjob *previous, *next;
+} bgjobs[MAXBGS], *used, *available;
+
+void initbg(void) {
+ size_t i;
+
+ for (i = 0; i < MAXBGS - 1; ++i) {
+ bgjobs[i + 1].previous = &bgjobs[i];
+ bgjobs[i].next = &bgjobs[i + 1];
+ }
+ available = bgjobs;
}
-void sigchldbghandler(int sig) {
+void removebg(struct job *j) {
+ struct bgjob *b;
+
+ b = (struct bgjob *)j;
+
+ if (b->next) b->next->previous = b->previous;
+ if (b->previous) {
+ b->previous->next = b->next;
+ b->previous = NULL;
+ } else used = b->next;
+ if ((b->next = available)) available->previous = b;
+ available = b;
+}
+
+void sigchldbghandler(int signal) {
int e, s;
- struct bglink *p;
- pid_t id;
+ struct bgjob *b, *p;
+ pid_t cpid;
- (void)sig;
+ (void)signal;
e = errno;
- p = bgjobs.active;
- while (p) {
- while ((id = waitpid(-p->job.id, &s, WNOHANG | WUNTRACED)) > 0)
+ b = used;
+ while (b) {
+ while ((cpid = waitpid(-b->job.id, &s, WNOHANG | WUNTRACED)) > 0)
if (WIFSTOPPED(s)) {
- p->job.suspended = 1;
+ b->job.suspended = 1;
break;
}
- if (id == -1) {
- id = p->job.id;
- p = p->next;
- removebg(id);
- } else p = p->next;
+ if (cpid == -1) p = b;
+ b = b->next;
+ if (cpid == -1) removebg(&p->job);
}
errno = e;
}
-void initbg(void) {
- size_t i;
-
- bgaction = (struct sigaction){.sa_handler = sigchldbghandler};
-
- for (i = 0; i < MAXBG - 1; ++i)
- bgjobs.entries[i].next = bgjobs.entries + i + 1;
- bgjobs.free = bgjobs.entries;
-}
-
int bgfull(void) {
- return !bgjobs.free;
+ return !available;
}
-int pushbg(struct bgjob job) {
- struct bglink *p;
-
- if (bgfull()) return 0;
-
- (p = bgjobs.free)->job = job;
- bgjobs.free = p->next;
- p->next = bgjobs.active;
- bgjobs.active = p;
+void pushbg(struct job job) {
+ struct bgjob *b;
- return 1;
+ (b = available)->job = job;
+ if ((available = b->next)) available->previous = NULL;
+ if ((b->next = used)) used->previous = b;
+ used = b;
}
-int pushbgid(pid_t id) {
- return pushbg((struct bgjob){.id = id, .config = canonical});
+struct job *peekbg(void) {
+ return used ? &used->job : NULL;
}
-int peekbg(struct bgjob *job) {
- if (bgjobs.active && job) *job = bgjobs.active->job;
- return bgjobs.active != NULL;
-}
-
-int searchbg(pid_t id, struct bgjob *job) {
- struct bglink *p;
+struct job *searchbg(pid_t id) {
+ struct bgjob *b;
- for (p = bgjobs.active; p; p = p->next) if (p->job.id == id) {
- if (job) *job = p->job;
- return 1;
- }
+ for (b = used; b; b = b->next) if (b->job.id == id) return &b->job;
- return 0;
+ return NULL;
}
void deinitbg(void) {
- struct bglink *p;
+ struct bgjob *b;
- for (p = bgjobs.active; p; p = p->next) killpg(p->job.id, SIGKILL);
+ for (b = used; b; b = b->next) killpg(b->job.id, SIGKILL);
}
int bg(char **args, size_t numargs) {
- struct bglink *p;
- struct bgjob job;
+ struct bgjob *b;
+ struct job *j, job;
long l;
switch (numargs) {
case 1:
- for (p = bgjobs.active; p; p = p->next) if (p->job.suspended) {
- job = p->job;
- break;
- }
- if (!p) {
- note("No suspended jobs to run in the background");
- return EXIT_FAILURE;
- }
+ for (b = used; b; b = b->next) if (b->job.suspended) break;
+ if (!b) return note("No suspended jobs to run in the background");
+ j = &b->job;
break;
case 2:
- errno = 0;
- if ((l = strtol(args[1], NULL, 10)) == LONG_MAX && errno || l <= 0) {
- note("Invalid job id %ld", l);
- return EXIT_FAILURE;
- }
- if (!searchbg(l, &job)) {
- note("Unable to find job %d", (pid_t)l);
- return EXIT_FAILURE;
- }
- if (!job.suspended) {
- note("Job %d already in background", job.id);
- return EXIT_FAILURE;
- }
+ if ((l = strtol(args[1], NULL, 10)) == LONG_MAX && errno || l <= 0)
+ return note("Invalid job id %ld", l);
+ if (!(j = searchbg(l))) return note("Unable to find job %d", (pid_t)l);
+ if (!j->suspended) return note("Job %d already in background", j->id);
break;
default:
return usage(args[0], "[pgid]");
}
- if (killpg(job.id, SIGCONT) == -1) {
- note("Unable to wake up suspended job %d", job.id);
- return EXIT_FAILURE;
- }
- removebg(job.id);
+ if (killpg(j->id, SIGCONT) == -1)
+ fatal("Unable to wake up suspended job %d", j->id);
+
+ job = *j;
+ removebg(j);
job.suspended = 0;
pushbg(job);
-struct bgjob {
+struct job {
pid_t id;
struct termios config;
int suspended;
};
-extern struct sigaction bgaction;
-
-void removebg(pid_t id);
-void sigchldbghandler(int sig);
void initbg(void);
+void removebg(struct job *j);
+void sigchldbghandler(int signal);
int bgfull(void);
-int pushbg(struct bgjob job);
-int pushbgid(pid_t id);
-int peekbg(struct bgjob *job);
-int searchbg(pid_t id, struct bgjob *job);
+void pushbg(struct job job);
+struct job *peekbg(void);
+struct job *searchbg(pid_t id);
void deinitbg(void);
-#include <err.h>
-#include <fcntl.h>
#include <dirent.h>
-#include <string.h>
-#include <sys/errno.h>
#include "../../external/cbs/cbs.c"
#define MAXBUILTINS 50
int main(void) {
- int listfd, d;
+ int fd, d;
DIR *dir;
char *srcs[MAXBUILTINS + 2 + 1], **src, *decl;
struct dirent *entry;
build("./");
- if ((listfd = open("list.c", O_WRONLY | O_CREAT | O_TRUNC, 0644)) == -1)
- err(EXIT_FAILURE, "Unable to open/create `list.c'");
- if (!(dir = opendir("./")))
- err(EXIT_FAILURE, "Unable to open current directory");
+ if ((fd = open("builtins.c", O_WRONLY | O_CREAT | O_TRUNC, 0644)) == -1)
+ err(errno, "Unable to open/create `builtins.c'");
+ if (!(dir = opendir("./"))) err(errno, "Unable to open current directory");
- dprintf(listfd, "#include <stddef.h>\n\n#include \"builtin.h\"\n\n");
+ dprintf(fd, "#include <stddef.h>\n\n#include \"builtins.h\"\n\n");
src = srcs;
errno = 0;
|| !(*src = strrchr(entry->d_name, '.')) || strcmp(*src, ".c") != 0)
continue;
if (!(*src = strdup(entry->d_name)))
- err(EXIT_FAILURE, "Unable to duplicate directory entry");
+ err(errno, "Unable to duplicate directory entry");
(*src)[strlen(*src) - 2] = '\0';
if (src - srcs == 2 + MAXBUILTINS + 1)
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, "int %s(char **args, size_t numargs);\n", *src);
+ if (strcmp(*src, "builtins") != 0)
+ dprintf(fd, "int %s(char **args, size_t numargs);\n", *src);
++src;
}
- if (errno) err(EXIT_FAILURE, "Unable to read from current directory");
+ if (errno) err(errno, "Unable to read from current directory");
*src = NULL;
- decl = "struct builtin builtins[] = {";
- d = (int)strlen(decl);
- dprintf(listfd, "\n%s", decl);
+ d = (int)strlen(decl = "struct builtin *builtins = (struct builtin []){");
+ dprintf(fd, "\n%s", decl);
for (src = srcs; *src; ++src)
- if (strcmp(*src, "builtin") != 0 && strcmp(*src, "list") != 0)
- dprintf(listfd, "{\"%s\", %s},\n%*s", *src, *src, d, "");
- dprintf(listfd, "{NULL}};\n");
+ if (strcmp(*src, "builtins") != 0)
+ dprintf(fd, "{\"%s\", %s},\n%*s", *src, *src, d, "");
+ dprintf(fd, "{NULL}};\n");
- if (closedir(dir) == -1)
- err(EXIT_FAILURE, "Unable to close current directory");
- if (close(listfd) == -1) err(EXIT_FAILURE, "Unable to close `list.c'");
+ if (closedir(dir) == -1) err(errno, "Unable to close current directory");
+ if (close(fd) == -1) err(errno, "Unable to close `builtins.c'");
cflags = LIST("-I../");
for (src = srcs; *src; ++src) compile(*src);
+++ /dev/null
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "builtin.h"
-#include "utils.h"
-
-int (*getbuiltin(char *name))(char **args, size_t numargs) {
- struct builtin *builtin;
-
- for (builtin = builtins; builtin->func; ++builtin)
- if (strcmp(name, builtin->name) == 0) return builtin->func;
-
- return NULL;
-}
-
-int usage(char *program, char *options) {
- fprintf(stderr, "usage: %s", program);
- if (options) fprintf(stderr, " %s", options);
- fputc('\n', stderr);
- return EXIT_FAILURE;
-}
+++ /dev/null
-extern struct builtin {
- char *name;
- int (*func)(char **args, size_t numargs);
-} builtins[];
-
-int (*getbuiltin(char *name))(char **args, size_t numargs);
-int usage(char *program, char *options);
--- /dev/null
+extern struct builtin {
+ char *name;
+ int (*func)(char **args, size_t numargs);
+} *builtins;
#include <string.h>
#include <unistd.h>
-#include "builtin.h"
#include "utils.h"
int cd(char **args, size_t numargs) {
path = home;
break;
case 2:
- if (!(path = realpath(args[1], buffer))) {
- note(args[1]);
- return EXIT_FAILURE;
- }
+ if (!(path = realpath(args[1], buffer))) return note(args[1]);
l = strlen(buffer);
buffer[l++] = '/';
buffer[l] = '\0';
return usage(args[0], "[directory]");
}
- if (chdir(path) == -1) {
- note(path);
- return EXIT_FAILURE;
- }
+ if (chdir(path) == -1) return note(path);
- if (setenv("PWD", path, 1) == -1) {
- note("Unable to set $PWD$");
- return EXIT_FAILURE;
- }
+ if (setenv("PWD", path, 1) == -1) fatal("Unable to set $PWD$");
return EXIT_SUCCESS;
}
-#include <signal.h>
#include <stdlib.h>
#include <string.h>
-#include <termios.h>
#include <unistd.h>
-#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");
+int execute(struct context *c) {
+ if (!c->t) return EXIT_SUCCESS;
+ if (c->builtin) return c->builtin(c->tokens, c->numtokens);
- 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);
+ execve(c->path, c->tokens, environ);
+ return note("Unable to run `%s' command", c->name);
}
int exec(char **args, size_t numargs) {
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 */
- exit(EXIT_FAILURE);
+ memcpy(c.tokens, args + 1, (c.numtokens = numargs - 1) * sizeof*args);
+ strcpy(c.name, args[1]);
+ if (!(c.builtin = getbuiltin(c.name)) && !(c.path = getpath(c.name)))
+ return note("Unable to find `%s' command", c.name);
+
+ deinit();
+
+ exit(execute(&c));
}
-void execute(struct context *c);
+int execute(struct context *c);
#include <stdlib.h>
-#include "builtin.h"
#include "utils.h"
int exeunt(char **args, size_t numargs) {
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
-#include <string.h>
#include <sys/errno.h>
#include <sys/wait.h>
#include <termios.h>
#include <unistd.h>
#include "bg.h"
-#include "builtin.h"
#include "context.h"
#include "options.h"
-#include "run.h"
#include "signals.h"
#include "utils.h"
-static struct {
- pid_t id;
- int status, done;
-} fgjob;
-static struct sigaction fgaction;
static pid_t pgid;
-struct termios canonical;
-static struct termios raw;
+struct job fgjob;
+static int done;
-static void sigchldfghandler(int sig) {
+void initfg(void) {
+ pid_t pid;
+
+ pid = getpid();
+ pgid = getpgrp();
+ if (login && pid != pgid && setpgid(0, pgid = pid) == -1)
+ fatal("Unable to set process group id for self");
+
+ if (tcsetpgrp(STDIN_FILENO, pgid) == -1)
+ fatal("Unable to set self as foreground process group");
+}
+
+static void sigchldfghandler(int signal) {
int e, s;
- pid_t id;
+ pid_t cpid;
e = errno;
- while ((id = waitpid(-fgjob.id, &s, WNOHANG | WUNTRACED)) > 0) {
- if (id == fgjob.id) fgjob.status = s;
- if (WIFSTOPPED(s)) {
- fgjob.status = s;
- fgjob.done = 1;
+ while ((cpid = waitpid(-fgjob.id, &s, WNOHANG | WUNTRACED)) != 0) {
+ if (cpid == fgjob.id) {
+ if (WIFEXITED(s)) status = WEXITSTATUS(s);
+ else if (WIFSTOPPED(s)) {
+ status = WSTOPSIG(s);
+ fgjob.suspended = 1;
+ } else status = WTERMSIG(s);
+ }
+ if (cpid == -1 || WIFSTOPPED(s)) {
+ done = 1;
break;
}
}
- if (id == -1) fgjob.done = 1;
- sigchldbghandler(sig);
+ sigchldbghandler(signal);
errno = e;
}
-int setconfig(struct termios *mode) {
- if (tcsetattr(STDIN_FILENO, TCSANOW, mode) == -1) {
- note("Unable to configure TTY");
- return 0;
+int runfg(void) {
+ struct termios canonical;
+
+ if (tcgetattr(STDIN_FILENO, &canonical) == -1)
+ fatal("Unable to save TTY mode");
+ if (tcsetattr(STDIN_FILENO, TCSANOW, &fgjob.config) == -1)
+ fatal("Unable to set TTY mode for job %d", fgjob.id);
+ if (tcsetpgrp(STDIN_FILENO, fgjob.id) == -1)
+ fatal("Unable to set foreground process group for job %d", fgjob.id);
+ if (fgjob.suspended) {
+ if (killpg(fgjob.id, SIGCONT) == -1) fatal("Unable to wake job %d", fgjob.id);
+ fgjob.suspended = 0;
}
- return 1;
-}
-
-void initfg(void) {
- pid_t pid;
-
- fgaction = (struct sigaction){.sa_handler = sigchldfghandler};
- pid = getpid();
- pgid = getpgrp();
- if (login && pid != pgid && setpgid(0, pgid = pid) == -1) exit(errno);
-
- if (tcsetpgrp(STDIN_FILENO, pgid) == -1
- || tcgetattr(STDIN_FILENO, &canonical) == -1)
- exit(errno);
- raw = canonical;
- raw.c_lflag &= ~(ICANON | ECHO);
- if (!setconfig(&raw)) exit(EXIT_FAILURE);
-}
-
-int runfg(pid_t id) {
- struct bgjob job;
-
- if (!searchbg(id, &job)) job = (struct bgjob){.id = id, .config = canonical};
- if (!setconfig(&job.config)) return 0;
- if (tcsetpgrp(STDIN_FILENO, id) == -1) {
- note("Unable to bring job %d to foreground", id);
- setconfig(&raw);
- return 0;
- }
- if (killpg(id, SIGCONT) == -1) {
- note("Unable to wake up job %d", id);
- return 0;
- }
- removebg(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 */
- fgjob.id = id;
- setsig(SIGCHLD, &fgaction);
- while (!fgjob.done) {
+ /* `sigchldfghandler()' is what reaps the foreground process. `sigsuspend()'
+ * just blocks the current thread of execution until that foreground process
+ * has been reaped. */
+ sethandler(SIGCHLD, sigchldfghandler);
+ while (!done) {
sigsuspend(&shellsigmask);
if (sigquit) {
deinit();
if (sigint) sigint = 0;
if (sigwinch) sigwinch = 0;
}
- setsig(SIGCHLD, &defaultaction);
- fgjob.done = errno = 0;
+ sethandler(SIGCHLD, SIG_DFL);
+ done = errno = 0;
- if (tcsetpgrp(STDIN_FILENO, pgid) == -1) {
- deinit();
- exit(errno);
+ if (tcsetpgrp(STDIN_FILENO, pgid) == -1)
+ fatal("Unable to set self as foreground process group");
+ if (tcgetattr(STDIN_FILENO, &fgjob.config) == -1)
+ fatal("Unable to save TTY mode from job %d", fgjob.id);
+ if (tcsetattr(STDIN_FILENO, TCSANOW, &canonical) == -1)
+ fatal("Unable to restore TTY mode");
+
+ if (!fgjob.suspended) return status;
+
+ if (!bgfull()) {
+ pushbg(fgjob);
+ return status;
}
- if (tcgetattr(STDIN_FILENO, &job.config) == -1)
- note("Unable to save termios config of job %d", id);
- setconfig(&raw);
-
- if (WIFEXITED(fgjob.status)) status = WEXITSTATUS(fgjob.status);
- else if (WIFSTOPPED(fgjob.status)) {
- status = WSTOPSIG(fgjob.status);
- job.suspended = 1;
- if (!pushbg(job)) {
- note("Unable to suspend current job; too many background jobs\n"
- "(Press any key to continue)");
- getchar();
- return runfg(id);
- }
- } else status = WTERMSIG(fgjob.status);
- return 1;
+ note("Unable to suspend current job; too many background jobs\n"
+ "(Press any key to continue)");
+ getchar();
+ return runfg();
}
int fg(char **args, size_t numargs) {
- struct bgjob job;
+ struct job *j;
long l;
- pid_t id;
switch (numargs) {
case 1:
- if (!peekbg(&job)) {
- note("No job to bring into the foreground");
- return EXIT_FAILURE;
- }
- id = job.id;
+ if (!(j = peekbg()))
+ return note("No suspended jobs to run in the foreground");
break;
case 2:
- errno = 0;
- 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;
- if (!searchbg(id, NULL)) {
- note("Unable to find job %d", id);
- return EXIT_FAILURE;
- }
+ if ((l = strtol(args[1], NULL, 10)) == LONG_MAX && errno || l <= 0)
+ return note("Invalid job id %ld", l);
+ if (!(j = searchbg((pid_t)l))) return note("Unable to find job %d", (pid_t)l);
break;
default:
return usage(args[0], "[pgid]");
}
- if (!runfg(id)) return EXIT_FAILURE;
+ fgjob = *j;
+ removebg(j);
- return EXIT_SUCCESS;
+ return runfg();
}
-extern struct termios canonical;
+extern struct job fgjob;
-int setconfig(struct termios *mode);
void initfg(void);
-int runfg(pid_t id);
+int runfg(void);
#include <stdlib.h>
#include <string.h>
-#include "builtin.h"
-#include "context.h"
-#include "run.h"
+#include "utils.h"
+
+int verbose;
int mode(char **args, size_t numargs) {
switch (numargs) {
default:
return usage(args[0], "[verbose | quiet]");
}
+
return EXIT_SUCCESS;
}
--- /dev/null
+extern int verbose;
#include <string.h>
#include <unistd.h>
-#include "builtin.h"
#include "utils.h"
int pwd(char **args, size_t numargs) {
- char *cwd, buffer[PATH_MAX];
+ char buffer[PATH_MAX], *cwd;
size_t l;
if (numargs != 1) return usage(args[0], NULL);
- if (!(cwd = getcwd(buffer, PATH_MAX))) {
- note("Unable to get current working directory");
- return EXIT_FAILURE;
- }
+ if (!(cwd = getcwd(buffer, PATH_MAX)))
+ fatal("Unable to get current working directory");
if (buffer[(l = strlen(buffer)) - 1] != '/') {
buffer[l++] = '/';
buffer[l] = '\0';
}
- if (setenv("PWD", buffer, 1) == -1) {
- note("Unable to set $PWD$");
- return EXIT_FAILURE;
- }
+
+ if (setenv("PWD", cwd, 1) == -1) fatal("Unable to set $PWD$");
puts(cwd);
#include <stdlib.h>
-#include "builtin.h"
#include "utils.h"
int set(char **args, size_t numargs) {
switch (numargs) {
case 3:
- if (setenv(args[1], args[2], 1) == -1) {
- note("Unable to set `%s' to `%s'", args[1], args[2]);
- return EXIT_FAILURE;
- }
+ if (setenv(args[1], args[2], 1) == -1)
+ fatal("Unable to set `%s' to `%s'", args[1], args[2]);
case 2:
break;
default:
return usage(args[0], "name [value]");
}
+
return EXIT_SUCCESS;
}
#include <fcntl.h>
#include <limits.h>
-#include <stdlib.h>
+#include <string.h>
#include <unistd.h>
-#include "builtin.h"
#include "context.h"
#include "input.h"
#include "options.h"
int source(char **args, size_t numargs) {
struct context c;
- char **vector;
size_t count;
-
+ char **vector;
+
if (numargs < 2) return usage(args[0], "file [args ...]");
c = (struct context){.script = args[1], .input = scriptinput};
/* See comment in `src/options.c' */
args[1] = argvector[0];
- vector = argvector;
count = argcount;
- argvector = args + 1;
+ vector = argvector;
argcount = numargs - 1;
+ argvector = args + 1;
while (run(&c));
argvector = vector;
argcount = count;
- return EXIT_SUCCESS;
+ return status;
}
void config(char *name) {
char path[PATH_MAX];
int fd;
- if (!catpath(home, name, path)) return;
+ strcpy(path, home);
+ strcat(path, name);
- if ((fd = open(path, O_RDONLY | O_CREAT, 0644)) == -1) {
- note("Unable to create `%s'", path);
- return;
- }
- if (close(fd) == -1) {
- note("Unable to close `%s'", path);
- return;
- }
+ if ((fd = open(path, O_RDONLY | O_CREAT, 0644)) == -1)
+ fatal("Unable to create `%s'", path);
+ if (close(fd) == -1) fatal("Unable to close `%s'", path);
source((char *[]){"source", path, NULL}, 2);
}
#include <stdlib.h>
#include "alias.h"
-#include "builtin.h"
+#include "utils.h"
int unalias(char **args, size_t numargs) {
if (numargs != 2) return usage(args[0], "name");
+
return removealias(args[1]) ? EXIT_SUCCESS : EXIT_FAILURE;
}
#include <stdlib.h>
-#include "builtin.h"
#include "utils.h"
int unset(char **args, size_t numargs) {
if (numargs != 2) return usage(args[0], "name");
- if (unsetenv(args[1]) == -1) {
- note("Unable to unset `%s'", args[1]);
- return EXIT_FAILURE;
- }
+ if (unsetenv(args[1]) == -1) fatal("Unable to unset `%s'", args[1]);
return EXIT_SUCCESS;
}
#include <sys/stat.h>
#include "alias.h"
-#include "builtin.h"
+#include "builtins.h"
#include "utils.h"
+int (*getbuiltin(char *name))(char **args, size_t numargs) {
+ struct builtin *b;
+
+ for (b = builtins; b->func; ++b)
+ if (strcmp(name, b->name) == 0) return b->func;
+
+ return NULL;
+}
+
static int exists(char *path) {
struct stat pstat;
mode_t mask;
- if (stat(path, &pstat) != -1) {
- mask = S_IFREG | S_IXUSR;
- if ((pstat.st_mode & mask) == mask) return 1;
- } else if (errno != ENOENT) note("Unable to check if `%s' exists", path);
- else errno = 0;
+ if (stat(path, &pstat) == -1) return errno = 0;
+
+ mask = S_IFREG | S_IXUSR;
+ if ((pstat.st_mode & mask) != mask) return 0;
- return 0;
+ return 1;
}
char *getpath(char *file) {
- char *slash, *entry, *end, dir[PATH_MAX];
+ char *slash, *entry, *end;
size_t l;
static char path[PATH_MAX];
if (!(slash = strchr(file, '/'))) {
- if (!(entry = getenv("PATH"))) {
- note("Unable to examine $PATH$");
- return NULL;
- }
+ if (!(entry = getenv("PATH"))) fatal("Unable to examine $PATH$");
for (end = entry; end; entry = end + 1) {
l = (end = strchr(entry, ':')) ? end - entry : strlen(entry);
- strncpy(dir, entry, l);
- if (dir[l - 1] != '/') dir[l++] = '/';
- dir[l] = '\0';
- if (!catpath(dir, file, path)) return NULL;
+ strncpy(path, entry, l);
+ if (path[l - 1] != '/') path[l++] = '/';
+ path[l] = '\0';
+ strcat(path, file);
if (exists(path)) return path;
}
}
- if (!realpath(file, path)) {
- if (errno != ENOENT) note("Unable to expand `%s'", file); else errno = 0;
+ if (!realpath(file, path) || !exists(path)) {
+ errno = 0;
return NULL;
}
- return exists(path) ? path : NULL;
+ return path;
}
int which(char **args, size_t numargs) {
+int (*getbuiltin(char *name))(char **args, size_t numargs);
char *getpath(char *file);
-#include <stddef.h>
+#include <stdlib.h>
#include "context.h"
#include "input.h"
+int status;
+
int clear(struct context *c) {
c->b = NULL;
c->t = NULL;
c->r = NULL;
- c->current.name[0] = c->buffer[0] = '\0';
- c->previous.term = c->current.term = SEMI;
+ c->name[0] = c->buffer[0] = '\0';
+ c->previous.type = c->current.type = ';';
return 1;
}
int quit(struct context *c) {
clear(c);
+ if (status == EXIT_SUCCESS) status = EXIT_FAILURE;
return c->input == userinput;
}
#define MAXCHARS 1000
-#define MAXCOMMANDS (MAXCHARS + 1) / 2
#define MAXREDIRECTS (MAXCHARS / 3)
-struct redirect {
- enum {
- NONE,
- READ = '<',
- READWRITE,
- WRITE = '>',
- APPEND,
- } mode;
- int oldfd, newfd;
- char *oldname;
-};
-
-struct command {
- 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;
+ *tokens[(MAXCHARS + 1) / 2 + 1], **t, name[MAXCHARS + 1], *path;
size_t maplen, numtokens;
- int (*input)(struct context *c), alias;
- struct redirect redirects[MAXREDIRECTS + 1], *r;
- struct command current, previous;
+ int (*input)(struct context *c), alias,
+ (*builtin)(char **args, size_t numargs);
+ struct {
+ enum {
+ NONE,
+ READ = '<',
+ READWRITE,
+ WRITE = '>',
+ APPEND,
+ } mode;
+ int oldfd, newfd;
+ char *oldname;
+ } redirects[MAXREDIRECTS + 1], *r;
+ struct {
+ char type;
+ int pipe[2];
+ } previous, current;
};
+extern int status;
+
int clear(struct context *c);
int quit(struct context *c);
#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
-#include <stdlib.h>
#include <string.h>
#include <sys/errno.h>
#include "context.h"
-#include "options.h"
#include "utils.h"
#define MAXHIST 100
-#define INC(x) (history.x = (history.x + 1) % (MAXHIST + 1))
-#define DEC(x) (history.x = (history.x + MAXHIST) % (MAXHIST + 1))
+static char history[MAXHIST + 1][MAXCHARS + 1], *b, *s, *c, *t, path[PATH_MAX];
-static struct {
- char path[PATH_MAX], entries[MAXHIST + 1][MAXCHARS + 1];
- size_t b, s, c, t;
-} history;
+static char *inc(char **x) {
+ return *x = (*x == history[MAXHIST] ? history[0] : *x + MAXCHARS + 1);
+}
static void readhistory(FILE *file) {
- history.b = history.t = 0;
- while (fgets(history.entries[history.t], sizeof*history.entries, file)) {
- history.entries[history.t][strlen(history.entries[history.t]) - 1] = '\0';
- if (INC(t) == history.b) INC(b);
+ b = t = history[0];
+ while (fgets(t, MAXCHARS + 1, file)) {
+ t[strlen(t) - 1] = '\0';
+ if (inc(&t) == b) inc(&b);
}
- history.s = history.c = history.t;
+ s = c = t;
}
void inithistory(void) {
FILE *file;
- if (!interactive) return;
-
- if (!catpath(home, ".thushistory", history.path)) exit(EXIT_FAILURE);
- if (!(file = fopen(history.path, "r"))) {
+ strcpy(path, home);
+ strcat(path, ".thushistory");
+ if (!(file = fopen(path, "r"))) {
if (errno == ENOENT) return;
fatal("Unable to open history file for reading");
}
if (fclose(file) == EOF) fatal("Unable to close history file");
}
+static char *dec(char **x) {
+ return *x = (*x == history[0] ? history[MAXHIST] : *x - (MAXCHARS + 1));
+}
+
int gethistory(int back, char *buffer) {
- if (history.c == (back ? history.b : history.t)) return 0;
+ if (c == (back ? b : t)) return 0;
/* Save the most recently modified history entry at the top of the list */
- if (strcmp(history.entries[history.c], buffer) != 0)
- strcpy(history.entries[history.t], buffer);
+ if (strcmp(c, buffer) != 0) strcpy(t, buffer);
- strcpy(buffer, history.entries[back ? DEC(c) : INC(c)]);
+ strcpy(buffer, back ? dec(&c) : inc(&c));
return 1;
}
void addhistory(char *buffer) {
if (buffer) {
- strcpy(history.entries[history.t], buffer);
- if (INC(t) == history.b) INC(b);
- if (history.t == history.s) INC(s);
+ strcpy(t, buffer);
+ if (inc(&t) == b) inc(&b);
+ if (t == s) inc(&s);
}
- *history.entries[history.c = history.t] = '\0';
+ *(c = t) = '\0';
}
static void writehistory(FILE *file) {
- for (history.c = history.b; history.c != history.t; INC(c)) {
- if (fputs(history.entries[history.c], file) == EOF) {
- note("Unable to write to history file");
- break;
- }
- if (fputc('\n', file) == EOF) {
- note("Unable to terminate line of history file");
- break;
- }
+ for (c = b; c != t; inc(&c)) {
+ if (fputs(c, file) == EOF) fatal("Unable to write to history file");
+ if (fputc('\n', file) == EOF)
+ fatal("Unable to create new line in history file");
}
}
int fd;
FILE *file;
- if (!interactive) return;
-
- if ((fd = open(history.path, O_WRONLY | O_CREAT | O_APPEND, 0600)) == -1) {
- note("Unable to open history file for writing");
- return;
- }
- if (!(file = fdopen(fd, "a"))) {
- note("Unable to open history file descriptor as FILE pointer");
- return;
- }
- history.b = history.s;
+ if ((fd = open(path, O_WRONLY | O_CREAT | O_APPEND, 0600)) == -1)
+ fatal("Unable to open history file for writing");
+ if (!(file = fdopen(fd, "a")))
+ fatal("Unable to associate a new stream with the history file");
+ b = s;
writehistory(file);
- if (!freopen(history.path, "r", file)) {
- note("Unable to reopen history file for reading");
- return;
- }
+ if (!freopen(path, "r", file))
+ fatal("Unable to reopen history file for reading");
readhistory(file);
- if (!freopen(history.path, "w", file)) {
- note("Unable to reopen history file for writing");
- return;
- }
+ if (!freopen(path, "w", file))
+ fatal("Unable to reopen history file for writing");
writehistory(file);
- if (fclose(file) == EOF) note("Unable to close history stream");
+ if (fclose(file) == EOF) fatal("Unable to close history file");
}
#include <fcntl.h>
-#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/errno.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/stat.h>
+#include <termios.h>
#include <unistd.h>
#include "context.h"
#include "signals.h"
#include "utils.h"
-#define OFFSET(x) ((promptlen + x - start) % window.ws_col)
-
-static char *end, *cursor, *start;
-static struct winsize window;
+static char *end, *start, *cursor;
static size_t promptlen;
+static struct winsize window;
int stringinput(struct context *c) {
size_t l;
if (!c->string[0]) {
if (c->script && munmap(c->map, c->maplen) == -1)
- note("Unable to unmap memory associated with `%s'", c->script);
+ fatal("Unable to unmap memory associated with `%s'", c->script);
return 0;
}
while (*end && *end != '\n') ++end;
l = end - c->string;
while (*end == '\n') ++end;
- if (l > MAXCHARS) {
- note("Line too long, exceeds %d character limit", MAXCHARS);
- return 0;
- }
+ if (l > MAXCHARS) fatal("Line too long, exceeds %d character limit", MAXCHARS);
strncpy(c->buffer, c->string, l);
c->buffer[l++] = ';';
int fd;
struct stat sstat;
- if ((fd = open(c->script, O_RDONLY)) == -1) {
- note("Unable to open `%s'", c->script);
- return 0;
- }
- if (stat(c->script, &sstat) == -1) {
- note("Unable to stat `%s'", c->script);
- return 0;
- }
+ if ((fd = open(c->script, O_RDONLY)) == -1)
+ fatal("Unable to open `%s'", c->script);
+ if (stat(c->script, &sstat) == -1) fatal("Unable to stat `%s'", c->script);
if ((c->maplen = sstat.st_size) == 0) return 0;
if ((c->map = mmap(NULL, c->maplen, PROT_READ, MAP_PRIVATE, fd, 0))
- == MAP_FAILED) {
- note("Unable to memory map `%s'", c->script);
- return 0;
- }
- if (close(fd) == -1) {
- note("Unable to close `%s'", c->script);
- return 0;
- }
+ == MAP_FAILED)
+ fatal("Unable to memory map `%s'", c->script);
+ if (close(fd) == -1) fatal("Unable to close `%s'", c->script);
c->string = c->map;
c->input = stringinput;
return c->input(c);
}
+static size_t offset(char *x) {
+ return (promptlen + x - start) % window.ws_col;
+}
+
static void moveright(void) {
putchar(*cursor);
- if (OFFSET(cursor++) == window.ws_col - 1) putchar('\n');
+ if (offset(cursor++) == window.ws_col - 1) putchar('\n');
}
static void prompt(void) {
char *p, *oldstart, *oldcursor;
- if (!(p = getenv("PROMPT")) && setenv("PROMPT", p = "> ", 1) == -1)
- note("Unable to update $PROMPT$ environment variable");
+ if (!(p = getenv("PROMPT")))
+ fatal("Unable to access $PROMPT$ environment variable");
oldstart = start;
oldcursor = cursor;
}
static void moveleft(void) {
- if (OFFSET(cursor--)) putchar('\b');
+ if (offset(cursor--)) putchar('\b');
else printf("\033[A\033[%dC", window.ws_col - 1);
}
size_t i;
for (i = (promptlen + end - cursor) / window.ws_col; i > 0; --i) putchar('\n');
- if (OFFSET(cursor) > OFFSET(end)) putchar('\n');
- if (OFFSET(end)) putchar('\n');
+ if (offset(cursor) > offset(end)) putchar('\n');
+ if (offset(end)) putchar('\n');
putchar('\r');
fflush(stdout);
}
DEL = '\177',
};
+ struct termios canonical, raw;
int current;
char *oldcursor, *oldend;
clear(c);
end = cursor = start = c->buffer;
+
+ if (tcgetattr(STDIN_FILENO, &canonical) == -1)
+ fatal("Unable to save TTY mode");
+ raw = canonical;
+ raw.c_lflag &= ~(ICANON | ECHO);
+ if (tcsetattr(STDIN_FILENO, TCSANOW, &raw) == -1)
+ fatal("Unable to set raw TTY mode for input");
+
while (start == end) {
prompt();
while ((current = getchar()) != '\n') switch (current) {
if (sigquit) {
case EOT:
newline();
+
+ if (tcsetattr(STDIN_FILENO, TCSANOW, &canonical) == -1)
+ fatal("Unable to restore TTY mode");
+
+ status = EXIT_SUCCESS;
return 0;
}
if (sigint) {
addhistory(NULL);
}
+
+ errno = 0;
+
break;
case FF:
oldcursor = cursor;
newline();
}
+ if (tcsetattr(STDIN_FILENO, TCSANOW, &canonical) == -1)
+ fatal("Unable to restore TTY mode");
+
while (*start == ' ') ++start;
if (start == end) return quit(c);
while (*(end - 1) == ' ') --end;
while (run(&c));
deinit();
- return EXIT_SUCCESS;
+ return status;
}
#include <string.h>
#include <unistd.h>
-#include "builtin.h"
#include "context.h"
#include "input.h"
#include "utils.h"
+size_t argcount;
char **argvector;
int login, interactive;
-size_t argcount;
void options(int argc, char **argv, struct context *c) {
char *p, *message = "[file | -c string] [arg ...] [-hl]\n"
" -l Run as a login shell";
int opt;
- argvector = argv;
- if (argvector[0][0] == '-') {
+ argcount = argc;
+ if ((argvector = argv)[0][0] == '-') {
++argvector[0];
login = 1;
}
if ((p = strrchr(argvector[0], '/'))) argvector[0] = p + 1;
opt = 0;
- argcount = argc;
interactive = 1;
c->input = userinput;
while (opt != 'c' && (opt = getopt(argcount, argvector, ":c:hl")) != -1)
+extern size_t argcount;
extern char **argvector;
extern int login, interactive;
-extern size_t argcount;
void options(int argc, char **argv, struct context *c);
#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) {
int parse(struct context *c) {
char *end, *stlend, *p, *var, term;
- int quote, globbing, v, offset, globflags;
- size_t prevnumglobs;
+ int quote, globbing, v, offset;
+ size_t g;
long l;
static glob_t globs;
c->previous = c->current;
for (end = c->b; *end; ++end);
- prevnumglobs = globbing = quote = 0;
+ g = globbing = quote = 0;
if (globs.gl_pathc) {
globfree(&globs);
globs.gl_pathc = 0;
if (quote || c->r->mode) break;
if (*c->t == c->b) c->r->newfd = *c->b == '>';
- else if ((c->r->newfd = strtol(*c->t, &stlend, 10)) < 0
- || c->r->newfd > INT_MAX || stlend != c->b)
- break;
+ else if ((l = strtol(*c->t, &stlend, 10)) >= 0
+ && l <= INT_MAX && stlend == c->b)
+ c->r->newfd = l;
+ else break;
c->r->mode = *c->b;
if (*(c->b + 1) == '>') {
++c->r->mode;
break;
case '\\':
- if (!quote) break;
- switch (*(c->b + 1)) {
+ if (quote) switch (*(c->b + 1)) {
default:
memmove(c->b, c->b + 1, end-- - c->b);
--c->b;
}
if (globbing) {
- globflags = GLOB_MARK;
- if (prevnumglobs) globflags |= GLOB_APPEND;
- switch (glob(*c->t, globflags, NULL, &globs)) {
+ switch (glob(*c->t, GLOB_MARK | (g ? GLOB_APPEND : 0), NULL, &globs)) {
case GLOB_NOMATCH:
note("No matches found for `%s'", *c->t);
return quit(c);
}
globbing = 0;
- sub(c, globs.gl_pathv + prevnumglobs, globs.gl_pathc - prevnumglobs);
- prevnumglobs = globs.gl_pathc;
+ sub(c, globs.gl_pathv + g, globs.gl_pathc - g);
+ g = globs.gl_pathc;
}
if (*c->t != c->b) {
*c->t = NULL;
if (c->t != c->tokens) {
c->numtokens = c->t - c->tokens;
- strcpy(c->current.name, c->tokens[0]);
+ strcpy(c->name, c->tokens[0]);
} else c->t = NULL;
if (c->r == c->redirects) c->r = NULL;
- switch (term) {
- case '&':
- case '|':
- c->current.term = term;
- if (*(c->b + 1) == term) {
- ++c->current.term;
- *++c->b = '\0';
- }
- break;
- case ';':
- c->current.term = SEMI;
- }
+ c->current.type = term;
++c->b;
return 1;
#include <unistd.h>
#include "bg.h"
-#include "builtin.h"
#include "context.h"
#include "exec.h"
#include "fg.h"
+#include "mode.h"
#include "parse.h"
#include "signals.h"
#include "utils.h"
#include "which.h"
-int verbose, status;
+static void closepipe(struct context *c, int current) {
+ int *fds;
-static int closepipe(struct command command) {
- int result;
-
- result = close(command.pipe[0]) == 0;
- result &= close(command.pipe[1]) == 0;
- if (!result) note("Unable to close `%s' pipe", command.name);
-
- return result;
-}
-
-static void redirectfiles(struct redirect *r) {
- int access;
-
- for (; r->mode; ++r) {
- if (r->oldname) {
- switch (r->mode) {
- case READ:
- default:
- access = O_RDONLY;
- break;
- case WRITE:
- access = O_WRONLY | O_CREAT | O_TRUNC;
- break;
- case READWRITE:
- access = O_RDWR | O_CREAT | O_APPEND;
- break;
- case APPEND:
- access = O_WRONLY | O_CREAT | O_APPEND;
- }
- if ((r->oldfd = open(r->oldname, access, 0644)) == -1)
- fatal("Unable to open `%s'", r->oldname);
- }
- if (dup2(r->oldfd, r->newfd) == -1)
- 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);
- }
+ fds = current ? c->current.pipe : c->previous.pipe;
+ if (close(fds[0]) == -1 || close(fds[1]) == -1)
+ fatal("Unable to close pipe %s `%s' command",
+ current ? "after" : "before", c->name);
}
int run(struct context *c) {
- int islist, ispipe, ispipestart, ispipeend;
- pid_t cpid, jobid;
+ enum {
+ PREVIOUS,
+ CURRENT,
+ };
+
+ pid_t cpid;
+ struct job *j;
+ int access;
static pid_t pipeid;
- setsig(SIGCHLD, &bgaction);
+ sethandler(SIGCHLD, sigchldbghandler);
if (!parse(c)) return 0;
- setsig(SIGCHLD, &defaultaction);
+ sethandler(SIGCHLD, SIG_DFL);
if (verbose && (c->t || c->r)) {
+ if (c->previous.type == '|') fputs(" | ", stdout);
if (c->t) {
for (c->t = c->tokens; *c->t; ++c->t) {
if (c->t != c->tokens) putchar(' ');
if (c->r->oldname) fputs(c->r->oldname, stdout);
else printf("&%d", c->r->oldfd);
}
- switch (c->current.term) {
- case PIPE:
- fputs(" | ", stdout);
- fflush(stdout);
- break;
- case BG:
- putchar('&');
- default:
+ if ((linestart = c->current.type != '|')) {
+ if (c->current.type == '&') putchar('&');
putchar('\n');
}
}
- islist = c->previous.term > BG || c->current.term > BG;
-
- if (!c->t) {
- if (islist) {
- if (c->previous.term == PIPE) {
- killpg(pipeid, SIGKILL);
- if (verbose) putchar('\n');
- }
- note("Expected command");
- return quit(c);
+ if (c->t ? !(c->builtin = getbuiltin(c->name)) && !(c->path = getpath(c->name))
+ : c->previous.type == '|' || c->current.type == '|') {
+ if (c->t) note("Unable to find `%s' command", c->name);
+ else note("Expected command in pipeline");
+ if (c->previous.type == '|') {
+ if (killpg(pipeid, SIGKILL) == -1) fatal("Unable to kill job %d", pipeid);
+ closepipe(c, PREVIOUS);
}
- if (!c->r) return 1;
-
- if ((cpid = fork()) == -1) {
- note("Unable to fork child process");
- return quit(c);
- }
- if (!cpid) {
- redirectfiles(c->redirects);
- exit(EXIT_SUCCESS);
- }
- waitpid(cpid, NULL, 0);
- errno = 0;
-
- return 1;
+ return quit(c);
}
-
- if (c->current.term == BG && bgfull()) {
+ if (!c->t && !c->r) return 1;
+ if (c->previous.type != '|' && c->current.type != ';' && bgfull()) {
note("Unable to place job in background; too many background jobs");
return quit(c);
}
- 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->previous.term == PIPE) killpg(pipeid, SIGKILL);
- return quit(c);
+
+ if (c->current.type == '|' && pipe(c->current.pipe) == -1)
+ fatal("Unable to create pipe after `%c' command", c->name);
+
+ if (c->builtin && !c->r && c->current.type == ';') {
+ status = execute(c);
+ return 1;
}
- ispipe = c->previous.term == PIPE || c->current.term == PIPE;
- ispipestart = ispipe && c->previous.term != PIPE;
- ispipeend = ispipe && c->current.term != PIPE;
+ if ((cpid = fork()) == -1) fatal("Unable to fork child process");
- if (ispipe) {
- if (!ispipeend && pipe(c->current.pipe) == -1) {
- note("Unable to create pipe");
- if (!ispipestart) closepipe(c->previous);
- return quit(c);
- }
- if ((cpid = fork()) == -1) {
- note("Unable to fork child process");
- return quit(c);
- }
- if (!cpid) {
- if (!ispipestart) {
- 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);
- }
- redirectfiles(c->redirects);
- execute(c);
+ if (cpid) {
+ if (!c->t) {
+ waitpid(cpid, NULL, 0);
+ errno = 0;
+ return 1;
}
- if (ispipestart) pipeid = cpid;
- else if (!closepipe(c->previous)) {
- killpg(pipeid, SIGKILL);
- return quit(c);
+
+ if (c->previous.type == '|' || c->current.type == '|') {
+ if (c->previous.type == '|') closepipe(c, PREVIOUS); else pipeid = cpid;
+ fgjob.id = pipeid;
+ } else fgjob.id = cpid;
+ if (setpgid(cpid, fgjob.id) == -1)
+ fatal("Unable to set `%s' command to process group %d", c->name, fgjob.id);
+ if (tcgetattr(STDIN_FILENO, &fgjob.config) == -1)
+ fatal("Unable to set TTY mode of `%s' command", c->name);
+ fgjob.suspended = 0;
+
+ switch (c->current.type) {
+ case ';':
+ if ((j = searchbg(fgjob.id))) removebg(j);
+ if (runfg() != EXIT_SUCCESS) return quit(c);
+ break;
+ case '&':
+ case '|':
+ if (c->previous.type != '|') pushbg(fgjob);
}
- jobid = pipeid;
- } else if (c->current.builtin && !c->r) {
- 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) {
- redirectfiles(c->redirects);
- execute(c);
+
+ return 1;
}
- if (cpid) {
- if (setpgid(cpid, jobid) == -1) {
- if (errno != ESRCH) {
- note("Unable to set pgid of `%s' command to %d", c->current.name, jobid);
- if (kill(cpid, SIGKILL) == -1)
- note("Unable to kill process %d; may need to manually terminate", cpid);
+ if (sigprocmask(SIG_SETMASK, &childsigmask, NULL) == -1)
+ fatal("Unable to unblock TTY signals");
+
+ if (c->previous.type == '|') {
+ if (dup2(c->previous.pipe[0], 0) == -1)
+ fatal("Unable to duplicate pipe read end of `%s' command", c->name);
+ closepipe(c, PREVIOUS);
+ }
+ if (c->current.type == '|') {
+ if (dup2(c->current.pipe[1], 1) == -1)
+ fatal("Unable to duplicate pipe write end of `%s' command", c->name);
+ closepipe(c, CURRENT);
+ }
+
+ if (c->r) for (c->r = c->redirects; c->r->mode; ++c->r) {
+ if (c->r->oldname) {
+ switch (c->r->mode) {
+ case READ:
+ access = O_RDONLY;
+ break;
+ case WRITE:
+ access = O_WRONLY | O_CREAT | O_TRUNC;
+ break;
+ case READWRITE:
+ access = O_RDWR | O_CREAT | O_APPEND;
+ break;
+ case APPEND:
+ access = O_WRONLY | O_CREAT | O_APPEND;
+ break;
+ case NONE:
+ fatal("Unreachable mode for file redirection");
}
- return quit(c);
- }
- if (ispipestart || c->current.term == BG) {
- pushbgid(jobid);
- return 1;
+ if ((c->r->oldfd = open(c->r->oldname, access, 0644)) == -1)
+ fatal("Unable to open `%s'", c->r->oldname);
}
- if (c->current.term != PIPE && !runfg(jobid)) return quit(c);
+ if (dup2(c->r->oldfd, c->r->newfd) == -1)
+ fatal("Unable to redirect file descriptor %d to %d",
+ c->r->newfd, c->r->oldfd);
+ if (c->r->oldname && close(c->r->oldfd) == -1)
+ fatal("Unable to close `%s'", c->r->oldname);
}
- if (status != EXIT_SUCCESS) {
- if (!islist) return quit(c);
- if (c->current.term == AND) return clear(c);
- } else if (c->current.term == OR) return clear(c);
-
- return 1;
+ exit(execute(c));
}
-extern int verbose, status;
-
int run(struct context *c);
#include <signal.h>
-#include <stdlib.h>
#include <string.h>
-#include <sys/errno.h>
#include "utils.h"
int sigquit, sigint, sigwinch;
sigset_t shellsigmask, childsigmask;
-struct sigaction defaultaction;
-void setsig(int sig, struct sigaction *act) {
- if (sigaction(sig, act, NULL) == -1)
- fatal("Unable to install %s handler", strsignal(sig));
+void sethandler(int signal, void (*handler)(int signal)) {
+ struct sigaction action;
+
+ action = (struct sigaction){.sa_handler = handler};
+ if (sigaction(signal, &action, NULL) == -1)
+ fatal("Unable to install %s handler", strsignal(signal));
}
-static void sigquithandler(int sig) {
- (void)sig;
+static void sigquithandler(int signal) {
+ (void)signal;
sigquit = 1;
}
-static void siginthandler(int sig) {
- (void)sig;
+static void siginthandler(int signal) {
+ (void)signal;
sigint = 1;
}
-static void sigwinchhandler(int sig) {
- (void)sig;
+static void sigwinchhandler(int signal) {
+ (void)signal;
sigwinch = 1;
}
void initsignals(void) {
- struct sigaction action;
+ int *signals;
sigemptyset(&shellsigmask);
- sigaddset(&shellsigmask, SIGTSTP);
- sigaddset(&shellsigmask, SIGTTIN);
- sigaddset(&shellsigmask, SIGTTOU);
- if (sigprocmask(SIG_BLOCK, &shellsigmask, &childsigmask) == -1) exit(errno);
-
- defaultaction = (struct sigaction){.sa_handler = SIG_DFL};
- setsig(SIGTSTP, &defaultaction);
- setsig(SIGTTOU, &defaultaction);
- setsig(SIGTTIN, &defaultaction);
-
- action = (struct sigaction){.sa_handler = sigquithandler};
- setsig(SIGHUP, &action);
- setsig(SIGQUIT, &action);
- setsig(SIGTERM, &action);
-
- action = (struct sigaction){.sa_handler = siginthandler};
- setsig(SIGINT, &action);
-
- action = (struct sigaction){.sa_handler = sigwinchhandler};
- setsig(SIGWINCH, &action);
+ for (signals = (int []){SIGTSTP, SIGTTOU, SIGTTIN, 0}; *signals; ++signals) {
+ sigaddset(&shellsigmask, *signals);
+ sethandler(*signals, SIG_DFL);
+ }
+ if (sigprocmask(SIG_BLOCK, &shellsigmask, &childsigmask) == -1)
+ fatal("Unable to block TTY signals");
+
+ for (signals = (int []){SIGHUP, SIGQUIT, SIGTERM, 0}; *signals; ++signals)
+ sethandler(*signals, sigquithandler);
+ sethandler(SIGINT, siginthandler);
+ sethandler(SIGWINCH, sigwinchhandler);
}
-extern int sigwinch, sigquit, sigint;
+extern int sigquit, sigint, sigwinch;
extern sigset_t shellsigmask, childsigmask;
-extern struct sigaction defaultaction;
-void setsig(int sig, struct sigaction *act);
+void sethandler(int signal, void (*handler)(int signal));
void initsignals(void);
#include <limits.h>
-#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include "options.h"
#include "signals.h"
+int linestart = 1;
char *home;
-void note(char *fmt, ...) {
+int usage(char *program, char *options) {
+ if (!linestart) {
+ fputc('\n', stderr);
+ linestart = 1;
+ }
+
+ fprintf(stderr, "usage: %s", program);
+ if (options) fprintf(stderr, " %s", options);
+ fputc('\n', stderr);
+
+ return EXIT_FAILURE;
+}
+
+static void print(FILE *file, char *format, va_list args) {
+ if (!linestart) {
+ fputs("\r\n", file);
+ linestart = 1;
+ }
+
+ fprintf(file, "%s: ", argvector[0]);
+ vfprintf(file, format, args);
+ if (errno) fprintf(file, ": %s", strerror(errno));
+ fputs("\r\n", file);
+}
+
+int note(char *format, ...) {
va_list args;
+ int result;
- fprintf(stderr, "%s: ", argvector[0]);
- va_start(args, fmt);
- vfprintf(stderr, fmt, args);
+ va_start(args, format);
+ print(stdout, format, args);
va_end(args);
- if (errno) {
- fprintf(stderr, ": %s", strerror(errno));
- errno = 0;
- }
- putchar('\n');
+
+ result = errno ? errno : EXIT_FAILURE;
+ errno = 0;
+ return result;
}
-void fatal(char *fmt, ...) {
+void fatal(char *format, ...) {
va_list args;
- fprintf(stderr, "%s: ", argvector[0]);
- va_start(args, fmt);
- vfprintf(stderr, fmt, args);
+ va_start(args, format);
+ print(stderr, format, args);
va_end(args);
- if (errno) fprintf(stderr, ": %s", strerror(errno));
- putchar('\n');
- exit(errno);
+ exit(errno ? errno : EXIT_FAILURE);
}
void init(void) {
char *shlvlstr, buffer[PATH_MAX];
- size_t l;
long shlvl;
+ size_t l;
if (!(shlvlstr = getenv("SHLVL"))) shlvlstr = "0";
if ((shlvl = strtol(shlvlstr, NULL, 10)) < 0) shlvl = 0;
sprintf(buffer, "%ld", ++shlvl);
- if (setenv("SHLVL", buffer, 1) == -1)
- note("Unable to update $SHLVL$ environment variable");
+ if (setenv("SHLVL", buffer, 1) == -1) fatal("Unable to set $SHLVL$");
if (!(home = getenv("HOME"))) fatal("Unable to find home directory");
if (home[(l = strlen(home)) - 1] != '/') {
buffer[l++] = '/';
buffer[l] = '\0';
if (setenv("HOME", buffer, 1) == -1 || !(home = getenv("HOME")))
- note("Unable to append trailing slash to $HOME$");
+ fatal("Unable to append trailing slash to $HOME$");
}
if (!getcwd(buffer, PATH_MAX)) fatal("Unable to find current directory");
buffer[l++] = '/';
buffer[l] = '\0';
if (setenv("PWD", buffer, 1) == -1)
- note("Unable to append trailing slash to $PWD$");
+ fatal("Unable to append trailing slash to $PWD$");
if (shlvl == 1
&& setenv("PATH", "/usr/local/bin/:/usr/local/sbin/"
":/usr/bin/:/usr/sbin/:/bin/:/sbin/", 1) == -1)
- note("Unable to initialize $PATH$");
+ fatal("Unable to set $PATH$");
getcolumns();
initsignals();
initfg();
initbg();
- inithistory();
-}
-
-char *catpath(char *dir, char *filename, char *buffer) {
- size_t l;
- int slash;
-
- slash = dir[(l = strlen(dir)) - 1] == '/';
- if (l + slash + strlen(filename) + 1 > PATH_MAX) {
- note("Path name `%s%s%s' too long", dir, slash ? "/" : "", filename);
- return NULL;
- }
-
- strcpy(buffer, dir);
- if (!slash) strcat(buffer, "/");
- strcat(buffer, filename);
-
- return buffer;
+ if (interactive) inithistory();
}
char *quoted(char *token) {
- char *p, *end, quote;
enum {
NONE,
DOUBLE,
ESCAPEDOUBLE,
ANY,
} degree;
+ char *p, *end, quote;
static char buffer[MAXCHARS + 1];
if (!token[0]) return "\"\"";
}
void deinit(void) {
- deinithistory();
+ if (interactive) deinithistory();
deinitbg();
- setconfig(&canonical);
}
+extern int linestart;
extern char *home;
-void note(char *fmt, ...);
-void fatal(char *fmt, ...);
+int usage(char *program, char *options);
+int note(char *format, ...);
+void fatal(char *format, ...);
void init(void);
-char *catpath(char *dir, char *filename, char *buffer);
char *quoted(char *token);
void deinit(void);
} else strcpy(path, "/usr/local/");
strcat(path, "bin/");
- if ((cpid = fork()) == -1) err(EXIT_FAILURE, "Unable to fork");
+ if ((cpid = fork()) == -1) err(errno, "Unable to fork");
if (!cpid) run("/bin/mkdir", LIST("mkdir", "-p", path), "create", path);
await(cpid, "create", path);
strcat(path, "thus");
- if ((cpid = fork()) == -1) err(EXIT_FAILURE, "Unable to fork");
+ if ((cpid = fork()) == -1) err(errno, "Unable to fork");
if (!cpid)
run("/bin/cp", LIST("cp", "-f", "bin/thus", path), "copy", "bin/thus");
await(cpid, "copy", "bin/thus");
-#include <stdlib.h>
-
#include "cbs.c"
-/* C preprocessor being finicky */
-#define STR(x) STRINGIFY(x)
+/* Because of the C preprocessor's convoluted nature, we have to manually expand
+ * `PATH' with the `STR()' macro before stringify it.
+ *
+ * See "C Preprocessor Stringification" reference in `README.md' */
#define STRINGIFY(x) #x
+#define STR(x) STRINGIFY(x)
int main(int argc, char **argv) {
pid_t cpid;
- if (argc != 1) err(EXIT_FAILURE, "usage: %s\n", argv[0]);
+ if (argc != 1) errx(EXIT_FAILURE, "usage: %s\n", argv[0]);
- if ((cpid = fork()) == -1) err(EXIT_FAILURE, "Unable to fork");
+ if ((cpid = fork()) == -1) err(errno, "Unable to fork");
if (!cpid)
run("/bin/rm", LIST("rm", STR(PATH), "uninstall"), "remove", STR(PATH));
await(cpid, "remove", STR(PATH));