-Subproject commit 5ab49da8d4ab0b8e6cb23c3cb1f927e4402db4fc
+Subproject commit beedfb79168cadd181b26ee46e50a01a6a44de52
#include "../external/cbs/cbs.c"
-#include "../external/cbsfile.c"
+#include "../external/cbsext.c"
#define BUILTINS LIST("-Ibuiltin/")
buildfiles((struct cbsfile []){{"../bin/ash", NONE, 'x'},
+ {"context", NONE},
{"history", NONE},
{"input", NONE},
{"job", NONE},
{"main", BUILTINS},
{"options", NONE},
- {"parse", NONE},
+ {"parse", BUILTINS},
{"run", BUILTINS},
{"utils", BUILTINS},
--- /dev/null
+# Builtins
+
+## TODO
+ - Documentation on how to add builtins of your own
+ - Reworking aliasing stuff
+ - unset
+ - which needs to test for aliases
+ - pwd
static struct {
struct {
char lhs[MAXCHARS - 5], *rhs;
- struct context context;
} entries[MAXALIAS + 1];
size_t size;
} aliases;
-void applyaliases(struct command *command) {
- struct command *c;
- char **end;
- size_t i, l, a;
- struct context *context;
+char *getaliasrhs(char *token) {
+ size_t i;
- c = command;
+ for (i = 0; i < aliases.size; ++i)
+ if (strcmp(token, aliases.entries[i].lhs) == 0) return aliases.entries[i].rhs;
- end = c->args;
- while ((c = c->next)) if (c->args) end = c->args;
- if (end) while (*end) ++end;
+ return NULL;
+}
- while ((c = command = command->next)) {
- if (!command->args) continue;
- for (i = 0; i < aliases.size; ++i)
- if (strcmp(aliases.entries[i].lhs, *command->args) == 0) break;
- if (i == aliases.size) continue;
- context = &aliases.entries[i].context;
-
- strcpy(context->buffer, aliases.entries[i].rhs);
- l = strlen(context->buffer);
- context->buffer[l + 1] = '\0';
- context->buffer[l] = ';';
-
- parse(context);
-
- for (a = 0; context->tokens[a]; ++a);
- memmove(command->args + a, command->args + 1,
- (end - command->args + 1) * sizeof*command->args);
- memcpy(command->args, context->tokens, a * sizeof*command->args);
- while ((c = c->next)) c->args += a - 1;
- }
+char **getalias(char *token) {
+ char *rhs;
+ size_t l;
+ static struct context context;
+
+ if (!(rhs = getaliasrhs(token))) return NULL;
+
+ strcpy(context.buffer, rhs);
+ l = strlen(rhs);
+ context.buffer[l + 1] = '\0';
+ context.buffer[l] = ';';
+ context.alias = 1;
+ context.b = context.buffer;
+
+ if (!parse(&context)) return NULL;
+
+ return context.tokens;
}
-BUILTINSIG(alias) {
+BUILTIN(alias) {
size_t i;
char *lhs;
break;
default:
- puts("Usage: alias [lhs rhs]\r");
+ fputs("Usage: alias [lhs rhs]\r\n", stderr);
return EXIT_FAILURE;
}
-void applyaliases(struct command *command);
+char *getaliasrhs(char *token);
+char **getalias(char *token);
#include "job.h"
#include "utils.h"
-BUILTINSIG(bg) {
+BUILTIN(bg) {
long l;
pid_t id;
struct job *job;
note("Unable to find job %d", id);
return EXIT_FAILURE;
}
- if (job->type == BACKGROUND) {
+ if (!job->suspended) {
note("Job %d already in background", id);
return EXIT_FAILURE;
}
- } else if (!(job = searchjobtype(SUSPENDED))) {
- note("No suspended jobs to run in background");
- return EXIT_FAILURE;
- }
+ } else if (!(job = peeksuspendedjob())) return EXIT_FAILURE;
deletejobid(job->id);
- if (!pushjob(job)) {
- note("Unable to add job to background; too many jobs");
- return EXIT_FAILURE;
- }
+ if (!pushjob(job)) return EXIT_FAILURE;
if (killpg(job->id, SIGCONT) == -1) {
- note("Unable to wake up suspended process group %d", job->id);
+ note("Unable to wake up suspended job %d", job->id);
return EXIT_FAILURE;
}
- job->type = BACKGROUND;
+ job->suspended = 0;
return EXIT_SUCCESS;
}
#include <sys/errno.h>
#include "../../external/cbs/cbs.c"
-#include "../../external/cbsfile.c"
+#include "../../external/cbsext.c"
#define MAXBUILTINS 50
DIR *dir;
size_t i, offset;
- /* The three extra files correspond to:
- * 1) output file (../libbuiltin.a)
- * 2) list.c
- * 3) builtin.c */
+ /* 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;
name[strlen(name) - strlen(".c")] = '\0';
if (strcmp(name, "list") == 0) continue;
if (strcmp(name, "builtin") != 0)
- dprintf(listfd, "extern BUILTINSIG(%s);\n", name);
+ 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");
-#define BUILTINSIG(name) int name(int argc, char **argv)
+#define BUILTIN(name) int name(int argc, char **argv)
int isbuiltin(char **args);
+#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "builtin.h"
#include "utils.h"
-BUILTINSIG(cd) {
+BUILTIN(cd) {
char *path;
+ if (argc > 2) {
+ fputs("Usage: cd [directory]\r\n", stderr);
+ return EXIT_FAILURE;
+ }
+
path = argc == 1 ? home : argv[1];
if (chdir(path) == -1) {
return EXIT_FAILURE;
}
- if (setenv("PWD", path, 1) == -1) {
+ if (setenv("PWD", path, 1) == -1) { // TODO: Slash-terminate path
note("Unable to change $PWD$ to `%s'", path);
return EXIT_FAILURE;
}
#include <sys/errno.h>
#include <termios.h>
#include <unistd.h>
+#include <string.h> // XXX
#include "builtin.h"
#include "job.h"
#include "utils.h"
-static int fgstatus;
+static struct {
+ pid_t id;
+ int status, done;
+} fgpg;
struct termios canonical;
static struct termios raw;
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;
}
static void sigchldhandler(int sig) {
+ int e;
pid_t id;
struct job *job;
(void)sig;
- while ((id = waitpid(-1, &fgstatus, WNOHANG | WUNTRACED)) > 0)
+ e = errno;
+ while ((id = waitpid(-1, &fgpg.status, WNOHANG | WUNTRACED)) > 0)
if ((job = searchjobid(id))) {
- if (WIFSTOPPED(fgstatus)) job->type = SUSPENDED; else deletejobid(id);
- } else if (!WIFSTOPPED(fgstatus)) while (waitpid(-id, NULL, 0) != -1);
+ if (WIFSTOPPED(fgpg.status)) job->suspended = 1; else deletejobid(id);
+ } else {
+ fgpg.done = 1;
+ if (WIFSTOPPED(fgpg.status)) continue;
+ if (id != fgpg.id) waitpid(fgpg.id, &fgpg.status, 0);
+ while (waitpid(-fgpg.id, NULL, 0) != -1);
+ }
+ errno = e;
}
void setsigchld(struct sigaction *act) {
}
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);
/* SIGCHLD handler is really the function that reaps the foreground process,
* the waitpid() below is just to block the current thread of execution until
- * the foreground process has been reaped */
+ * the foreground process has been reaped. */
+ fgpg.id = job.id;
setsigchld(&sigchld);
- waitpid(job.id, NULL, 0);
- errno = 0; // waitpid() will set errno
+ while (waitpid(fgpg.id, NULL, 0) && !fgpg.done);
+ fgpg.done = errno = 0;
setsigchld(&sigdfl);
if (sigaction(SIGTTOU, &(struct sigaction){{SIG_IGN}}, NULL) == -1
}
if (tcgetattr(STDIN_FILENO, &job.config) == -1)
note("Unable to save termios config of job %d", job.id);
+// puts("waitfg\r");
setconfig(&raw);
- if (WIFEXITED(fgstatus)) status = WEXITSTATUS(fgstatus);
- else if (WIFSIGNALED(fgstatus)) status = WTERMSIG(fgstatus);
+// 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 {
- status = WSTOPSIG(fgstatus);
- job.type = SUSPENDED;
+ status = WSTOPSIG(fgpg.status);
+ job.suspended = 1;
if (!pushjob(&job)) {
- note("Unable to add job %d to list; too many jobs\r\n"
- "Press any key to continue", job.id);
+ note("Press any key to continue");
getchar();
if (setfg(job)) return waitfg(job);
note("Manual intervention required for job %d", job.id);
}
}
+// printf("status = %d\r\n", status);
}
void deinitfg(void) {
+// puts("deinitfg");
setconfig(&canonical);
}
-BUILTINSIG(fg) {
+BUILTIN(fg) {
long l;
pid_t id;
struct job *job;
return EXIT_FAILURE;
}
if (!(job = searchjobid(id))) {
- note("Unable to find process group %d", id);
+ note("Unable to find job %d", id);
return EXIT_FAILURE;
}
deletejobid(id);
} else if (!(job = pulljob())) {
- note("No processes to bring into the foreground");
+ note("No job to bring into the foreground");
return EXIT_FAILURE;
}
struct builtin {
char *name;
- BUILTINSIG((*func));
+ BUILTIN((*func));
};
extern struct builtin builtins[];
--- /dev/null
+#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 (setenv(argv[1], argv[2], 1) == -1) {
+ note("Unable to set %s to %s", argv[1], argv[2]);
+ return EXIT_FAILURE;
+ }
+
+ return EXIT_SUCCESS;
+}
#include "run.h"
#include "utils.h"
-BUILTINSIG(source) {
+BUILTIN(source) {
struct context context;
-
- if (argc != 2) {
- note("Usage: source file");
+ int c;
+ char **v;
+
+ if (argc == 1) {
+ fputs("Usage: source file [args ...]\r\n", stderr);
return EXIT_FAILURE;
}
+ context = (struct context){0};
context.script = argv[1];
context.input = scriptinput;
- while (run(parse(context.input(&context))));
+
+ c = argcount;
+ v = arglist;
+ argcount = argc - 1;
+ arglist = argv + 1;
+
+ while (run(&context));
+
+ argcount = c;
+ arglist = v;
return EXIT_SUCCESS;
}
void config(char *name) {
char path[PATH_MAX];
- source(2, (char *[]){name, catpath(home, name, path), NULL});
+ source(2, (char *[]){"source", catpath(home, name, path), NULL});
}
--- /dev/null
+#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 (!getenv(argv[1])) {
+ note("Environment variable does not exist");
+ return EXIT_FAILURE;
+ }
+ if (unsetenv(argv[1]) == -1) {
+ note("Unable to unset $%s$", argv[1]);
+ return EXIT_FAILURE;
+ }
+
+ return EXIT_SUCCESS;
+}
#include <sys/errno.h>
#include <sys/stat.h>
+#include "alias.h"
#include "builtin.h"
#include "list.h"
#include "utils.h"
-BUILTINSIG(which) {
- int result;
- size_t i, l;
- struct builtin *builtin;
- char *entry, *end, dir[PATH_MAX], path[PATH_MAX];
+enum {
+ BUILTIN,
+ PATH,
+ ALIAS,
+};
+
+static int exists(char *path) {
struct stat pstat;
-
- if (argc == 1) return EXIT_FAILURE;
+ 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);
+
+ return 0;
+}
+
+static char *getpathtype(char *file, int *type) {
+ 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 = getaliasrhs(file))) {
+ *type = ALIAS;
+ return entry;
+ }
- result = EXIT_SUCCESS;
- for (i = 1; argv[i]; ++i) {
for (builtin = builtins; builtin->func; ++builtin)
- if (strcmp(argv[i], builtin->name) == 0) {
- printf("%s is built-in\r\n", argv[i]);
- break;
+ if (strcmp(file, builtin->name) == 0) {
+ *type = BUILTIN;
+ return file;
}
- if (builtin->func) continue;
if (!(entry = getenv("PATH"))) {
note("Unable to examine $PATH$");
- return EXIT_FAILURE;
+ return NULL;
}
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, argv[i], path)) return EXIT_FAILURE;
- if (stat(path, &pstat) != -1) {
- if (pstat.st_mode & S_IXUSR) {
- printf("%s\r\n", path);
- break;
- }
- } else if (errno != ENOENT) {
- note("Unable to check if `%s' exists", path);
- return EXIT_FAILURE;
- }
+ if (!catpath(dir, file, path)) return NULL;
+ if (exists(path)) return path;
}
- if (entry != end + 1) continue;
+ }
- printf("%s not found\r\n", argv[i]);
- result = EXIT_FAILURE;
+ if (!realpath(file, path)) {
+ if (errno != ENOENT) note("Unable to expand `%s'", file); else errno = 0;
+ return NULL;
}
+ if (exists(path)) return path;
+
+ return NULL;
+}
+
+char *getpath(char *file) {
+ int type;
+
+ return getpathtype(file, &type);
+}
+
+BUILTIN(which) {
+ int type;
+ char *result;
+
+ if (argc != 2) {
+ fputs("Usage: which name\r\n", stderr);
+ return EXIT_FAILURE;
+ }
+
+ if (!(result = getpathtype(argv[1], &type))) {
+ printf("%s not found\r\n", argv[1]);
+ return EXIT_SUCCESS;
+ }
+
+ fputs(result, stdout);
+ if (type == BUILTIN) fputs(" is built-in", stdout);
+ puts("\r");
- return result;
+ return EXIT_SUCCESS;
}
--- /dev/null
+char *getpath(char *file);
--- /dev/null
+#include <stdlib.h>
+
+#include "context.h"
+#include "input.h"
+
+int clear(struct context *c) {
+ c->b = NULL;
+ c->t = NULL;
+ c->r = NULL;
+ *c->current.name = '\0';
+ c->current.term = SEMI;
+
+ return 1;
+}
+
+int quit(struct context *c) {
+ clear(c);
+
+ return c->input == userinput;
+}
#define MAXCOMMANDS (MAXCHARS + 1) / 2
#define MAXREDIRECTS (MAXCHARS / 3)
-#define PIPELINE(name) struct context *name(struct context *context)
-
-enum access {
+enum {
NONE,
READ = '<',
READWRITE,
};
struct redirect {
- enum access mode;
- int oldfd, newfd;
+ int mode, oldfd, newfd;
char *oldname;
- struct redirect *next;
};
-enum terminator {
- SEMI = ';',
+enum {
+ SEMI,
BG = '&',
AND,
PIPE = '|',
};
struct command {
- char **args;
- struct redirect *r;
- enum terminator term;
- int pipe[2];
- struct command *prev, *next;
+ char name[MAXCHARS + 1];
+ int term, pipe[2];
};
struct context {
- struct {
- char *string, *script;
- struct {
- char *map;
- size_t len;
- };
- PIPELINE((*input));
- };
- char buffer[MAXCHARS + 1 + 1], *tokens[MAXCOMMANDS + 1];
- struct redirect redirects[MAXREDIRECTS + 1];
- struct command commands[1 + MAXCOMMANDS];
+ char *string, *script, *map, buffer[MAXCHARS + 1 + 1], *b,
+ *tokens[MAXCOMMANDS + 1], **t;
+ size_t maplen;
+ int (*input)(struct context *c), alias;
+ struct redirect redirects[MAXREDIRECTS + 1], *r;
+ struct command current, prev;
};
+
+int clear(struct context *c);
+int quit(struct context *c);
#include "input.h"
#include "utils.h"
-PIPELINE(stringinput) {
+int stringinput(struct context *c) {
char *start;
size_t l;
- if (!*context->string) {
- if (context->script && munmap(context->map, context->len) == -1)
- note("Unable to unmap memory associated with `%s'", context->script);
- return NULL;
+ if (!*c->string) {
+ if (c->script && munmap(c->map, c->maplen) == -1)
+ note("Unable to unmap memory associated with `%s'", c->script);
+ return 0;
}
- start = context->string;
- while (*context->string && *context->string != '\n') ++context->string;
- l = context->string - start;
- if (*context->string == '\n') ++context->string;
+ start = c->string;
+ while (*c->string && *c->string != '\n') ++c->string;
+ l = c->string - start;
+ if (*c->string == '\n') ++c->string;
if (l > MAXCHARS) {
note("Line too long, exceeds %d character limit", MAXCHARS);
- return NULL;
+ return 0;
}
- strncpy(context->buffer, start, l);
- context->buffer[l] = ';';
- context->buffer[l + 1] = '\0';
+ strncpy(c->buffer, start, l);
+ c->buffer[l] = ';';
+ c->buffer[l + 1] = '\0';
- return context;
+ return 1;
}
-PIPELINE(scriptinput) {
+int scriptinput(struct context *c) {
int fd;
struct stat sstat;
- if ((fd = open(context->script, O_RDONLY)) == -1) {
- note("Unable to open `%s'", context->script);
- return NULL;
+ if ((fd = open(c->script, O_RDONLY)) == -1) {
+ note("Unable to open `%s'", c->script);
+ return 0;
}
- if (stat(context->script, &sstat) == -1) {
- note("Unable to stat `%s'", context->script);
- return NULL;
+ if (stat(c->script, &sstat) == -1) {
+ note("Unable to stat `%s'", c->script);
+ return 0;
}
- if ((context->len = sstat.st_size) == 0) return NULL;
- if ((context->map = mmap(NULL, context->len, PROT_READ, MAP_PRIVATE, fd, 0))
+ 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'", context->script);
- return NULL;
+ note("Unable to memory map `%s'", c->script);
+ return 0;
}
if (close(fd) == -1) {
- note("Unable to close `%s'", context->script);
- return NULL;
+ note("Unable to close `%s'", c->script);
+ return 0;
}
- context->string = context->map;
- context->input = stringinput;
+ c->string = c->map;
+ c->input = stringinput;
- return context->input(context);
+ return c->input(c);
}
static void prompt(void) {
printf("\r%s ", p);
}
-PIPELINE(userinput) {
+int userinput(struct context *c) {
char *start, *cursor, *end;
- unsigned int c;
+ unsigned int current;
int i;
size_t oldlen, newlen;
- end = cursor = start = context->buffer;
- *context->buffer = '\0';
+ end = cursor = start = c->buffer;
+ *c->buffer = '\0';
while (start == end) {
prompt();
- while ((c = getchar()) != '\r') switch (c) {
+ while ((current = getchar()) != '\r') switch (current) {
default:
- if (c >= ' ' && c <= '~') {
+ if (current >= ' ' && current <= '~') {
if (end - start == MAXCHARS) continue;
memmove(cursor + 1, cursor, end - cursor);
- *cursor++ = c;
+ *cursor++ = current;
*++end = '\0';
- putchar(c);
+ putchar(current);
fputs(cursor, stdout);
for (i = end - cursor; i > 0; --i) putchar('\b');
}
break;
case CTRLC:
puts("^C\r");
- *context->buffer = '\0';
- return context;
+ return quit(c);
case CTRLD:
puts("^D\r");
- return NULL;
+ return 0;
case CLEAR:
fputs("\033[H\033[J", stdout);
prompt();
- fputs(context->buffer, stdout);
+ fputs(c->buffer, stdout);
continue;
case ESCAPE:
- switch ((c = getchar())) {
+ switch ((current = getchar())) {
case FORWARD:
while (cursor != end && *cursor != ' ') putchar(*cursor++);
while (cursor != end && *cursor == ' ') putchar(*cursor++);
while (cursor != start && *(cursor - 1) != ' ') putchar((--cursor, '\b'));
break;
case ARROW:
- switch ((c = getchar())) {
+ switch ((current = getchar())) {
case UP:
case DOWN:
- oldlen = strlen(context->buffer);
- if (!gethistory(c, context->buffer)) continue;
- newlen = strlen(context->buffer);
+ oldlen = strlen(c->buffer);
+ if (!gethistory(current, c->buffer)) continue;
+ newlen = strlen(c->buffer);
end = cursor = start + newlen;
putchar('\r');
prompt();
- fputs(context->buffer, stdout);
+ fputs(c->buffer, stdout);
for (i = oldlen - newlen; i > 0; --i) putchar(' ');
for (i = oldlen - newlen; i > 0; --i) putchar('\b');
}
break;
default:
- ungetc(c, stdin);
+ ungetc(current, stdin);
}
break;
case BACKSPACE:
}
while (*start == ' ') ++start;
- if (start == end) {
- *context->buffer = '\0';
- return context;
- }
+ if (start == end) return quit(c);
- sethistory(context->buffer);
+ sethistory(c->buffer);
*end++ = ';';
*end = '\0';
- return context;
+ return 1;
}
DEL = '\177',
};
-PIPELINE(stringinput);
-PIPELINE(scriptinput);
-PIPELINE(userinput);
+int stringinput(struct context *c);
+int scriptinput(struct context *c);
+int userinput(struct context *c);
struct job *pushjob(struct job *job) {
struct joblink *p;
- if (!jobs.free) return NULL;
+ if (!jobs.free) {
+ note("Unable to %s, exceeds %d job limit",
+ job->suspended ? "suspend job" : "place job in background", MAXJOBS);
+ return NULL;
+ }
(p = jobs.free)->job = *job;
jobs.free = p->next;
return jobs.active ? &jobs.active->job : NULL;
}
-struct job *searchjobid(pid_t id) {
+struct job *peeksuspendedjob(void) {
struct joblink *p;
- for (p = jobs.active; p; p = p->next) if (p->job.id == id) return &p->job;
+ for (p = jobs.active; p; p = p->next) if (p->job.suspended) return &p->job;
+
+ note("No suspended job to run in background");
return NULL;
}
-struct job *searchjobtype(enum jobtype type) {
+struct job *searchjobid(pid_t id) {
struct joblink *p;
- for (p = jobs.active; p; p = p->next) if (p->job.type == type) return &p->job;
+ for (p = jobs.active; p; p = p->next) if (p->job.id == id) return &p->job;
return NULL;
}
-enum jobtype {
- BACKGROUND,
- SUSPENDED,
-};
-
struct job {
pid_t id;
struct termios config;
- enum jobtype type;
+ int suspended;
};
void initjobs(void);
struct job *pushjob(struct job *job);
struct job *pulljob(void);
struct job *peekjob(void);
+struct job *peeksuspendedjob(void);
struct job *searchjobid(pid_t id);
-struct job *searchjobtype(enum jobtype);
struct job *deletejobid(pid_t id);
#include <stdlib.h>
#include "context.h"
-#include "input.h"
#include "options.h"
-#include "parse.h"
#include "run.h"
#include "source.h"
#include "utils.h"
-int main(int c, char **v) {
- argc = c;
- argv = v;
+int main(int argc, char **argv) {
+ struct context context;
- options();
+ argcount = argc;
+ arglist = argv;
+ context = (struct context){0};
+
+ options(&context);
init();
if (login) config(".ashlogin");
if (interactive) config(".ashrc");
- while (run(parse(context.input(&context))));
+ while (run(&context));
deinit();
#include "input.h"
#include "utils.h"
-int login, interactive, argc;
-char **argv;
-struct context context;
+int login, interactive;
static void usage(char *program, int code) {
printf("Usage: %s [file] [-c string] [-hl]\n"
exit(code);
}
-void options(void) {
+void options(struct context *context) {
int opt, l;
- login = **argv == '-';
+ login = **arglist == '-';
interactive = 1;
- context.input = userinput;
+ context->input = userinput;
- while ((opt = getopt(argc, argv, ":c:hl")) != -1) {
+ while ((opt = getopt(argcount, arglist, ":c:hl")) != -1) {
switch (opt) {
case 'c':
interactive = 0;
- context.string = optarg;
- context.input = stringinput;
+ context->string = optarg;
+ context->input = stringinput;
+ arglist[--optind] = ""; // Empty program name when running a string
break;
case 'h':
- usage(*argv, EXIT_SUCCESS);
+ usage(*arglist, EXIT_SUCCESS);
case 'l':
login = 1;
break;
case ':':
note("Expected argument following `-%c'\n", optopt);
- usage(*argv, EXIT_FAILURE);
+ usage(*arglist, EXIT_FAILURE);
case '?':
default:
note("Unknown command line option `-%c'\n", optopt);
- usage(*argv, EXIT_FAILURE);
+ usage(*arglist, EXIT_FAILURE);
}
if (opt == 'c') break;
}
- if (!context.string && argv[optind]) {
+ if (!context->string && arglist[optind]) {
interactive = 0;
- context.script = argv[optind];
- context.input = scriptinput;
+ context->script = arglist[optind];
+ context->input = scriptinput;
}
if (!interactive) {
- argc -= optind;
- argv += optind;
+ argcount -= optind;
+ arglist += optind;
}
}
-extern int login, interactive, argc;
-extern char **argv;
-extern struct context context;
+extern int login, interactive;
-void options(void);
+void options(struct context *context);
+#include <glob.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/errno.h>
+#include "alias.h"
#include "context.h"
-#include "input.h"
#include "options.h"
#include "utils.h"
-static void initcommand(struct command *c) {
- c->args = NULL;
- c->r = NULL;
- c->prev = c - 1;
- c->next = NULL;
-}
-
-PIPELINE(parse) {
- char *b, **t, *name, *value, *stlend, *p, *end, *env;
- struct redirect *r, *q;
- struct command *c;
+int parse(struct context *c) {
+ int globbing, e, offset;
+ char *stlend, *p, *end, *env, term, **sub;
long l;
- int e, offset;
-
- if (!context) return NULL;
-
- b = context->buffer;
- t = context->tokens;
- r = context->redirects;
- c = context->commands + 1;
- context->commands->next = NULL;
- *t = value = name = NULL;
- r->mode = NONE;
- for (initcommand(c); *b; ++b) switch (*b) {
- default:
- if (r->mode) break;
- if (!c->args) c->args = t;
- if (!*(b - 1)) {
- if (!name) *t++ = b; else if (!value) value = b;
- }
- break;
+ size_t sublen;
+ static glob_t globs;
+
+ if (!c->b) {
+ if (!c->input(c)) return 0;
+ c->b = c->buffer;
+ }
+ if (globs.gl_pathc) {
+ globfree(&globs);
+ globs.gl_pathc = 0;
+ }
+ c->t = c->tokens;
+ c->r = c->redirects;
+ c->r->mode = NONE;
+ c->prev = c->current;
+ globbing = 0;
+
+ for (*c->t = c->b; *c->b; ++c->b) switch (*c->b) {
case '<':
case '>':
- if (r->mode) {
- note("File redirections should be separated by spaces");
- return context;
+ if (c->r->mode) {
+ note("Invalid syntax for file redirection");
+ return quit(c);
}
- if (r - context->redirects == MAXREDIRECTS) {
+ if (c->r - c->redirects == MAXREDIRECTS) {
note("Too many file redirects, exceeds %d redirect limit", MAXREDIRECTS);
- return context;
+ return quit(c);
}
- if (!c->r) c->r = r;
- if (*(b - 1)) {
- if (c->args == --t) c->args = NULL;
- if ((l = strtol(*t, &stlend, 10)) < 0 || l > INT_MAX || stlend != b) {
+ if (*c->t != c->b) {
+ if ((l = strtol(*c->t, &stlend, 10)) < 0 || l > INT_MAX || stlend != c->b) {
note("Invalid value for a file redirection");
- return context;
+ return quit(c);
}
- r->newfd = l;
- } else r->newfd = *b == '>';
- r->mode = *b;
- if (*(b + 1) == '>') {
- ++r->mode;
- ++b;
+ c->r->newfd = l;
+ } else c->r->newfd = *c->b == '>';
+ c->r->mode = *c->b;
+ if (*(c->b + 1) == '>') {
+ ++c->r->mode;
+ ++c->b;
}
- r->oldname = b + 1;
- if (*(b + 1) == '&') ++b;
+ c->r->oldname = c->b + 1;
+ if (*(c->b + 1) == '&') ++c->b;
break;
- case '"':
- if (!*(b - 1)) {
- if (!name) *t++ = b; else if (!value) value = b;
+ case '$':
+ p = c->b++;
+ while (*c->b && *c->b != '$') ++c->b;
+ if (!*c->b) {
+ note("Environment variable lacks a terminating `$'");
+ return quit(c);
}
- for (end = (p = b) + 1, b = NULL; *end; ++end) {
- if (!b && *end == '"') b = end;
+ *c->b++ = '\0';
+ for (end = c->b; *end; ++end);
+
+ l = strtol(p + 1, &stlend, 10);
+ errno = 0;
+ if (stlend == c->b - 1) env = l >= 0 && l < argcount ? arglist[l] : c->b - 1;
+ else if (strcmp(p + 1, "^") == 0) {
+ if (!sprintf(env = (char [12]){0}, "%d", status)) {
+ note("Unable to get previous command status");
+ return quit(c);
+ }
+ } else if (!(env = getenv(p + 1))) {
+ note("Environment variable does not exist");
+ return quit(c);
+ }
+
+ e = strlen(env);
+ offset = e - (c->b - p);
+ memmove(c->b + offset, c->b, end - c->b + 1);
+ strncpy(p, env, e);
+ c->b += offset - 1;
+
+ break;
+ case '~':
+ for (end = c->b; *end; ++end);
+ offset = strlen(home);
+ memmove(c->b + offset, c->b + 1, end - c->b);
+ strncpy(c->b, home, offset);
+ c->b += offset - 1;
+ break;
+ case '[':
+ while (*c->b && *c->b != ']') ++c->b;
+ if (!*c->b) {
+ note("Range in glob left open-ended");
+ return quit(c);
+ }
+ case '*':
+ case '?':
+ globbing = 1;
+ break;
+ case '"':
+ for (end = (p = c->b) + 1, c->b = NULL; *end; ++end) if (!c->b) {
+ if (*end == '"') c->b = end;
if (*end == '\\') ++end;
}
- if (!b) {
+ if (!c->b) {
note("Quote left open-ended");
- return context;
+ return quit(c);
}
memmove(p, p + 1, end-- - p);
- --b;
+ --c->b;
- while (p != b) if (*p++ == '\\') {
+ while (p != c->b) if (*p++ == '\\') {
switch (*p) {
case 't':
*p = '\t';
*p = '\n';
break;
}
- memmove(p - 1, p, end-- - p);
- --b;
- }
- *end = '\0';
- memmove(p, p + 1, end - p);
- --b;
-
- break;
- case '=':
- name = *--t;
- *b = '\0';
- break;
- case '$':
- if (!*(b - 1)) {
- if (!name) *t++ = b; else if (!value) value = b;
- }
- p = b++;
- while (*b && *b != '$') ++b;
- if (!*b) {
- note("Environment variable lacks a terminating `$'");
- return context;
- }
- *b++ = '\0';
- for (end = b; *end; ++end);
-
- l = strtol(p + 1, &stlend, 10);
- if (stlend == b - 1) env = l >= 0 && l < argc ? argv[l] : b - 1;
- else if (strcmp(p + 1, "^") == 0) {
- if (!sprintf(env = (char [12]){0}, "%d", status)) {
- note("Unable to get previous command status");
- return context;
- }
- } else if ((env = getenv(p + 1)) == NULL) {
- note("Environment variable does not exist");
- return context;
+ memmove(p - 1, p, end-- - p + 1);
+ --c->b;
}
-
- e = strlen(env);
- offset = e - (b - p);
- memmove(b + offset, b, end - b + 1);
- strncpy(p, env, e);
- b += offset - 1;
+ memmove(p, p + 1, end-- - p);
+ --c->b;
- break;
- case '~':
- if (!*(b - 1)) {
- if (!name) *t++ = b; else if (!value) value = b;
- }
- for (end = b; *end; ++end);
- offset = strlen(home);
- memmove(b + offset, b + 1, end - b);
- strncpy(b, home, offset);
- b += offset - 1;
break;
case '#':
- *(b + 1) = '\0';
+ *(c->b + 1) = '\0';
case '&':
case '|':
case ';':
- if (name && *c->args == name) c->args = NULL;
- if (c->args || c->r) {
- if ((c->term = *b) == *(b + 1) && (*b == '&' || *b == '|')) {
- ++c->term;
- *b++ = '\0';
- }
- *b = '\0';
-
- if (r->mode) {
- r++->next = NULL;
- r->mode = NONE;
- } else if (c->r) (r - 1)->next = NULL;
- for (q = c->r; q; q = q->next) if (*q->oldname == '&') {
- if ((l = strtol(++q->oldname, &stlend, 10)) < 0 || l > INT_MAX || *stlend) {
- note("Incorrect syntax for file redirection");
- return context;
+ case ' ':
+ term = *c->b;
+ *c->b = '\0';
+
+ if (c->r->mode) {
+ switch (*c->r->oldname) {
+ case '&':
+ if ((l = strtol(++c->r->oldname, &stlend, 10)) < 0 || l > INT_MAX || *stlend) {
+ case '\0':
+ note("Invalid syntax for file redirection");
+ return quit(c);
}
- q->oldfd = l;
- q->oldname = NULL;
+ c->r->oldfd = l;
+ c->r->oldname = NULL;
}
+ (++c->r)->mode = NONE;
+ globbing = 0;
- initcommand(c = c->next = c + 1);
- *t++ = NULL;
- }
- case ' ':
- *b = '\0';
- if (value) {
- if (setenv(name, value, 1) == -1) {
- note("Unable to set environment variable");
- return context;
+ *c->t = c->b;
+ } else if (!c->alias && c->t == c->tokens && (sub = getalias(*c->tokens)) || globbing) {
+ if (globbing) {
+ switch (glob(*c->t, GLOB_APPEND | GLOB_MARK, NULL, &globs)) {
+ case GLOB_NOMATCH:
+ note("No matches found for %s", *c->t);
+ return quit(c);
+ case GLOB_NOSPACE:
+ fatal("Memory allocation");
+ }
+ sublen = globs.gl_matchc;
+ sub = globs.gl_pathv + globs.gl_pathc - sublen;
+ globbing = 0;
+ } else for (sublen = 0; sub[sublen]; ++sublen);
+
+ memcpy(c->t, sub, sublen * sizeof*c->t);
+ c->t += sublen;
+ *c->t = c->b;
+ } else if (*c->t != c->b) ++c->t;
+
+ if (term != ' ') {
+ if (c->t != c->tokens) {
+ *c->t = NULL;
+ strcpy(c->current.name, *c->tokens);
+ } 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;
}
- value = name = NULL;
- }
- if (r->mode) {
- r = r->next = r + 1;
- r->mode = NONE;
+ ++c->b;
+
+ return 1;
}
+ *c->t = c->b + 1;
}
- (--c)->next = NULL;
- switch (c->term) {
+ switch (c->current.term) {
case AND:
case PIPE:
case OR:
note("Expected another command");
- return context;
- default:
- break;
+ return quit(c);
}
- context->commands->next = context->commands + 1;
+ if (c->t == c->tokens) c->t = NULL;
+ if (c->r == c->redirects) c->r = NULL;
+ c->b = NULL;
- return context;
+ return 1;
}
-PIPELINE(parse);
+int parse(struct context *c);
#include <limits.h>
#include <signal.h>
#include <stdlib.h>
+#include <string.h>
#include <sys/errno.h>
#include <sys/wait.h>
#include <termios.h>
#include "builtin.h"
#include "context.h"
-#include "alias.h"
-#include "input.h"
#include "job.h"
#include "fg.h"
+#include "parse.h"
#include "utils.h"
+#include "which.h"
-static int closepipe(struct command *command) {
+extern char **environ;
+
+static int closepipe(struct command c) {
int result;
- result = close(command->pipe[0]) == 0;
- result &= close(command->pipe[1]) == 0;
- if (!result) note("Unable to close `%s' pipe", *command->args);
+ result = close(c.pipe[0]) == 0;
+ result &= close(c.pipe[1]) == 0;
+ if (!result) note("Unable to close `%s' pipe", c.name);
return result;
}
static void redirectfiles(struct redirect *r) {
- int mode, fd;
+ int access, fd;
- for (; r; r = r->next) {
+ for (; r->mode; ++r) {
if (r->oldname) {
switch (r->mode) {
case READ:
- mode = O_RDONLY;
+ access = O_RDONLY;
break;
case WRITE:
- mode = O_WRONLY | O_CREAT | O_TRUNC;
+ access = O_WRONLY | O_CREAT | O_TRUNC;
break;
case READWRITE:
- mode = O_RDWR | O_CREAT | O_APPEND;
+ access = O_RDWR | O_CREAT | O_APPEND;
break;
case APPEND:
- mode = O_WRONLY | O_CREAT | O_APPEND;
+ access = O_WRONLY | O_CREAT | O_APPEND;
default:
break;
}
- if ((fd = open(r->oldname, mode, 0644)) == -1)
+ if ((fd = open(r->oldname, access, 0644)) == -1)
fatal("Unable to open `%s'", r->oldname);
r->oldfd = fd;
}
}
}
-static void exec(struct command *c) {
+static void exec(char *path, struct context *c) {
char cwd[PATH_MAX];
- redirectfiles(c->r);
-
- if (isbuiltin(c->args)) exit(status);
- execvp(*c->args, c->args);
- if (!getcwd(cwd, PATH_MAX)) fatal("Unable to check current working directory");
- execvP(*c->args, cwd, c->args);
+ redirectfiles(c->redirects);
- fatal("Couldn't find `%s' command", *c->args);
+ if (isbuiltin(c->tokens)) exit(status);
+ execve(path, c->tokens, environ);
+ fatal("Couldn't find `%s' command", c->current.name);
}
-PIPELINE(run) {
- struct command *c;
- int ispipe, ispipestart, ispipeend;
+int run(struct context *c) {
+ int islist, ispipe, ispipestart, ispipeend;
+ char *path;
pid_t cpid, jobid;
- struct job job;
-
- if (!context) return NULL;
-
- applyaliases(c = context->commands);
+ struct job *p, job;
+ setsigchld(&sigchld);
+ if (!parse(c)) return 0;
setsigchld(&sigdfl);
- while ((c = c->next)) if (c->args) {
- ispipe = c->term == PIPE || c->prev->term == PIPE;
- ispipestart = ispipe && c->prev->term != PIPE;
- ispipeend = ispipe && c->term != PIPE;
+ islist = c->prev.term > BG || c->current.term > BG;
+ if (c->t) {
+ if (!(path = getpath(c->current.name))) {
+ note("Couldn't find `%s' command", c->current.name);
+ if (c->prev.term == PIPE) closepipe(c->prev);
+ return quit(c);
+ }
+
+ ispipe = c->prev.term == PIPE || c->current.term == PIPE;
+ ispipestart = ispipe && c->prev.term != PIPE;
+ ispipeend = ispipe && c->current.term != PIPE;
if (ispipe) {
- if (!ispipeend && pipe(c->pipe) == -1) {
+ if (!ispipeend && pipe(c->current.pipe) == -1) {
note("Unable to create pipe");
if (!ispipestart) closepipe(c->prev);
- break;
+ return quit(c);
}
if ((jobid = cpid = fork()) == -1) {
note("Unable to fork child process");
- break;
+ 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->args);
+ 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 (!ispipeend) {
- if (dup2(c->pipe[1], 1) == -1)
- fatal("Unable to duplicate write end of `%s' pipe", *c->args);
- if (!closepipe(c)) exit(EXIT_FAILURE);
+ 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(c);
+ exec(path, c);
}
if (!ispipestart) {
closepipe(c->prev);
- jobid = (ispipeend ? pulljob : peekjob)()->id;
+ if (!(p = (ispipeend ? pulljob : peekjob)())) {
+ note("Unable to %s pipeline job from background",
+ ispipeend ? "remove" : "get");
+ return quit(c);
+ }
+ jobid = p->id;
}
- } else if (!c->r && isbuiltin(c->args)) cpid = 0;
+ } else if (!c->r && isbuiltin(c->tokens)) cpid = 0;
else if ((jobid = cpid = fork()) == -1) {
note("Unable to fork child process");
- break;
- } else if (cpid == 0) exec(c);
+ return quit(c);
+ } else if (cpid == 0) exec(path, c);
if (cpid) {
if (setpgid(cpid, jobid) == -1) {
if (errno != ESRCH) {
- note("Unable to set pgid of `%s' command to %d", *c->args, jobid);
+ 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);
}
- break;
+ return quit(c);
}
- job = (struct job){.id = jobid, .config = canonical, .type = BACKGROUND};
- if (ispipestart || c->term == BG) {
+ job = (struct job){.id = jobid, .config = canonical};
+ if (ispipestart || c->current.term == BG) {
if (!pushjob(&job)) {
- note("Unable to add job to background; too many background jobs");
- if (ispipestart) closepipe(c);
- break;
+ if (ispipestart) closepipe(c->current);
+ return quit(c);
}
- } else if (c->term != PIPE) {
- if (!setfg(job)) break;
+ } else if (c->current.term != PIPE) {
+ if (!setfg(job)) return quit(c);
waitfg(job);
}
}
- if (c->term == AND && status != EXIT_SUCCESS) break;
- if (c->term == OR && status == EXIT_SUCCESS) break;
+ 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);
} else {
- if (c->term == AND || c->term == PIPE || c->term == OR) {
+ if (islist) {
+ if (c->prev.term == PIPE) closepipe(c->prev);
note("Expected command");
- break;
+ return quit(c);
}
- if (!c->r) break;
+ if (c->r) return 1;
if ((cpid = fork()) == -1) {
note("Unable to fork child process");
- break;
+ return quit(c);
} else if (cpid == 0) {
- redirectfiles(c->r);
+ redirectfiles(c->redirects);
exit(EXIT_SUCCESS);
}
waitpid(cpid, NULL, 0);
- errno = 0; // waitpid() might set errno
+ errno = 0;
}
- setsigchld(&sigchld);
-
- return context;
+ return 1;
}
-extern int status;
-
-PIPELINE(run);
+int run(struct context *c);
#include <termios.h>
#include <unistd.h>
+#include "context.h"
#include "history.h"
#include "job.h"
#include "fg.h"
#include "options.h"
-char *home;
-int status;
+int argcount, status;
+char **arglist, *home;
void note(char *fmt, ...) {
va_list args;
}
void init(void) {
- char *shlvlstr, buffer[19 + 1];
+ char buffer[PATH_MAX], *shlvlstr;
+ size_t l;
long shlvl;
if (!(home = getenv("HOME")))
- fatal("Unable to locate user's home directory, $HOME$ not set");
+ fatal("Unable to query $HOME$");
+ strcpy(buffer, home);
+ l = strlen(buffer);
+ buffer[l + 1] = '\0';
+ buffer[l] = '/';
+ if (setenv("HOME", buffer, 1) == -1 || !(home = getenv("HOME")))
+ fatal("Unable to append trailing slash to $HOME$");
if (!(shlvlstr = getenv("SHLVL"))) shlvlstr = "0";
if ((shlvl = strtol(shlvlstr, NULL, 10)) < 0) shlvl = 0;
-extern char *home;
-extern int status;
+extern int argcount, status;
+extern char **arglist, *home;
void note(char *fmt, ...);
void fatal(char *fmt, ...);