# thus
-While Unix shells are a solved problem in computer science, recent iterations have admittedly been more and more complicated by feature sets that attempt to cater to all users. Ash seeks to be a completely stripped down, simplified approach to shells, only including the essential parts, i.e., just the things *I personally use*. In fact, the main goals for this project in order or priority have been:
+thus is a custom UNIX shell built entirely from scratch for POSIX platforms.
-1. To learn more about the interaction between shells, terminals, and the operating system.
-2. To create a utility I would personally want to use.
-3. To create a utility other people would want to use.
+## Features
-## Feature Set
-
-- Foreground and background process groups (`fg`, `bg`, `&`)
-- Unix pipes
+- Job control (`fg`, `bg`, `&`, `^Z`)
+- Pipelines
- Conditional execution (`&&`, `||`)
-- Shell history (cached in `~/.thushistory`)
-- File redirection
-- Environment variables
-- File globbing
+- File redirection (`<file`, `2>&1`, etc.)
+- Globbing (`*`, `?`, `[...]`)
+- Quoting with escape sequences (`"\r...\n"`)
+- Environment variables (`set`, `unset`, `$VAR$`, etc.)
+- Aliasing (`alias`, `unalias`)
+- Configuration files (`~.thuslogin`, `~.thusrc`)
+- Cached history (`~.thushistory`)
+- [Automated integration of built-in commands](src/builtins/README.md)
## Building
-Similar to my other projects, thus uses [cbs](https://github.com/trenthuber/cbs) as its build system, included as a git submodule, so make sure to clone recursively.
+thus uses [cbs](https://github.com/trenthuber/cbs/) as its build system.
```console
-$ git clone --recursive https://github.com/trenthuber/thus
-$ cd thus
-$ cc -o build build.c
-$ ./build
-$ ./bin/thus
+> git clone --recursive https://github.com/trenthuber/thus/
+> cd thus/
+> cc -o build build.c
+> build
```
-Note, you only need to run the `cc` command the first time you build the project, as the `./build` executable will recompile itself everytime it is run.
+After building, you can use `install` to add the shell to your path. The default installation prefix is `/usr/local/`, but a custom one can be passed as an argument to the utility.
-## Resources
+```console
+> sudo ./install
+```
+
+After installing, you can use `uninstall` to remove the shell from the install location.
+
+```console
+> sudo ./uninstall
+```
+
+## Quirks
-These websites have been invaluable in the making of thus.
+While thus operates for the most part like Bourne shell, there are a few places where it takes a subtly different approach.
+
+### Quotes
+
+Quoting is done with double quotes (`"..."`) and undergoes no shell substitution, similar to single quotes in Bourne shell. In place of substitution, quotes can be concatenated with surrounding tokens not separated by whitespace.
+
+### Environment variables and aliases
+
+Environment variables are referred to by tokens that begin and end with a `$`. For example, evaluating the path would look like `$PATH$`. Setting environment variables is done with the `set` built-in command, not with the `name=value` syntax. This syntax is similarly avoided when declaring aliases with the `alias` built-in command.
+
+### Leading and trailing slashes
+
+Prepending `./` to executables located in the current directory is not mandatory unless there already exists an executable in `$PATH$` with the same name that you would like to override.
+
+The `$HOME$`, `$PWD$`, and `$PATH$` environment variables are always initialized with trailing slashes. Therefore, whenever one of these variables or `~` is substituted in the shell, it will retain the trailing slash.
+
+## Resources
- [TTY Demystified](http://www.linusakesson.net/programming/tty/)
- [Process Groups and Terminal Signaling](https://cs162.org/static/readings/ic221_s16_lec17.html)
+- [Terminal Input Sequences](https://en.wikipedia.org/wiki/ANSI_escape_code#Terminal_input_sequences)
+++ /dev/null
-struct cbsfile {
- char *name, **flags, type;
-};
-
-void buildfiles(struct cbsfile *files) {
- char **c, **l, **names;
- struct cbsfile *target;
- size_t i;
-
- c = cflags;
- l = lflags;
-
- target = files++;
-
- for (i = 0; files[i].name; ++i) if (files[i].flags) {
- cflags = files[i].flags;
- compile(files[i].name);
- }
-
- names = allocate((i + 1) * sizeof *names);
- for (i = 0; files[i].name; ++i) names[i] = files[i].name;
-
- lflags = target->flags;
- load(target->type, target->name, names);
-
- free(names);
-
- cflags = c;
- lflags = l;
-}
#include "../external/cbs/cbs.c"
-#include "../external/cbsext.c"
-#define BUILTINS LIST("-Ibuiltin/")
+#define SRC1 "context", "history", "input"
+#define SRC2 "main", "options", "parse", "run", "utils"
int main(void) {
- build("./");
-
- build("builtin/");
+ char **src;
- buildfiles((struct cbsfile []){{"../bin/thus", NONE, 'x'},
+ build("./");
- {"context", NONE},
- {"history", NONE},
- {"input", NONE},
- {"main", BUILTINS},
- {"options", BUILTINS},
- {"parse", BUILTINS},
- {"run", BUILTINS},
- {"utils", BUILTINS},
+ build("builtins/");
- {"builtin.a"},
+ for (src = LIST(SRC1); *src; ++src) compile(*src);
+ cflags = LIST("-Ibuiltins/");
+ for (src = LIST(SRC2); *src; ++src) compile(*src);
- {NULL}});
+ load('x', "../bin/thus", LIST(SRC1, SRC2, "builtins.a"));
return EXIT_SUCCESS;
}
+++ /dev/null
-# Builtins
-
-## TODO
- - Documentation on how to add builtins of your own
- - unalias builtin
+++ /dev/null
-char *getaliasrhs(char *token);
-char **getalias(char *token);
+++ /dev/null
-#include <err.h>
-#include <fcntl.h>
-#include <dirent.h>
-#include <string.h>
-#include <sys/errno.h>
-
-#include "../../external/cbs/cbs.c"
-#include "../../external/cbsext.c"
-
-#define MAXBUILTINS 50
-
-int main(void) {
- int listfd, l;
- DIR *dir;
- size_t i, offset;
-
- /* The three extra files are:
- * 1) ../libbuiltin.a (target file)
- * 2) list.c (generated by this code)
- * 3) builtin.c (not a builtin, just the API)
- *
- * The remaining files all correspond to shell builtins. */
- struct cbsfile files[3 + MAXBUILTINS + 1];
-
- struct dirent *entry;
- char *name, *identifier;
-
- 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");
-
- dprintf(listfd, "#include <stddef.h>\n\n#include \"builtin.h\"\n"
- "#include \"list.h\"\n\n");
-
- errno = i = 0;
- files[i++] = (struct cbsfile){"../builtin", NONE, 's'};
- files[i++] = (struct cbsfile){"list", NONE};
- offset = i;
- while ((entry = readdir(dir))) {
- if (strcmp(entry->d_name, "build.c") == 0) continue;
- if (!(name = strrchr(entry->d_name, '.')) || strcmp(name, ".c") != 0)
- continue;
- if (i == 3 + MAXBUILTINS + 1)
- errx(EXIT_FAILURE, "Unable to add built-in `%s', maximum reached (%d)",
- name, MAXBUILTINS);
- if (!(name = strdup(entry->d_name)))
- err(EXIT_FAILURE, "Unable to duplicate directory entry");
- name[strlen(name) - strlen(".c")] = '\0';
- if (strcmp(name, "list") == 0) continue;
- if (strcmp(name, "builtin") != 0)
- dprintf(listfd, "extern BUILTIN(%s);\n", name);
- files[i++] = (struct cbsfile){name, LIST("-I../")};
- }
- if (errno) err(EXIT_FAILURE, "Unable to read from current directory");
- files[i] = (struct cbsfile){NULL};
-
- identifier = "struct builtin builtins[] = {";
- l = (int)strlen(identifier);
- dprintf(listfd, "\n%s", identifier);
- for (i = offset; (name = files[i].name); ++i)
- if (strcmp(name, "builtin") != 0 && strcmp(name, "list") != 0)
- dprintf(listfd, "{\"%s\", %s},\n%*s", name, name, l, "");
- dprintf(listfd, "{NULL}};");
-
- if (closedir(dir) == -1)
- err(EXIT_FAILURE, "Unable to close current directory");
- if (close(listfd) == -1) err(EXIT_FAILURE, "Unable to close `list.c'");
-
- buildfiles(files);
-
- while (files[offset].name) free(files[offset++].name);
-
- return EXIT_SUCCESS;
-}
+++ /dev/null
-#include <limits.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <unistd.h>
-
-#include "builtin.h"
-#include "utils.h"
-
-BUILTIN(pwd) {
- char *cwd, buffer[PATH_MAX];
-
- if (argc != 1) return usage(argv[0], NULL);
-
- if (!(cwd = getenv("PWD")) && !(cwd = getcwd(buffer, PATH_MAX)))
- fatal("Unable to get current working directory");
-
- puts(cwd);
-
- return EXIT_SUCCESS;
-}
--- /dev/null
+# 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).
+
+```c
+// foo.c
+
+#include <stdlib.h>
+
+#include "builtin.h"
+#include "utils.h"
+
+BUILTIN(foo) {
+ return EXIT_SUCCESS;
+}
+```
+
+The `BUILTIN()` macro is defined in [`builtin.h`](builtin.h#L1) 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.
+
+Errors should be reported to the user using the `note()` function defined in [`utils.c`](../utils.c#L20).
+
+Once the source is done being written, simply rebuild the shell and it will automatically incorporate the new built-in.
#include "builtin.h"
#include "context.h"
-#include "input.h"
#include "parse.h"
#include "utils.h"
size_t size;
} aliases;
-char *getaliasrhs(char *token) {
+char *getrawalias(char *token) {
size_t i;
for (i = 0; i < aliases.size; ++i)
size_t l;
static struct context context;
- if (!(rhs = getaliasrhs(token))) return NULL;
+ if (!(rhs = getrawalias(token))) return NULL;
+ while (*rhs == ' ') ++rhs;
strcpy(context.buffer, rhs);
l = strlen(rhs);
context.buffer[l + 1] = '\0';
context.b = context.buffer;
if (!parse(&context)) return NULL;
+ if (!context.t) *context.tokens = NULL;
return context.tokens;
}
note("Unable to add alias `%s', maximum reached (%d)", argv[1], MAXALIAS);
return EXIT_FAILURE;
}
- if (!*argv[2]) {
- note("Cannot add empty alias");
- return EXIT_FAILURE;
- }
for (i = 0; i < aliases.size; ++i)
if (strcmp(aliases.entries[i].lhs, argv[1]) == 0) break;
--- /dev/null
+char *getrawalias(char *token);
+char **getalias(char *token);
#include <limits.h>
#include <signal.h>
#include <stdlib.h>
-#include <string.h>
#include <sys/errno.h>
#include <sys/wait.h>
#include <termios.h>
};
static struct {
- struct bglink entries[MAXBG + 1], *active, *free;
+ struct bglink entries[MAXBG], *active, *free;
} bgjobs;
void initbg(void) {
int peekbg(struct bgjob *job) {
if (bgjobs.active && job) *job = bgjobs.active->job;
+
return bgjobs.active != NULL;
}
e = errno;
p = bgjobs.active;
while (p) {
- while ((id = waitpid(-p->job.id, &s, WNOHANG | WUNTRACED)) > 0) {
+ while ((id = waitpid(-p->job.id, &s, WNOHANG | WUNTRACED)) > 0)
if (WIFSTOPPED(s)) {
p->job.suspended = 1;
break;
}
- }
if (id == -1) {
id = p->job.id;
p = p->next;
--- /dev/null
+#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, l;
+ DIR *dir;
+ char *src[MAXBUILTINS + 2 + 1], **p;
+ 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");
+
+ dprintf(listfd, "#include <stddef.h>\n\n#include \"builtin.h\"\n"
+ "#include \"list.h\"\n\n");
+
+ p = src;
+ errno = 0;
+ while ((entry = readdir(dir))) {
+ if (strcmp(entry->d_name, "build.c") == 0
+ || !(*p = strrchr(entry->d_name, '.')) || strcmp(*p, ".c") != 0)
+ continue;
+ if (!(*p = strdup(entry->d_name)))
+ err(EXIT_FAILURE, "Unable to duplicate directory entry");
+ (*p)[strlen(*p) - 2] = '\0';
+ if (p - src == 2 + MAXBUILTINS + 1)
+ errx(EXIT_FAILURE, "Unable to add %s built-in, maximum reached (%d)",
+ *p, MAXBUILTINS);
+ if (strcmp(*p, "builtin") != 0 && strcmp(*p, "list") != 0)
+ dprintf(listfd, "extern BUILTIN(%s);\n", *p);
+ ++p;
+ }
+ if (errno) err(EXIT_FAILURE, "Unable to read from current directory");
+
+ *p = "struct builtin builtins[] = {";
+ l = (int)strlen(*p);
+ dprintf(listfd, "\n%s", *p);
+ *p = NULL;
+ for (p = src; *p; ++p)
+ if (strcmp(*p, "builtin") != 0 && strcmp(*p, "list") != 0)
+ dprintf(listfd, "{\"%s\", %s},\n%*s", *p, *p, l, "");
+ dprintf(listfd, "{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'");
+
+ cflags = LIST("-I../");
+ for (p = src; *p; ++p) compile(*p);
+ load('s', "../builtins", src);
+
+ for (p = src; *p; ++p) free(*p);
+
+ return EXIT_SUCCESS;
+}
#include "utils.h"
int isbuiltin(char **args) {
- struct builtin *builtinp;
+ struct builtin *builtin;
size_t n;
- for (builtinp = builtins; builtinp->func; ++builtinp)
- if (strcmp(args[0], builtinp->name) == 0) {
+ for (builtin = builtins; builtin->func; ++builtin)
+ if (strcmp(args[0], builtin->name) == 0) {
for (n = 1; args[n]; ++n);
- status = builtinp->func(n, args);
+ status = builtin->func(n, args);
return 1;
}
}
int usage(char *program, char *options) {
- fprintf(stderr, "Usage: %s", program);
+ fprintf(stderr, "usage: %s", program);
if (options) fprintf(stderr, " %s", options);
fputc('\n', stderr);
#include <limits.h>
-#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "bg.h"
#include "builtin.h"
-#include "context.h"
-#include "input.h"
#include "utils.h"
static struct {
--- /dev/null
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "builtin.h"
+#include "utils.h"
+
+BUILTIN(pwd) {
+ char *cwd, buffer[PATH_MAX];
+ size_t l;
+
+ if (argc != 1) return usage(argv[0], NULL);
+
+ if (!(cwd = getenv("PWD"))) {
+ if (!(cwd = getcwd(buffer, PATH_MAX))) {
+ note("Unable to get current working directory");
+ return EXIT_FAILURE;
+ }
+ l = strlen(cwd);
+ if (cwd[l - 1] != '/') {
+ cwd[l] = '/';
+ cwd[l + 1] = '\0';
+ }
+ if (setenv("PWD", cwd, 1) == -1) {
+ note("Unable to set $PWD$");
+ return EXIT_FAILURE;
+ }
+ }
+
+ puts(cwd);
+
+ return EXIT_SUCCESS;
+}
#include <fcntl.h>
#include <limits.h>
-#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "builtin.h"
#include "context.h"
#include "input.h"
-#include "parse.h"
#include "run.h"
#include "utils.h"
#include "utils.h"
enum {
+ ALIAS,
BUILTIN,
PATH,
- ALIAS,
};
+static int type;
+
static int exists(char *path) {
struct stat pstat;
mode_t mask;
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;
return 0;
}
-static char *getpathtype(char *file, int *type) {
+char *getpath(char *file) {
char *slash, *entry, *end, dir[PATH_MAX];
struct builtin *builtin;
size_t l;
static char path[PATH_MAX];
- *type = PATH;
+ type = PATH;
if (!(slash = strchr(file, '/'))) {
- if ((entry = getaliasrhs(file))) {
- *type = ALIAS;
+ if ((entry = getrawalias(file))) {
+ type = ALIAS;
return entry;
}
for (builtin = builtins; builtin->func; ++builtin)
if (strcmp(file, builtin->name) == 0) {
- *type = BUILTIN;
+ type = BUILTIN;
return file;
}
return NULL;
}
-char *getpath(char *file) {
- int type;
-
- return getpathtype(file, &type);
-}
-
BUILTIN(which) {
- int type;
char *result;
if (argc != 2) return usage(argv[0], "name");
- if (!(result = getpathtype(argv[1], &type))) {
+ if (!(result = getpath(argv[1]))) {
printf("%s not found\n", argv[1]);
return EXIT_SUCCESS;
}
-#include <stdlib.h>
+#include <stddef.h>
#include "context.h"
#include "input.h"
#include <sys/errno.h>
#include "context.h"
-#include "input.h"
+#include "options.h"
#include "utils.h"
#define MAXHIST 100
-#define INC(v) (history.v = (history.v + 1) % (MAXHIST + 1))
-#define DEC(v) (history.v = (history.v + MAXHIST) % (MAXHIST + 1))
+#define INC(x) (history.x = (history.x + 1) % (MAXHIST + 1))
+#define DEC(x) (history.x = (history.x + MAXHIST) % (MAXHIST + 1))
static struct {
char path[PATH_MAX], entries[MAXHIST + 1][MAXCHARS + 1];
void inithistory(void) {
FILE *file;
+ if (!interactive) return;
+
if (!catpath(home, ".thushistory", history.path)) exit(EXIT_FAILURE);
if (!(file = fopen(history.path, "r"))) {
if (errno == ENOENT) return;
int fd;
FILE *file;
+ if (!interactive) return;
+
if ((fd = open(history.path, O_WRONLY | O_CREAT | O_TRUNC, 0600)) == -1) {
note("Unable to open history file for writing");
return;
#include <fcntl.h>
-#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "context.h"
#include "history.h"
-#include "input.h"
#include "utils.h"
enum {
/* This is a very minimal way to handle arrow keys. All modifiers except for
* the ALT key are processed but ignored.
*
- * Reference:
- * https://en.wikipedia.org/wiki/ANSI_escape_code#Terminal_input_sequences */
+ * See "Terminal Input Sequences" reference in `README.md'. */
case ESCAPE:
if ((current = getchar()) == '[') {
while ((current = getchar()) >= '0' && current <= '9');
-#include <stdio.h>
#include <stdlib.h>
+#include <string.h>
#include <unistd.h>
#include "builtin.h"
int login, interactive;
void options(struct context *context) {
- int opt, l;
- char *message = "[file | -c string] [arg ...] [-hl]\n"
- " <file> [arg ...] Run script with args\n"
- " -c <string> [arg ...] Run string with args\n"
- " -h Show this help message\n"
- " -l Run as a login shell\n";
+ int opt;
+ char *p, *message = "[file | -c string] [arg ...] [-hl]\n"
+ " <file> [arg ...] Run script\n"
+ " -c <string> [arg ...] Run string\n"
+ " -h Show this help message\n"
+ " -l Run as a login shell";
-
- login = **arglist == '-';
+ opt = 0;
+ if (*arglist[0] == '-') {
+ ++arglist[0];
+ login = 1;
+ }
+ if ((p = strrchr(arglist[0], '/'))) arglist[0] = p + 1;
interactive = 1;
context->input = userinput;
- while ((opt = getopt(argcount, arglist, ":c:hl")) != -1) {
+ while (opt != 'c' && (opt = getopt(argcount, arglist, ":c:hl")) != -1)
switch (opt) {
case 'c':
interactive = 0;
note("Unknown command line option `-%c'\n", optopt);
exit(usage(arglist[0], message));
}
- if (opt == 'c') break;
- }
if (!context->string && arglist[optind]) {
interactive = 0;
context->script = arglist[optind];
#include "alias.h"
#include "context.h"
-#include "options.h"
#include "utils.h"
int parse(struct context *c) {
#include <fcntl.h>
-#include <limits.h>
#include <signal.h>
#include <stdlib.h>
-#include <string.h>
#include <sys/errno.h>
#include <sys/wait.h>
#include <termios.h>
#include <unistd.h>
-#include <stdio.h> // XXX
+#include "bg.h"
#include "builtin.h"
#include "context.h"
-#include "bg.h"
#include "fg.h"
#include "parse.h"
#include "utils.h"
}
static void exec(char *path, struct context *c) {
- char cwd[PATH_MAX];
-
redirectfiles(c->redirects);
if (isbuiltin(c->tokens)) exit(status);
islist = c->prev.term > BG || c->current.term > BG;
if (c->t) {
if (c->current.term == BG && fullbg()) {
- note("Unable to place job in background, too many background jobs",
- c->current.name);
+ note("Unable to place job in background, too many background jobs");
return quit(c);
}
if (!(path = getpath(c->current.name))) {
-#include <err.h>
#include <limits.h>
#include <stdarg.h>
#include <stdio.h>
#include <unistd.h>
#include "bg.h"
-#include "context.h"
#include "fg.h"
#include "history.h"
-#include "options.h"
int argcount, status;
char **arglist, *home;
void note(char *fmt, ...) {
va_list args;
+
+ fprintf(stderr, "%s: ", arglist[0]);
+
va_start(args, fmt);
- (errno ? vwarn : vwarnx)(fmt, args);
+ vfprintf(stderr, fmt, args);
va_end(args);
- errno = 0;
+
+ if (errno) {
+ fprintf(stderr, ": %s", strerror(errno));
+ errno = 0;
+ }
+
+ putchar('\n');
}
void fatal(char *fmt, ...) {
va_list args;
+
+ fprintf(stderr, "%s: ", arglist[0]);
+
va_start(args, fmt);
- (errno ? verr : verrx)(EXIT_FAILURE, fmt, args);
+ vfprintf(stderr, fmt, args);
+ va_end(args);
+
+ if (errno) fprintf(stderr, ": %s", strerror(errno));
+
+ putchar('\n');
+
+ exit(EXIT_FAILURE);
}
void init(void) {
if (setenv("PWD", buffer, 1) == -1)
fatal("Unable to append trailing slash to $PWD$");
+ if (setenv("PATH", "/usr/local/bin/:/usr/local/sbin/"
+ ":/usr/bin/:/usr/sbin/:/bin/:/sbin/", 1) == -1)
+ fatal("Unable to initialize $PATH$");
+
if (!(shlvlstr = getenv("SHLVL"))) shlvlstr = "0";
if ((shlvl = strtol(shlvlstr, NULL, 10)) < 0) shlvl = 0;
sprintf(buffer, "%ld", shlvl + 1);
initfg();
initbg();
- if (interactive) inithistory();
+ inithistory();
}
char *catpath(char *dir, char *filename, char *buffer) {
}
void deinit(void) {
- if (interactive) deinithistory();
+ deinithistory();
deinitbg();
deinitfg();
}
int slash;
pid_t cpid;
- if (argc > 2) errx(EXIT_FAILURE, "%s [prefix]", argv[0]);
+ if (argc > 2) errx(EXIT_FAILURE, "usage: %s [prefix]", argv[0]);
path = stpcpy(define, "-DPATH=\"");
if (argc == 2) {
#include "cbs.c"
-int main(void) {
+int main(int argc, char **argv) {
pid_t cpid;
+ if (argc != 1) err(EXIT_FAILURE, "usage: %s\n", argv[0]);
+
if ((cpid = fork()) == -1) err(EXIT_FAILURE, "Unable to fork");
else if (cpid == 0)
run("/bin/rm", LIST("rm", PATH, "uninstall"), "remove", PATH);