# ~.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
#! /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
```
# 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 */
#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.
#include "parse.h"
#include "utils.h"
-#define MAXALIAS 25
+#define MAXALIAS 50
static struct {
struct entry {
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) {
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;
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;
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;
}
}
break;
default:
- return usage(argv[0], "[pgid]");
+ return usage(args[0], "[pgid]");
}
if (killpg(job.id, SIGCONT) == -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, "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");
#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) {
-#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);
#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);
buffer[l] = '/';
break;
default:
- return usage(argv[0], "[directory]");
+ return usage(args[0], "[directory]");
}
if (chdir(path) == -1) {
--- /dev/null
+#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");
+
+ 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;
+}
--- /dev/null
+void execute(struct context *c);
#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);
#include "builtin.h"
#include "context.h"
#include "options.h"
+#include "run.h"
#include "signals.h"
#include "utils.h"
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;
/* 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) {
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);
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");
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;
}
break;
default:
- return usage(argv[0], "[pgid]");
+ return usage(args[0], "[pgid]");
}
if (!runfg(id)) return EXIT_FAILURE;
extern struct termios canonical;
+int setconfig(struct termios *mode);
void initfg(void);
int runfg(pid_t id);
void deinitfg(void);
struct builtin {
char *name;
- BUILTIN((*func));
+ int (*func)(char **args, size_t numargs);
};
extern struct builtin builtins[];
#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;
#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))) {
#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;
#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;
}
return;
}
- source(2, (char *[]){"source", path, NULL});
+ source((char *[]){"source", path, NULL}, 2);
}
#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;
}
#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;
}
#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;
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;
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;
}
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;
}
#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);
#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;
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);
}
}
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;
#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();
#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"
" <file> [arg ...] Run script\n"
" -c <string> [arg ...] Run string\n"
" -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;
}
}
+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);
#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;
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;
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");
}
*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;
if (!quote) break;
switch (*(c->b + 1)) {
case '$':
+ case '~':
case '"':
case '\\':
break;
}
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;
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);
}
#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;
}
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;
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(' ');
}
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) {
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) {
} 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');
}
-extern int verbose;
+extern int verbose, status;
int run(struct context *c);
#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);
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);
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();
-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);
#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] == '/';
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"));
#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;
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;
}