-# Ash
+# 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:
- Foreground and background process groups (`fg`, `bg`, `&`)
- Unix pipes
- Conditional execution (`&&`, `||`)
-- Shell history (cached in `~/.ashhistory`)
-
-### Future features
-
+- Shell history (cached in `~/.thushistory`)
- File redirection
- Environment variables
-- Scripting
- File globbing
## Building
-Similar to my other projects, ash uses [cbs](https://github.com/trenthuber/cbs) as its build system, included as a git submodule, so make sure to clone recursively.
+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.
```console
-$ git clone --recursive https://github.com/trenthuber/ash
-$ cd ash
+$ git clone --recursive https://github.com/trenthuber/thus
+$ cd thus
$ cc -o build build.c
$ ./build
-$ ./bin/ash
+$ ./bin/thus
```
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.
## Resources
-These websites have been invaluable in the making of ash.
+These websites have been invaluable in the making of thus.
- [TTY Demystified](http://www.linusakesson.net/programming/tty/)
- [Process Groups and Terminal Signaling](https://cs162.org/static/readings/ic221_s16_lec17.html)
build("builtin/");
- buildfiles((struct cbsfile []){{"../bin/ash", NONE, 'x'},
+ buildfiles((struct cbsfile []){{"../bin/thus", NONE, 'x'},
{"context", NONE},
{"history", NONE},
{"input", NONE},
{"job", NONE},
{"main", BUILTINS},
- {"options", NONE},
+ {"options", BUILTINS},
{"parse", BUILTINS},
{"run", BUILTINS},
{"utils", BUILTINS},
## TODO
- Documentation on how to add builtins of your own
- - Reworking aliasing stuff
- - unset
- - which needs to test for aliases
- - pwd
+ - unalias builtin
break;
default:
- fputs("Usage: alias [lhs rhs]\r\n", stderr);
- return EXIT_FAILURE;
+ return usage(argv[0], "[lhs rhs]");
}
return EXIT_SUCCESS;
pid_t id;
struct job *job;
- if (argc > 1) {
+ switch (argc) {
+ case 1:
+ if (!(job = peeksuspendedjob())) return EXIT_FAILURE;
+ break;
+ case 2:
errno = 0;
if ((l = strtol(argv[1], NULL, 10)) == LONG_MAX && errno || l <= 0) {
note("Invalid job id %ld", l);
note("Job %d already in background", id);
return EXIT_FAILURE;
}
- } else if (!(job = peeksuspendedjob())) return EXIT_FAILURE;
+ break;
+ default:
+ return usage(argv[0], "[pgid]");
+ }
deletejobid(job->id);
if (!pushjob(job)) return EXIT_FAILURE;
+#include <stdio.h>
+#include <stdlib.h>
#include <string.h>
#include "builtin.h"
size_t n;
for (builtinp = builtins; builtinp->func; ++builtinp)
- if (strcmp(*args, builtinp->name) == 0) {
- for (n = 0; args[n]; ++n);
+ if (strcmp(args[0], builtinp->name) == 0) {
+ for (n = 1; args[n]; ++n);
status = builtinp->func(n, args);
return 1;
}
return 0;
}
+
+int usage(char *program, char *options) {
+ fprintf(stderr, "Usage: %s %s\r\n", program, options);
+ return EXIT_FAILURE;
+}
#define BUILTIN(name) int name(int argc, char **argv)
int isbuiltin(char **args);
+int usage(char *program, char *options);
+#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
+#include <string.h>
#include <unistd.h>
#include "builtin.h"
#include "utils.h"
BUILTIN(cd) {
- char *path;
+ char *path, buffer[PATH_MAX + 1];
+ size_t l;
- if (argc > 2) {
- fputs("Usage: cd [directory]\r\n", stderr);
- return EXIT_FAILURE;
+ switch (argc) {
+ case 1:
+ path = home;
+ break;
+ case 2:
+ if (!realpath(argv[1], path = buffer)) {
+ note(argv[1]);
+ return EXIT_FAILURE;
+ }
+ l = strlen(path);
+ path[l + 1] = '\0';
+ path[l] = '/';
+ break;
+ default:
+ return usage(argv[0], "[directory]");
}
- path = argc == 1 ? home : argv[1];
-
if (chdir(path) == -1) {
- note("Unable to change directory to `%s'", path);
+ note(path);
return EXIT_FAILURE;
}
- if (setenv("PWD", path, 1) == -1) { // TODO: Slash-terminate path
- note("Unable to change $PWD$ to `%s'", path);
+ if (setenv("PWD", path, 1) == -1) {
+ note("Unable to set $PWD$");
return EXIT_FAILURE;
}
#include <sys/errno.h>
#include <termios.h>
#include <unistd.h>
-#include <string.h> // XXX
#include "builtin.h"
#include "job.h"
struct sigaction sigchld, sigdfl;
static int setconfig(struct termios *mode) {
-// printf("Setting config to %s\r\n", mode == &raw ? "raw" : "canonical");
if (tcsetattr(STDIN_FILENO, TCSANOW, mode) == -1) {
note("Unable to set termios config");
return 0;
}
int setfg(struct job job) {
-// puts("setfg\r");
if (!setconfig(&job.config)) return 0;
if (tcsetpgrp(STDIN_FILENO, job.id) == -1) {
note("Unable to bring job %d to foreground", job.id);
}
if (tcgetattr(STDIN_FILENO, &job.config) == -1)
note("Unable to save termios config of job %d", job.id);
-// puts("waitfg\r");
setconfig(&raw);
-// printf("fgstatus = %d\r\n", fgstatus);
if (WIFEXITED(fgpg.status)) status = WEXITSTATUS(fgpg.status);
- else if (WIFSIGNALED(fgpg.status)) { puts("SIGNAL RECEIVED\r"); status = WTERMSIG(fgpg.status); }
- else {
+ else if (WIFSTOPPED(fgpg.status)) {
status = WSTOPSIG(fgpg.status);
job.suspended = 1;
if (!pushjob(&job)) {
if (setfg(job)) return waitfg(job);
note("Manual intervention required for job %d", job.id);
}
- }
-// printf("status = %d\r\n", status);
+ } else status = WTERMSIG(fgpg.status);
}
void deinitfg(void) {
-// puts("deinitfg");
setconfig(&canonical);
}
pid_t id;
struct job *job;
- if (argc > 1) {
+ switch (argc) {
+ case 1:
+ if (!(job = pulljob())) {
+ note("No job to bring into the foreground");
+ return EXIT_FAILURE;
+ }
+ break;
+ case 2:
errno = 0;
if ((l = strtol(argv[1], NULL, 10)) == LONG_MAX && errno || l <= 0) {
note("Invalid process group id %ld", l);
return EXIT_FAILURE;
}
deletejobid(id);
- } else if (!(job = pulljob())) {
- note("No job to bring into the foreground");
- return EXIT_FAILURE;
+ break;
+ default:
+ return usage(argv[0], "[pgid]");
}
if (!setfg(*job)) return EXIT_FAILURE;
-#include <stdio.h>
#include <stdlib.h>
#include "builtin.h"
#include "utils.h"
-extern char **environ;
-
BUILTIN(set) {
- char **e;
-
- if (argc != 3) {
- fputs("Usage: set [name value]\r\n", stderr);
- return EXIT_FAILURE;
- }
+ if (argc != 3) return usage(argv[0], "name value");
if (setenv(argv[1], argv[2], 1) == -1) {
note("Unable to set %s to %s", argv[1], argv[2]);
-#include <stdio.h>
#include <stdlib.h>
#include "builtin.h"
#include "utils.h"
BUILTIN(unset) {
- if (argc != 2) {
- fputs("Usage: unset [name]\r\n", stderr);
- return EXIT_FAILURE;
- }
+ if (argc != 2) return usage(argv[0], "name");
if (!getenv(argv[1])) {
note("Environment variable does not exist");
int type;
char *result;
- if (argc != 2) {
- fputs("Usage: which name\r\n", stderr);
- return EXIT_FAILURE;
- }
+ if (argc != 2) return usage(argv[0], "name");
if (!(result = getpathtype(argv[1], &type))) {
printf("%s not found\r\n", argv[1]);
init();
- if (login) config(".ashlogin");
- if (interactive) config(".ashrc");
+ if (login) config(".thuslogin");
+ if (interactive) config(".thusrc");
while (run(&context));
#include <stdlib.h>
#include <unistd.h>
+#include "builtin.h"
#include "context.h"
#include "input.h"
#include "utils.h"
int login, interactive;
-static void usage(char *program, int code) {
- printf("Usage: %s [file] [-c string] [-hl]\n"
- " <file> ... Run script\n"
- " -c <string> ... Run commands\n"
- " -h Show this help message\n"
- " -l Run as a login shell\n", program);
- exit(code);
-}
-
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";
+
login = **arglist == '-';
interactive = 1;
arglist[--optind] = ""; // Empty program name when running a string
break;
case 'h':
- usage(*arglist, EXIT_SUCCESS);
+ usage(arglist[0], message);
+ exit(EXIT_SUCCESS);
case 'l':
login = 1;
break;
case ':':
note("Expected argument following `-%c'\n", optopt);
- usage(*arglist, EXIT_FAILURE);
+ exit(usage(arglist[0], message));
case '?':
default:
note("Unknown command line option `-%c'\n", optopt);
- usage(*arglist, EXIT_FAILURE);
+ exit(usage(arglist[0], message));
}
if (opt == 'c') break;
}
size_t l;
long shlvl;
- if (!(home = getenv("HOME")))
- fatal("Unable to query $HOME$");
+ if (!(home = getenv("HOME"))) fatal("Unable to find home directory");
strcpy(buffer, home);
l = strlen(buffer);
buffer[l + 1] = '\0';
if (setenv("HOME", buffer, 1) == -1 || !(home = getenv("HOME")))
fatal("Unable to append trailing slash to $HOME$");
+ if (!getcwd(buffer, PATH_MAX)) fatal("Unable to find current directory");
+ l = strlen(buffer);
+ buffer[l + 1] = '\0';
+ buffer[l] = '/';
+ if (setenv("PWD", buffer, 1) == -1)
+ fatal("Unable to append trailing slash to $PWD$");
+
if (!(shlvlstr = getenv("SHLVL"))) shlvlstr = "0";
if ((shlvl = strtol(shlvlstr, NULL, 10)) < 0) shlvl = 0;
sprintf(buffer, "%ld", shlvl + 1);