From: Trent Huber Date: Tue, 19 Aug 2025 07:18:17 +0000 (-0400) Subject: Test bugs, hone behavior, clean code X-Git-Url: https://trenthuber.com/code?a=commitdiff_plain;h=083328d583646a470985c8de6960530c2c46ac77;p=thus.git Test bugs, hone behavior, clean code --- diff --git a/external/cbs b/external/cbs index 5ab49da..beedfb7 160000 --- a/external/cbs +++ b/external/cbs @@ -1 +1 @@ -Subproject commit 5ab49da8d4ab0b8e6cb23c3cb1f927e4402db4fc +Subproject commit beedfb79168cadd181b26ee46e50a01a6a44de52 diff --git a/external/cbsfile.c b/external/cbsext.c similarity index 100% rename from external/cbsfile.c rename to external/cbsext.c diff --git a/src/build.c b/src/build.c index 2f0999e..c09161e 100644 --- a/src/build.c +++ b/src/build.c @@ -1,5 +1,5 @@ #include "../external/cbs/cbs.c" -#include "../external/cbsfile.c" +#include "../external/cbsext.c" #define BUILTINS LIST("-Ibuiltin/") @@ -10,12 +10,13 @@ int main(void) { 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}, diff --git a/src/builtin/README.md b/src/builtin/README.md new file mode 100644 index 0000000..ea1db20 --- /dev/null +++ b/src/builtin/README.md @@ -0,0 +1,8 @@ +# Builtins + +## TODO + - Documentation on how to add builtins of your own + - Reworking aliasing stuff + - unset + - which needs to test for aliases + - pwd diff --git a/src/builtin/alias.c b/src/builtin/alias.c index 49c9100..6ceb185 100644 --- a/src/builtin/alias.c +++ b/src/builtin/alias.c @@ -13,46 +13,39 @@ 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; @@ -82,7 +75,7 @@ BUILTINSIG(alias) { break; default: - puts("Usage: alias [lhs rhs]\r"); + fputs("Usage: alias [lhs rhs]\r\n", stderr); return EXIT_FAILURE; } diff --git a/src/builtin/alias.h b/src/builtin/alias.h index b84318f..52a3725 100644 --- a/src/builtin/alias.h +++ b/src/builtin/alias.h @@ -1 +1,2 @@ -void applyaliases(struct command *command); +char *getaliasrhs(char *token); +char **getalias(char *token); diff --git a/src/builtin/bg.c b/src/builtin/bg.c index 09c483c..4ab87b4 100644 --- a/src/builtin/bg.c +++ b/src/builtin/bg.c @@ -8,7 +8,7 @@ #include "job.h" #include "utils.h" -BUILTINSIG(bg) { +BUILTIN(bg) { long l; pid_t id; struct job *job; @@ -23,25 +23,19 @@ BUILTINSIG(bg) { 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; } diff --git a/src/builtin/build.c b/src/builtin/build.c index 9a5c843..36f41e2 100644 --- a/src/builtin/build.c +++ b/src/builtin/build.c @@ -5,7 +5,7 @@ #include #include "../../external/cbs/cbs.c" -#include "../../external/cbsfile.c" +#include "../../external/cbsext.c" #define MAXBUILTINS 50 @@ -14,10 +14,12 @@ int main(void) { 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; @@ -49,7 +51,7 @@ int main(void) { 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"); diff --git a/src/builtin/builtin.h b/src/builtin/builtin.h index 07fc387..bb197f8 100644 --- a/src/builtin/builtin.h +++ b/src/builtin/builtin.h @@ -1,3 +1,3 @@ -#define BUILTINSIG(name) int name(int argc, char **argv) +#define BUILTIN(name) int name(int argc, char **argv) int isbuiltin(char **args); diff --git a/src/builtin/cd.c b/src/builtin/cd.c index 0986e25..ecd67ca 100644 --- a/src/builtin/cd.c +++ b/src/builtin/cd.c @@ -1,12 +1,18 @@ +#include #include #include #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) { @@ -14,7 +20,7 @@ BUILTINSIG(cd) { 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; } diff --git a/src/builtin/fg.c b/src/builtin/fg.c index cdff59b..0ff7935 100644 --- a/src/builtin/fg.c +++ b/src/builtin/fg.c @@ -5,17 +5,22 @@ #include #include #include +#include // 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; @@ -24,14 +29,22 @@ static int setconfig(struct termios *mode) { } 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) { @@ -51,6 +64,7 @@ void initfg(void) { } 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); @@ -69,10 +83,11 @@ void waitfg(struct job job) { /* 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 @@ -84,28 +99,31 @@ void waitfg(struct job job) { } 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; @@ -117,12 +135,12 @@ BUILTINSIG(fg) { 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; } diff --git a/src/builtin/list.h b/src/builtin/list.h index 7c33a2f..911d534 100644 --- a/src/builtin/list.h +++ b/src/builtin/list.h @@ -1,6 +1,6 @@ struct builtin { char *name; - BUILTINSIG((*func)); + BUILTIN((*func)); }; extern struct builtin builtins[]; diff --git a/src/builtin/set.c b/src/builtin/set.c new file mode 100644 index 0000000..5eca3c6 --- /dev/null +++ b/src/builtin/set.c @@ -0,0 +1,23 @@ +#include +#include + +#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; +} diff --git a/src/builtin/source.c b/src/builtin/source.c index 7190f84..c53c09c 100644 --- a/src/builtin/source.c +++ b/src/builtin/source.c @@ -9,17 +9,29 @@ #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; } @@ -27,5 +39,5 @@ BUILTINSIG(source) { 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}); } diff --git a/src/builtin/unset.c b/src/builtin/unset.c new file mode 100644 index 0000000..fbdccf4 --- /dev/null +++ b/src/builtin/unset.c @@ -0,0 +1,23 @@ +#include +#include + +#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; +} diff --git a/src/builtin/which.c b/src/builtin/which.c index 4d8c58a..5d05e93 100644 --- a/src/builtin/which.c +++ b/src/builtin/which.c @@ -5,52 +5,94 @@ #include #include +#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; } diff --git a/src/builtin/which.h b/src/builtin/which.h new file mode 100644 index 0000000..f645a0f --- /dev/null +++ b/src/builtin/which.h @@ -0,0 +1 @@ +char *getpath(char *file); diff --git a/src/context.c b/src/context.c new file mode 100644 index 0000000..e11ec3c --- /dev/null +++ b/src/context.c @@ -0,0 +1,20 @@ +#include + +#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; +} diff --git a/src/context.h b/src/context.h index 4fa355e..d69b98b 100644 --- a/src/context.h +++ b/src/context.h @@ -2,9 +2,7 @@ #define MAXCOMMANDS (MAXCHARS + 1) / 2 #define MAXREDIRECTS (MAXCHARS / 3) -#define PIPELINE(name) struct context *name(struct context *context) - -enum access { +enum { NONE, READ = '<', READWRITE, @@ -13,14 +11,12 @@ enum access { }; 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 = '|', @@ -28,23 +24,18 @@ enum terminator { }; 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); diff --git a/src/input.c b/src/input.c index 0e5dc41..b73b69b 100644 --- a/src/input.c +++ b/src/input.c @@ -12,59 +12,59 @@ #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) { @@ -75,43 +75,42 @@ 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++); @@ -121,17 +120,17 @@ PIPELINE(userinput) { 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'); @@ -145,7 +144,7 @@ PIPELINE(userinput) { } break; default: - ungetc(c, stdin); + ungetc(current, stdin); } break; case BACKSPACE: @@ -166,15 +165,12 @@ PIPELINE(userinput) { } 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; } diff --git a/src/input.h b/src/input.h index 7ba2dcf..3762b05 100644 --- a/src/input.h +++ b/src/input.h @@ -14,6 +14,6 @@ enum { DEL = '\177', }; -PIPELINE(stringinput); -PIPELINE(scriptinput); -PIPELINE(userinput); +int stringinput(struct context *c); +int scriptinput(struct context *c); +int userinput(struct context *c); diff --git a/src/job.c b/src/job.c index 9c02e8b..c412040 100644 --- a/src/job.c +++ b/src/job.c @@ -25,7 +25,11 @@ void initjobs(void) { 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; @@ -52,18 +56,20 @@ struct job *peekjob(void) { 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; } diff --git a/src/job.h b/src/job.h index f477750..28a4673 100644 --- a/src/job.h +++ b/src/job.h @@ -1,18 +1,13 @@ -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); diff --git a/src/main.c b/src/main.c index c6dc202..9b434d3 100644 --- a/src/main.c +++ b/src/main.c @@ -1,25 +1,26 @@ #include #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(); diff --git a/src/options.c b/src/options.c index 35f39a3..73de4e4 100644 --- a/src/options.c +++ b/src/options.c @@ -6,9 +6,7 @@ #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" @@ -19,42 +17,43 @@ static void usage(char *program, int code) { 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; } } diff --git a/src/options.h b/src/options.h index 022538f..825670a 100644 --- a/src/options.h +++ b/src/options.h @@ -1,5 +1,3 @@ -extern int login, interactive, argc; -extern char **argv; -extern struct context context; +extern int login, interactive; -void options(void); +void options(struct context *context); diff --git a/src/parse.c b/src/parse.c index e36637e..2ef49b4 100644 --- a/src/parse.c +++ b/src/parse.c @@ -1,87 +1,122 @@ +#include #include #include #include #include +#include +#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'; @@ -96,116 +131,92 @@ PIPELINE(parse) { *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; } diff --git a/src/parse.h b/src/parse.h index 377fc37..496c99d 100644 --- a/src/parse.h +++ b/src/parse.h @@ -1 +1 @@ -PIPELINE(parse); +int parse(struct context *c); diff --git a/src/run.c b/src/run.c index 69c8dba..6fa314c 100644 --- a/src/run.c +++ b/src/run.c @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -9,43 +10,45 @@ #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; } @@ -56,111 +59,118 @@ static void redirectfiles(struct redirect *r) { } } -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; } diff --git a/src/run.h b/src/run.h index e7eaa08..6dfecfc 100644 --- a/src/run.h +++ b/src/run.h @@ -1,3 +1 @@ -extern int status; - -PIPELINE(run); +int run(struct context *c); diff --git a/src/utils.c b/src/utils.c index b95bcf9..649b1f4 100644 --- a/src/utils.c +++ b/src/utils.c @@ -8,13 +8,14 @@ #include #include +#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; @@ -35,11 +36,18 @@ void fatal(char *fmt, ...) { } 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; diff --git a/src/utils.h b/src/utils.h index c2884c0..4302a00 100644 --- a/src/utils.h +++ b/src/utils.h @@ -1,5 +1,5 @@ -extern char *home; -extern int status; +extern int argcount, status; +extern char **arglist, *home; void note(char *fmt, ...); void fatal(char *fmt, ...);