diff --git a/Makefile b/Makefile index 2c04f8a..79ec2fc 100644 --- a/Makefile +++ b/Makefile @@ -2,12 +2,17 @@ CC?=$(CROSS_COMPILE)gcc CFLAGS?=-ggdb \ -O0 \ -Wall \ - -Werror + -Werror \ + -Iinclude LDFLAGS?=-lgcc O=build -sh_OBJS=$(O)/sh.o +sh_OBJS=$(O)/sh.o \ + $(O)/readline.o \ + $(O)/cmd.o \ + $(O)/parse.o \ + $(O)/builtin.o all: mkdirs $(O)/sh @@ -23,5 +28,5 @@ install: $(O)/sh $(O)/sh: $(sh_OBJS) $(CC) $(LDFLAGS) -o $@ $(sh_OBJS) -$(O)/%.o: %.c +$(O)/%.o: src/%.c $(CC) $(CFLAGS) -c -o $@ $< diff --git a/include/builtin.h b/include/builtin.h new file mode 100644 index 0000000..8b62860 --- /dev/null +++ b/include/builtin.h @@ -0,0 +1,7 @@ +#pragma once + +struct cmd_unit; + +typedef int (*builtin_func_t) (const struct cmd_unit *); + +builtin_func_t builtin_find(const char *name); diff --git a/include/cmd.h b/include/cmd.h new file mode 100644 index 0000000..995fda0 --- /dev/null +++ b/include/cmd.h @@ -0,0 +1,14 @@ +#pragma once + +//struct cmd_exec { +// int in_fd, out_fd, err_fd; +// size_t argc; +// char *args[12]; +//}; +// +//struct cmd_link { +// struct cmd_exec cmd; +// struct cmd_link *prev, *next; +//}; + +int eval(char *str); diff --git a/include/config.h b/include/config.h new file mode 100644 index 0000000..d8b59cf --- /dev/null +++ b/include/config.h @@ -0,0 +1,17 @@ +#pragma once + +#define COLOR_RED "\033[31m" +#define COLOR_GREEN "\033[32m" +#define COLOR_YELLOW "\033[33m" +#define COLOR_MAGENTA "\033[35m" +#define COLOR_CYAN "\033[36m" +#define COLOR_RESET "\033[0m" + +// TODO: take this from env (kernel does not support env yet) +#define PATH "/bin" +#define PS1 COLOR_MAGENTA "%u" COLOR_RESET \ + "@" \ + COLOR_CYAN "%h" COLOR_RESET " " \ + COLOR_YELLOW "%d" COLOR_RESET \ + " %# " + diff --git a/include/parse.h b/include/parse.h new file mode 100644 index 0000000..6569c26 --- /dev/null +++ b/include/parse.h @@ -0,0 +1,15 @@ +#pragma once + +struct cmd_unit { + int fds[3]; + int pid, res; + int argc; + char *args[64]; + struct cmd_unit *prev, *next; +}; + +struct cmd { + struct cmd_unit *first, *last; +}; + +int cmd_parse(const char *str, struct cmd *cmd); diff --git a/include/readline.h b/include/readline.h new file mode 100644 index 0000000..61bbeaa --- /dev/null +++ b/include/readline.h @@ -0,0 +1,3 @@ +#pragma once + +int readline(char *buf, size_t lim); diff --git a/sh.c b/sh.c deleted file mode 100644 index a20c7f4..0000000 --- a/sh.c +++ /dev/null @@ -1,6 +0,0 @@ -#include - -int main(int argc, char **argv) { - printf("This is a stub\n"); - return 0; -} diff --git a/src/builtin.c b/src/builtin.c new file mode 100644 index 0000000..b0f7dfb --- /dev/null +++ b/src/builtin.c @@ -0,0 +1,297 @@ +#include +#include +#include +#include +#include +#include +#include "builtin.h" +#include "config.h" +#include "parse.h" +#include "cmd.h" + +#define DEF_BUILTIN(b_name) \ + static int __bcmd_##b_name(const struct cmd_unit *cmd) +#define DECL_BUILTIN(b_name) \ + { .name = #b_name, .exec = __bcmd_##b_name } + +extern char **environ; + +struct sh_builtin { + const char *name; + int (*exec) (const struct cmd_unit *cmd); +}; + +static struct sh_builtin __builtins[]; + +//// + +DEF_BUILTIN(env) { + for (size_t i = 0; environ[i]; ++i) { + printf("%s\n", environ[i]); + } + + return 0; +} + +// XXX: Broken +//DEF_BUILTIN(exit) { +// if (cmd->argc > 1) { +// exit(atoi(cmd->args[1])); +// } +// exit(0); +//} + +DEF_BUILTIN(cd) { + if (cmd->argc != 2) { + printf("usage: cd \n"); + return -1; + } + return chdir(cmd->args[1]); +} + +DEF_BUILTIN(cat) { + if (cmd->argc < 2) { + printf("usage: cat ...\n"); + return -1; + } + char buf[4096]; + ssize_t bread; + + for (int i = 1; i < cmd->argc; ++i) { + int fd = open(cmd->args[i], O_RDONLY, 0); + if (fd < 0) { + perror(cmd->args[i]); + continue; + } + + while ((bread = read(fd, buf, sizeof(buf))) > 0) { + write(STDOUT_FILENO, buf, bread); + } + + if (bread < 0) { + perror(cmd->args[i]); + } + + close(fd); + } + return 0; +} + +DEF_BUILTIN(chmod) { + mode_t mode = 0; + const char *p; + if (cmd->argc != 3) { + printf("usage: chmod \n"); + return -1; + } + p = cmd->args[1]; + while (*p) { + if (*p > '7' || *p < '0') { + printf("Invalid mode: %s\n", cmd->args[1]); + return -1; + } + mode <<= 3; + mode |= *p - '0'; + ++p; + } + + if (chmod(cmd->args[2], mode)) { + perror(cmd->args[2]); + return -1; + } + + return 0; +} + +DEF_BUILTIN(stat) { + struct stat st; + if (cmd->argc != 2) { + printf("usage: stat \n"); + return -1; + } + + if (stat(cmd->args[1], &st) != 0) { + return -1; + } + + printf("device %u\n", (unsigned int) st.st_dev); // TODO: __dev_t + printf("inode %u\n", (unsigned int) st.st_ino); // TODO: __ino_t + printf("mode %u\n", st.st_mode); + printf("nlink %u\n", (unsigned int) st.st_nlink); // TODO: __nlink_t + printf("uid %u\n", st.st_uid); + printf("gid %u\n", st.st_gid); + printf("rdev %u\n", (unsigned int) st.st_rdev); // TODO: __dev_t + printf("size %u\n", (unsigned int) st.st_size); // TODO: off_t here + printf("atime %u\n", (unsigned int) st.st_atime); + printf("mtime %u\n", (unsigned int) st.st_mtime); + printf("ctime %u\n", (unsigned int) st.st_ctime); + printf("blksize %u\n", (unsigned int) st.st_blksize); // TODO: __blksize_t + printf("blocks %u\n", (unsigned int) st.st_blocks); // TODO: __blkcnt_t + + return 0; +} + +DEF_BUILTIN(sync) { + sync(); + return 0; +} + +DEF_BUILTIN(touch) { + int fd; + if (cmd->argc != 2) { + printf("usage: touch \n"); + return -1; + } + + if ((fd = open(cmd->args[1], O_WRONLY | O_CREAT, 0755)) < 0) { + perror(cmd->args[1]); + return -1; + } + + close(fd); + + return 0; +} + +DEF_BUILTIN(chown) { + uid_t uid; + gid_t gid; + const char *p; + + if (cmd->argc != 3) { + printf("usage: chown : \n"); + return -1; + } + + if (!(p = strchr(cmd->args[1], ':'))) { + printf("Invalid UID:GID pair: %s\n", cmd->args[1]); + return -1; + } + uid = atoi(cmd->args[1]); + gid = atoi(p + 1); + + if (chown(cmd->args[2], uid, gid)) { + perror(cmd->args[2]); + return -1; + } + + return 0; +} + +DEF_BUILTIN(exec) { + //struct cmd_exec new_cmd; + if (cmd->argc < 2) { + printf("usage: exec ...\n"); + } + + char path_path[256]; + int res; + + if (cmd->args[1][0] == '.' || cmd->args[1][0] == '/') { + if ((res = access(cmd->args[1], X_OK)) != 0) { + perror(cmd->args[1]); + return res; + } + + strcpy(path_path, cmd->args[1]); + exit(execve(path_path, (char *const *) &cmd->args[1], NULL)); + } + + snprintf(path_path, sizeof(path_path), "%s/%s", PATH, cmd->args[1]); + if ((res = access(path_path, X_OK)) == 0) { + exit(execve(path_path, (char *const *) &cmd->args[1], NULL)); + } + + return -1; +} + +DEF_BUILTIN(clear) { + printf("\033[2J\033[1;1f"); + return 0; +} + +DEF_BUILTIN(echo) { + for (int i = 1; i < cmd->argc; ++i) { + printf("%s ", cmd->args[i]); + } + printf("\n"); + return 0; +} + +DEF_BUILTIN(set) { + if (cmd->argc != 3) { + fprintf(stderr, "Usage: set \n"); + return -1; + } + + setenv(cmd->args[1], cmd->args[2], 1); + + return 0; +} + +// TODO: support usernames (getpwnam_r) +DEF_BUILTIN(setid) { + if (cmd->argc == 2) { + // Assume gid == uid + if (setuid(atoi(cmd->args[1])) != 0) { + return -1; + } + if (setgid(atoi(cmd->args[1])) != 0) { + return -1; + } + return 0; + } + if (cmd->argc != 3) { + printf("usage: setid \n"); + return -1; + } + + if (setuid(atoi(cmd->args[1])) != 0) { + return -1; + } + if (setgid(atoi(cmd->args[2])) != 0) { + return -1; + } + return 0; +} + +DEF_BUILTIN(builtins) { + for (size_t i = 0; __builtins[i].name; ++i) { + printf("%s ", __builtins[i].name); + } + printf("\n"); + return 0; +} + +//// + +static struct sh_builtin __builtins[] = { + DECL_BUILTIN(builtins), + DECL_BUILTIN(cat), + DECL_BUILTIN(cd), + DECL_BUILTIN(chmod), + DECL_BUILTIN(chown), + DECL_BUILTIN(clear), + DECL_BUILTIN(echo), + DECL_BUILTIN(env), + DECL_BUILTIN(exec), +// DECL_BUILTIN(exit), + DECL_BUILTIN(set), + DECL_BUILTIN(setid), + DECL_BUILTIN(stat), + DECL_BUILTIN(sync), + DECL_BUILTIN(touch), + {NULL} +}; + +builtin_func_t builtin_find(const char *name) { + for (size_t i = 0; i < sizeof(__builtins) / sizeof(__builtins[0]); ++i) { + if (!__builtins[i].name) { + return NULL; + } + if (!strcmp(__builtins[i].name, name)) { + return __builtins[i].exec; + } + } + return NULL; +} diff --git a/src/cmd.c b/src/cmd.c new file mode 100644 index 0000000..b5dd061 --- /dev/null +++ b/src/cmd.c @@ -0,0 +1,203 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "builtin.h" +#include "config.h" +#include "parse.h" +#include "cmd.h" + +typedef int (*spawn_func_t) (void *arg, struct cmd_unit *cmd); +extern char **environ; + +static int spawn_binary(void *arg, struct cmd_unit *cmd) { + return execve(arg, (char *const *) cmd->args, environ); +} + +static int spawn_builtin(void *arg, struct cmd_unit *cmd) { + builtin_func_t func = arg; + return func(cmd); +} + +static int cmd_spawn(spawn_func_t fn, void *arg, struct cmd_unit *cmd, int *pgid) { + int pid; + int pipe_fds[2]; + + if (cmd->next) { + // Create pipe pair for this -> next communication + int res = pipe(pipe_fds); + if (res != 0) { + return -1; + } + + // this stdout = write end + cmd->fds[1] = pipe_fds[1]; + // next stdin = read end + cmd->next->fds[0] = pipe_fds[0]; + } + + if ((pid = fork()) < 0) { + perror("fork()"); + return -1; + } + + if (pid == 0) { + pid = getpid(); + if (*pgid == -1) { + *pgid = pid; + } + setpgid(pid, *pgid); + + if (cmd->prev) { + dup2(cmd->fds[0], STDIN_FILENO); + close(cmd->fds[0]); + } + if (cmd->next) { + dup2(cmd->fds[1], STDOUT_FILENO); + close(cmd->fds[1]); + } + + if (isatty(STDIN_FILENO)) { + signal(SIGTTOU, SIG_IGN); + tcsetpgrp(STDIN_FILENO, *pgid); + } + + exit(fn(arg, cmd)); + } else { + cmd->pid = pid; + if (cmd->next) { + // Close write end, it's now handled by child + close(pipe_fds[1]); + } + if (cmd->prev) { + // Close read end, it's now handled by child + close(cmd->fds[0]); + } + return 0; + } +} + +static int cmd_exec_binary(struct cmd_unit *cmd, int *pgid) { + char path_path[256]; + int res; + + if (cmd->args[0][0] == '.' || cmd->args[0][0] == '/') { + if ((res = access(cmd->args[0], X_OK)) != 0) { + perror(cmd->args[0]); + return res; + } + + strcpy(path_path, cmd->args[0]); + return cmd_spawn(spawn_binary, path_path, cmd, pgid); + } + + const char *pathvar = getenv("PATH"); + if (!pathvar || !*pathvar) { + return -1; + } + + const char *p = pathvar; + const char *e; + while (1) { + e = strchr(p, ':'); + size_t len; + if (!e) { + len = strlen(p); + } else { + len = e - p; + } + + strncpy(path_path, p, len); + path_path[len] = '/'; + strcpy(path_path + len + 1, cmd->args[0]); + + if ((res = access(path_path, X_OK)) == 0) { + return cmd_spawn(spawn_binary, path_path, cmd, pgid); + } + + if (!e) { + break; + } + p = e + 1; + } + + return -1; +} + +static int cmd_unit_exec(struct cmd_unit *cmd, int *pgid) { + int res; + builtin_func_t builtin; + + // TODO: don't spawn ALL the commands as separate processes + // e.g. exit is broken and $$ evaluation will be incorrect + // this is just a dirty workaround for "exit" + if (!strcmp(cmd->args[0], "exit")) { + if (cmd->argc > 1) { + exit(atoi(cmd->args[1])); + } + exit(0); + } else { + if ((builtin = builtin_find(cmd->args[0])) != NULL) { + return cmd_spawn(spawn_builtin, builtin, cmd, pgid); + } + } + + if ((res = cmd_exec_binary(cmd, pgid)) == 0) { + return 0; + } + + printf("sh: command not found: %s\n", cmd->args[0]); + return -1; +} + +int eval(char *str) { + char *p; + struct cmd cmd; + int cmd_res, pgid; + + while (isspace(*str)) { + ++str; + } + + if ((p = strchr(str, '#'))) { + *p = 0; + } + + if (!*str) { + return 0; + } + + if (cmd_parse(str, &cmd) != 0) { + return -1; + } + + // Spawn all subprocesses + pgid = -1; + for (struct cmd_unit *u = cmd.first; u; u = u->next) { + u->pid = -1; + cmd_unit_exec(u, &pgid); + } + + // Wait for spawned subprocesses to finish + for (struct cmd_unit *u = cmd.first; u; u = u->next) { + if (u->pid != -1) { + if (waitpid(u->pid, &cmd_res, 0) < 0) { + perror("waitpid()"); + } + } + } + + // Regain control of foreground group + pgid = getpgid(0); + signal(SIGTTOU, SIG_IGN); + tcsetpgrp(STDIN_FILENO, pgid); + + return 0; +} diff --git a/src/parse.c b/src/parse.c new file mode 100644 index 0000000..a260a99 --- /dev/null +++ b/src/parse.c @@ -0,0 +1,86 @@ +#include "parse.h" + +#include +#include +#include + +static int cmd_unit_parse(const char *str, struct cmd_unit *unit) { + // Parse until end of string or | + size_t len; + char *word = NULL; + char c; + unit->argc = 0; + + // TODO: escapes + // TODO: variable substitution + while ((c = *str++) && c != '|') { + if (isspace(c)) { + if (word) { + word[len] = 0; + unit->args[unit->argc++] = word; + word = NULL; + } + } else { + if (!word) { + word = malloc(256); + len = 0; + } + + if (len == 255) { + return -1; + } + word[len++] = c; + } + } + + if (word) { + word[len] = 0; + unit->args[unit->argc++] = word; + } + + unit->args[unit->argc] = NULL; + + return 0; +} + +int cmd_parse(const char *str, struct cmd *cmd) { + const char *p = str; + const char *e; + + cmd->last = NULL; + + while (1) { + while (isspace(*p)) ++p; + if (!*p) { + break; + } + + // TODO: escaped/quoted | + // and || + e = strchr(p, '|'); + + if (p != e) { + struct cmd_unit *unit = malloc(sizeof(struct cmd_unit)); + if (cmd_unit_parse(p, unit) != 0) { + // TODO: cleanup + return -1; + } + + unit->next = NULL; + if (cmd->last) { + cmd->last->next = unit; + } else { + cmd->first = unit; + } + unit->prev = cmd->last; + cmd->last = unit; + } + + if (!e) { + break; + } + p = e + 1; + } + + return 0; +} diff --git a/src/readline.c b/src/readline.c new file mode 100644 index 0000000..bae5fc7 --- /dev/null +++ b/src/readline.c @@ -0,0 +1,161 @@ +//#include +#include +#include + +//#define KEY_UP (256) +//#define KEY_DOWN (257) +//#define KEY_RIGHT (258) +//#define KEY_LEFT (259) +// +//// With support for escape sequences +//static int getch_del(void) { +// struct timeval tv = { +// .tv_sec = 0, +// .tv_usec = 500000 +// }; +// fd_set fds; +// FD_ZERO(&fds); +// FD_SET(STDIN_FILENO, &fds); +// int res; +// +// if ((res = select(STDIN_FILENO + 1, &fds, NULL, NULL, &tv)) < 0) { +// return res; +// } +// +// if (FD_ISSET(STDIN_FILENO, &fds)) { +// res = 0; +// if (read(STDIN_FILENO, &res, 1) != 1) { +// return -1; +// } +// return res; +// } else { +// return -1; +// } +//} +// +//static int escape_read(void) { +// // Maybe [ +// int c = getch_del(); +// +// if (c < 0) { +// return '\033'; +// } +// +// if (c != '[') { +// return -1; +// } +// +// c = getch_del(); +// +// switch (c) { +// case 'A': +// return KEY_UP; +// case 'B': +// return KEY_DOWN; +// case 'C': +// return KEY_RIGHT; +// case 'D': +// return KEY_LEFT; +// default: +// return -1; +// } +//} +// +//static int getch(void) { +// char c; +// +// if (read(STDIN_FILENO, &c, 1) != 1) { +// return -1; +// } +// +// if (c == '\033') { +// int r = escape_read(); +// +// if (r > 0) { +// return r; +// } +// } +// +// return c; +//} + +int readline(char *buf, size_t lim) { + // TODO: rewrite this for new kernel line discipline to disable ECHO/ICANON + ssize_t len = read(STDIN_FILENO, buf, lim); + + // Just strip newline + if (len <= 0) { + return -1; + } + + if (buf[len - 1] == '\n') { + buf[len - 1] = 0; + } + + //printf("Got len = %ld\n", len); + //while (1); + + //int len = 0; + //int cur = 0; + //int chr; + + //while (1) { + // if ((chr = getch()) <= 0) { + // return -1; + // } + + // if (len == lim) { + // printf("Input line is too long\n"); + // return -1; + // } + + // switch (chr) { + // case KEY_LEFT: + // if (cur) { + // puts2("\033[D"); + // --cur; + // } + // break; + // case KEY_RIGHT: + // if (cur < len) { + // puts2("\033[C"); + // ++cur; + // } + // break; + // } + + // if (chr == '\n') { + // putchar(chr); + // buf[len] = 0; + // break; + // } else if (chr == '\b') { + // if (cur) { + // --cur; + // for (int i = cur; i < len - 1; ++i) { + // buf[i] = buf[i + 1]; + // } + // puts2("\033[D\033[s"); + // --len; + // for (int i = cur; i < len; ++i) { + // putchar(buf[i]); + // } + // puts2(" \033[u"); + // } + // } else if (chr >= ' ' && chr < 255) { + // putchar(chr); + // puts2("\033[s"); + // for (int i = cur; i < len; ++i) { + // putchar(buf[i]); + // } + // puts2("\033[u"); + // for (int i = len; i > cur; --i) { + // buf[i] = buf[i - 1]; + // } + // buf[cur++] = chr; + // ++len; + // } + //} + + return len; +} + diff --git a/src/sh.c b/src/sh.c new file mode 100644 index 0000000..4626cbf --- /dev/null +++ b/src/sh.c @@ -0,0 +1,146 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "readline.h" +#include "config.h" +#include "cmd.h" + +// Data needed for prompt +static char p_hostname[64]; +static char p_username[64]; +static char p_cwd[256]; +static uid_t p_uid; +//static gid_t p_gid; + +static void update_prompt(void) { + struct utsname _u; + struct passwd _p; + struct passwd *p; + char pwbuf[256]; + + p_uid = getuid(); + + if (getcwd(p_cwd, sizeof(p_cwd)) == NULL) { + perror("getcwd()"); + p_cwd[0] = 0; + } + + if (uname(&_u) != 0) { + perror("uname()"); + } else { + strcpy(p_hostname, _u.nodename); + } + if (getpwuid_r(p_uid, &_p, pwbuf, sizeof(pwbuf), &p) != 0) { + perror("getpwuid_r()"); + p_username[0] = 0; + } else { + strcpy(p_username, p->pw_name); + } +} + +static void display_prompt(void) { + const char *p = PS1; + + while (*p) { + if (*p == '%') { + switch (*++p) { + case '#': + putchar(p_uid == 0 ? '#' : '$'); + break; + case 'h': + fputs(p_hostname, stdout); + break; + case 'u': + fputs(p_username, stdout); + break; + case 'd': + fputs(p_cwd, stdout); + break; + default: + putchar('%'); + putchar(*p); + break; + } + } else { + putchar(*p); + } + + ++p; + } + + fflush(stdout); +} + +static void signal_handle(int signum) { + switch (signum) { + case SIGINT: + printf("\n"); + update_prompt(); + display_prompt(); + break; + default: + printf("\nUnhandled signal: %d\n", signum); + break; + } +} + +int main(int argc, char **argv) { + int fd = STDIN_FILENO; + char linebuf[256]; + int res; + + signal(SIGINT, signal_handle); + + if (argc == 2) { + fd = open(argv[1], O_RDONLY, 0); + + if (fd < 0) { + perror(argv[1]); + return -1; + } + } else if (argc > 2) { + printf("usage: sh [filename]\n"); + return -1; + } + + // TODO: source /etc/profile? + setenv("PATH", "/sbin:/bin", 0); + + if (!isatty(fd)) { + while (1) { + // TODO: use fgets here + //if (gets_safe(fd, linebuf, sizeof(linebuf)) < 0) { + // break; + //} + + eval(linebuf); + } + + if (fd != STDIN_FILENO) { + close(fd); + } + + return 0; + } + + while (1) { + update_prompt(); + display_prompt(); + + if (readline(linebuf, sizeof(linebuf)) < 0) { + break; + } + + if ((res = eval(linebuf)) != 0) { + printf(COLOR_RED "Status: %d" COLOR_RESET "\n", res); + } + } + + return 0; +}