#include <sys/errno.h>
#include <termios.h>
#include <unistd.h>
+#include <sys/stat.h>
#include "job.h"
#include "stack.h"
#include "term.h"
#include "utils.h"
-#define BUILTINSIG(name) int name(char **tokens)
+#define BUILTINSIG(name) int name(int argc, char **argv)
struct builtin {
char *name;
BUILTINSIG((*func));
};
-BUILTINSIG(cd) { // TODO: Affect $PWD$ env var
- if (!tokens[1]) return 1;
- if (chdir(tokens[1]) != -1) return 0;
- note("Unable to change directory to `%s'", tokens[1]);
- return 1;
+BUILTINSIG(cd) {
+ char *fullpath;
+
+ if (argv[1]) {
+ if (!(fullpath = realpath(argv[1], NULL))) {
+ note("Could not resolve path name");
+ return 1;
+ }
+ } else fullpath = home;
+ if (chdir(fullpath) == -1) {
+ note("Unable to change directory to `%s'", argv[1]);
+ return 1;
+ }
+ if (setenv("PWD", fullpath, 1) == -1)
+ note("Unable to change $PWD$ to `%s'", fullpath);
+ if (fullpath != home) free(fullpath);
+ return 0;
}
BUILTINSIG(fg) {
long jobid;
struct job *job;
- if (tokens[1]) {
+ if (sigaction(SIGCHLD, &sigdfl, NULL) == -1) {
+ note("Unable to acquire lock on the job stack");
+ return 1;
+ }
+
+ if (argv[1]) {
errno = 0;
- if ((jobid = strtol(tokens[1], NULL, 10)) == LONG_MAX && errno
+ if ((jobid = strtol(argv[1], NULL, 10)) == LONG_MAX && errno
|| jobid <= 0) {
note("Invalid process group id");
return 1;
note("No processes to bring into the foreground");
return 1;
}
+
+ if (sigaction(SIGCHLD, &sigchld, NULL) == -1) {
+ note("Unable to install SIGCHLD handler");
+ return 1;
+ }
+
if (!setfg(*job)) return 1;
waitfg(*job);
long jobid;
struct job *job;
- if (tokens[1]) {
+ if (sigaction(SIGCHLD, &sigdfl, NULL) == -1) {
+ note("Unable to acquire lock on the job stack");
+ return 1;
+ }
+
+ if (argv[1]) {
errno = 0;
- if ((jobid = strtol(tokens[1], NULL, 10)) == LONG_MAX && errno
+ if ((jobid = strtol(argv[1], NULL, 10)) == LONG_MAX && errno
|| jobid <= 0) {
note("Invalid job id");
return 1;
}
job->type = BACKGROUND;
+ if (sigaction(SIGCHLD, &sigchld, NULL) == -1) {
+ note("Unable to install SIGCHLD handler");
+ return 1;
+ }
+
return 0;
}
BUILTIN(NULL),
};
+static int inpath(char *dir, char *filename) {
+ char *filepath;
+ struct stat estat;
+
+ if (stat(filepath = catpath(dir, filename), &estat) != -1) {
+ if (estat.st_mode & S_IXUSR) {
+ puts(filepath);
+ putchar('\r');
+ return 1;
+ }
+ } else if (errno != ENOENT) note("Unable to check if `%s' exists", filepath);
+ return 0;
+}
+
BUILTINSIG(which) {
struct builtin *builtin;
+ char *path, *dir, *p;
- if (!tokens[1]) return 1;
+ if (!argv[1]) return 1;
for (builtin = builtins; builtin->func; ++builtin)
- if (strcmp(tokens[1], builtin->name) == 0) {
- puts("Built-in command");
+ if (strcmp(argv[1], builtin->name) == 0) {
+ puts("Built-in command\r");
return 0;
}
- // TODO: Find command in PATH
+
+ if (!(path = getenv("PATH"))) {
+ note("Unable to examine $PATH$");
+ return 1;
+ }
+ if (!(path = p = strdup(path))) {
+ note("Unable to duplicate $PATH$");
+ return 1;
+ }
+ do {
+ if (!(dir = p)) break;
+ if ((p = strchr(dir, ':'))) *p++ = '\0';
+ } while (!inpath(dir, argv[1]));
+ free(path);
+ if (dir) return 0;
+
+ printf("%s not found\r\n", argv[1]);
return 1;
}
int isbuiltin(char **args, int *statusp) {
struct builtin *builtinp;
+ size_t n;
for (builtinp = builtins; builtinp->func; ++builtinp)
if (strcmp(*args, builtinp->name) == 0) {
- *statusp = builtinp->func(args);
+ for (n = 0; args[n]; ++n);
+ *statusp = builtinp->func(n, args);
return 1;
}
return 0;
+#define DEFAULTPROMPT ">"
+
#define HISTORYFILE ".ashhistory"
#define INTERACTIVEFILE ".ashinteractive"
#define LOGINFILE ".ashlogin"
void readhistory(void) {
FILE *file;
- if (!(file = fopen(prependhome(HISTORYFILE), "r"))) {
+ if (!(file = fopen(catpath(home, HISTORYFILE), "r"))) {
if (errno == ENOENT) return;
fatal("Unable to open history file for reading");
}
void writehistory(void) {
FILE *file;
- if (!(file = fopen(prependhome(HISTORYFILE), "w"))) {
+ if (!(file = fopen(catpath(home, HISTORYFILE), "w"))) {
note("Unable to open history file for writing");
return;
}
#include "stack.h"
#include "utils.h"
-#define PROMPT "> " // TODO: Have prompt be an environment variable
-
-enum character {
+enum {
CTRLC = '\003',
CTRLD,
BACKSPACE = '\010',
if (!origscript) {
origscript = script;
origstr = string;
- script = prependhome(name);
+ script = catpath(home, name);
}
if (!(result = scriptinput())) {
return result;
}
-static void waitbgsig(int sig) {
- (void)sig;
- waitbg();
+static size_t prompt(void) {
+ char *p;
+
+ if (!(p = getenv("PROMPT")) && setenv("PROMPT", p = DEFAULTPROMPT, 1) == -1)
+ note("Unable to update $PROMPT$ environment variable");
+ printf("%s ", p);
+ return strlen(p) + 1;
}
INPUT(userinput) {
char *cursor, *end;
+ size_t promptlen;
unsigned int c;
int i;
- signal(SIGCHLD, waitbgsig); // TODO: Use sigaction for portability
-
end = cursor = buffer;
*history.t = *buffer = '\0';
history.c = history.t;
while (buffer == end) {
- fputs(PROMPT, stdout);
+ promptlen = prompt();
while ((c = getchar()) != '\r') switch (c) {
default:
if (c >= ' ' && c <= '~') {
return buffer;
case CTRLD:
puts("^D\r");
- signal(SIGCHLD, SIG_DFL); // XXX
return NULL;
case CLEAR:
fputs("\033[H\033[J", stdout);
- fputs(PROMPT, stdout);
+ prompt();
fputs(buffer, stdout);
continue;
case ESCAPE:
if (history.c == (c == UP ? history.b : history.t)) continue;
putchar('\r');
- for (i = end - buffer + strlen(PROMPT); i > 0; --i) putchar(' ');
+ for (i = end - buffer + promptlen; i > 0; --i) putchar(' ');
putchar('\r');
if (strcmp((char *)history.c, buffer) != 0)
strcpy(buffer, (char *)history.c);
end = cursor = buffer + strlen(buffer);
- fputs(PROMPT, stdout);
+ prompt();
fputs(buffer, stdout);
break;
case LEFT:
}
puts("\r");
}
- fpurge(stdout);
push(&history, buffer);
*end++ = ';';
*end = '\0';
- signal(SIGCHLD, SIG_DFL); // XXX
-
return buffer;
}
static struct job jobarr[MAXJOBS + 1];
INITSTACK(jobs, jobarr, 0);
struct termios raw, canonical;
+struct sigaction sigchld, sigdfl, sigign;
+static int fgstatus;
void *findjob(pid_t jobid) {
if (jobs.b == jobs.t) return NULL;
}
int waitfg(struct job job) {
- int status, pgid, result;
+ while (waitpid(job.id, NULL, 0) != -1);
+ errno = 0;
- do {
- errno = 0;
- waitpid(job.id, &status, WUNTRACED);
- } while (errno == EINTR);
- if (!errno && !WIFSTOPPED(status)) do {
- errno = 0;
- while (waitpid(-job.id, NULL, 0) != -1);
- } while (errno == EINTR);
- result = errno != ECHILD ? errno : 0;
-
- // TODO: Use sigaction >:(
- if ((pgid = getpgid(0)) == -1 || signal(SIGTTOU, SIG_IGN) == SIG_ERR
- || tcsetpgrp(STDIN_FILENO, pgid) == -1
- || signal(SIGTTOU, SIG_DFL) == SIG_ERR) {
+ if (sigaction(SIGTTOU, &sigign, NULL) == -1
+ || tcsetpgrp(STDIN_FILENO, getpgrp()) == -1
+ || sigaction(SIGTTOU, &sigdfl, NULL) == -1) {
note("Unable to reclaim foreground; terminating");
deinitialize();
exit(EXIT_FAILURE);
if (tcgetattr(STDIN_FILENO, &job.config) == -1)
note("Unable to save termios config of job %d", job.id);
setconfig(&raw);
- if (result) return result;
- if (WIFSIGNALED(status)) {
- result = WTERMSIG(status);
- puts("\r");
- } else if (WIFSTOPPED(status)) {
- result = WSTOPSIG(status);
- job.type = SUSPENDED;
- if (push(&jobs, &job)) return result;
+ if (WIFEXITED(fgstatus)) return WEXITSTATUS(fgstatus);
+ else if (WIFSIGNALED(fgstatus)) return WTERMSIG(fgstatus);
+ job.type = SUSPENDED;
+ if (!push(&jobs, &job)) {
note("Unable to add job %d to list; too many jobs\r\n"
- "Press any key to continue", job.id);
+ "Press any key to continue", job.id);
getchar();
if (setfg(job)) return waitfg(job);
note("Manual intervention required for job %d", job.id);
- } else if (WIFEXITED(status)) result = WEXITSTATUS(status);
-
- return result;
+ }
+ return WSTOPSIG(fgstatus);
}
-void waitbg(void) {
+void sigchldhandler(int sig) {
int status;
pid_t id;
- for (jobs.c = jobs.b; jobs.c != jobs.t; INC(jobs, c)) {
- if (CURRENT->type != BACKGROUND) continue;
- id = CURRENT->id;
-
- // TODO: weird EINTR thing here too??
- while ((id = waitpid(-id, &status, WNOHANG | WUNTRACED)) > 0)
+ (void)sig;
+ while ((id = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0) {
+ for (jobs.c = jobs.b; jobs.c != jobs.t; INC(jobs, c)) if (CURRENT->id == id) {
if (WIFSTOPPED(status)) CURRENT->type = SUSPENDED;
-
- if (id == -1 && errno != ECHILD)
- note("Unable to wait on some child processes");
+ else deletejob();
+ break;
+ }
+ if (jobs.c == jobs.t) {
+ fgstatus = status;
+ if (!WIFSTOPPED(fgstatus)) while (waitpid(-id, NULL, 0) != -1);
+ }
}
}
extern struct stack jobs;
extern struct termios raw, canonical;
+extern struct sigaction sigchld, sigdfl, sigign;
void *findjob(pid_t jobid);
void *deletejob(void);
int setconfig(struct termios *mode);
int setfg(struct job job);
int waitfg(struct job job);
-void waitbg(void);
+void sigchldhandler(int sig);
#include "run.h"
#include "utils.h"
-int main(int argc, char **argv) {
- options(&argc, &argv);
+int main(int localargc, char **localargv) {
+ argc = localargc;
+ argv = localargv;
+
+ options();
initialize();
#include <err.h>
+#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "input.h"
#include "options.h"
-int login, interactive;
+int login, interactive, argc;
Input input;
+char **argv;
-void options(int *argcp, char ***argvp) {
+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(void) {
int opt, l;
- char *usage = "TODO: WRITE USAGE";
- login = ***argvp == '-';
+ login = **argv == '-';
interactive = 1;
input = userinput;
- while ((opt = getopt(*argcp, *argvp, ":c:hl")) != -1) switch (opt) {
- case 'c':
- interactive = 0;
- input = stringinput;
- string = optarg;
- break;
- case 'h':
- errx(EXIT_SUCCESS, "%s", usage);
- case 'l':
- login = 1;
- break;
- case ':':
- errx(EXIT_FAILURE, "Expected argument following `-%c'\n%s", optopt, usage);
- case '?':
- default:
- errx(EXIT_FAILURE, "Unknown command line option `-%c'\n%s", optopt, usage);
+ while ((opt = getopt(argc, argv, ":c:hl")) != -1) {
+ switch (opt) {
+ case 'c':
+ interactive = 0;
+ input = stringinput;
+ string = optarg;
+ break;
+ case 'h':
+ usage(*argv, EXIT_SUCCESS);
+ case 'l':
+ login = 1;
+ break;
+ case ':':
+ warnx("Expected argument following `-%c'\n", optopt);
+ usage(*argv, EXIT_FAILURE);
+ case '?':
+ default:
+ warnx("Unknown command line option `-%c'\n", optopt);
+ usage(*argv, EXIT_FAILURE);
+ }
+ if (opt == 'c') break;
}
- *argcp -= optind;
- *argvp += optind;
-
- if (!string && **argvp) {
+ if (!string && argv[optind]) {
interactive = 0;
input = scriptinput;
- script = **argvp;
+ script = argv[optind];
+ }
+ if (!interactive) {
+ argc -= optind;
+ argv += optind;
}
}
typedef INPUT((*Input));
-extern int login, interactive;
+extern int login, interactive, argc;
extern Input input;
+extern char **argv;
-void options(int *argcp, char ***argvp);
+void options(void);
#include "config.h"
#include "input.h"
+#include "options.h"
#include "parse.h"
#include "utils.h"
struct cmd empty = {0};
struct cmd *parse(char *b) {
- char **t, *name, *value, *end, *p, *env;
+ char **t, *name, *value, *stlend, *p, *end, *env;
struct cmd *c;
long l;
int e, offset;
c->r->newfd = *b == '>';
if (*(b - 1)) {
if (c->args == --t) c->args = NULL;
- if ((l = strtol(*t, &end, 10)) < 0 || l > INT_MAX || end != b) {
- note("Incorrect syntax for file redirection\r");
+ if ((l = strtol(*t, &stlend, 10)) < 0 || l > INT_MAX || stlend != b) {
+ note("Incorrect syntax for file redirection");
return ∅
}
c->r->newfd = l;
if (*end == '\\') ++end;
}
if (!b) {
- note("Quote left open-ended\r");
+ note("Quote left open-ended");
return ∅
}
memmove(p, p + 1, end-- - p);
p = b++;
while (*b && *b != '$') ++b;
if (!*b) {
- note("Environment variable lacks a terminating `$'\r");
+ note("Environment variable lacks a terminating `$'");
return ∅
}
*b++ = '\0';
for (end = b; *end; ++end);
- if ((env = getenv(p + 1)) == NULL) {
- note("Environment variable does not exist\r");
+ l = strtol(p + 1, &stlend, 10);
+ if (stlend == b - 1) env = l >= 0 && l < argc ? argv[l] : b - 1;
+ else if ((env = getenv(p + 1)) == NULL) {
+ note("Environment variable does not exist");
return ∅
}
e = strlen(env);
*b = '\0';
c->r->mode = END;
for (c->r = c->rds; c->r->mode; ++c->r) if (*c->r->oldname == '&') {
- if ((l = strtol(++c->r->oldname, &end, 10)) < 0 || l > INT_MAX || *end) {
- note("Incorrect syntax for file redirection\r");
+ if ((l = strtol(++c->r->oldname, &stlend, 10)) < 0
+ || l > INT_MAX || *stlend) {
+ note("Incorrect syntax for file redirection");
return ∅
}
c->r->oldfd = l;
*b = '\0';
if (value) {
if (setenv(name, value, 1) == -1) {
- note("Unable to set environment variable\r");
+ note("Unable to set environment variable");
return ∅
}
value = name = NULL;
case AND:
case PIPE:
case OR:
- note("Expected another command\r");
+ note("Expected another command");
return ∅
default:
break;
if (cmd->term == OR && status == 0) break;
}
}
- waitbg();
return 1;
}
#include <err.h>
+#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include "options.h"
#include "utils.h"
+char *home;
+
void note(char *fmt, ...) {
va_list args;
va_start(args, fmt);
(errno ? vwarn : vwarnx)(fmt, args);
va_end(args);
putchar('\r');
+ errno = 0;
}
void fatal(char *fmt, ...) {
exit(EXIT_FAILURE);
}
-char *prependhome(char *name) {
- static char *p, path[MAXPATH + 1];
+char *catpath(char *dir, char *filename) {
+ static char path[MAXPATH + 1];
- if (!p) {
- if (!(p = getenv("HOME")))
- fatal("Unable to access $HOME$ environment variable");
- strcpy(path, p);
- strcat(path, "/");
- p = path + strlen(path);
- }
- *p = '\0';
- strcat(path, name);
+ strcpy(path, dir);
+ strcat(path, "/");
+ strcat(path, filename);
return path;
}
-void initialize(void) { // <-- TODO: Set $SHLVL$ in this function
- cfmakeraw(&raw);
+void initialize(void) {
+ char *shlvlstr;
+ long shlvl;
+
+ // Raw mode
if (tcgetattr(STDIN_FILENO, &canonical) == -1)
- fatal("Unable to get default termios config");
+ err(EXIT_FAILURE, "Unable to get default termios config");
+ cfmakeraw(&raw);
if (!setconfig(&raw)) exit(EXIT_FAILURE);
+
+ // Set signal actions
+ sigchld.sa_handler = sigchldhandler;
+ sigdfl.sa_handler = SIG_DFL;
+ sigign.sa_handler = SIG_IGN;
+ if (sigaction(SIGCHLD, &sigchld, NULL) == -1)
+ fatal("Unable to install SIGCHLD handler");
+
+ // Initialize `home'
+ if (!(home = getenv("HOME")))
+ fatal("Unable to locate user's home directory, $HOME$ not set");
+
+ // Update $SHLVL$
+ if (!(shlvlstr = getenv("SHLVL"))) shlvlstr = "0";
+ if ((shlvl = strtol(shlvlstr, NULL, 10)) < 0) shlvl = 0;
+ asprintf(&shlvlstr, "%ld", shlvl + 1);
+ if (setenv("SHLVL", shlvlstr, 1) == -1)
+ note("Unable to update $SHLVL$ environment variable");
+ free(shlvlstr);
+
+ // History read
if (interactive) readhistory();
}
void deinitialize(void) {
+
+ // History write
if (interactive) writehistory();
+
+ // Canonical mode
setconfig(&canonical);
}
+extern char *home;
+
void note(char *fmt, ...);
void fatal(char *fmt, ...);
-char *prependhome(char *name);
+char *catpath(char *dir, char *filename);
void initialize(void);
void deinitialize(void);