diff options
Diffstat (limited to 'usr.bin/osh')
-rw-r--r-- | usr.bin/osh/osh.c | 416 |
1 files changed, 347 insertions, 69 deletions
diff --git a/usr.bin/osh/osh.c b/usr.bin/osh/osh.c index 2f592ad..71ca6de 100644 --- a/usr.bin/osh/osh.c +++ b/usr.bin/osh/osh.c @@ -29,63 +29,131 @@ #include <sys/types.h> #include <sys/cdefs.h> +#include <sys/errno.h> +#include <sys/spawn.h> +#include <sys/wait.h> #include <fcntl.h> #include <stddef.h> +#include <stdbool.h> #include <unistd.h> #include <string.h> +#include <stdio.h> -#define prcons(FD, STR) write((FD), (STR), strlen((STR))) +#define is_printable(C) ((C) >= 32 && (C) <= 126) #define is_ascii(C) ((C) >= 0 && (C) <= 128) + +#define INPUT_SIZE 64 + +#define REPEAT "!!" +#define COMMENT '@' #define WELCOME \ ":::::::::::::::::::::::::::::::::::::::\n" \ ":: OSMORA GATEWAY ~ Every key echos ::\n" \ ":: ..... Proceed with purpose ..... ::\n" \ - ":::::::::::::::::::::::::::::::::::::::\n" + ":::::::::::::::::::::::::::::::::::::::" #define HELP \ "Default commands:\n" \ - "help - Display this help message\n" \ - "echo - Print the arguments to the console\n" \ - "exit - Exit the shell\n" + "help - Display this help message\n" \ + "echo - Print the arguments to the console\n" \ + "reboot - Reboot the machine\n" \ + "kmsg - Print kernel message buffer\n" \ + "fetch - System information\n" \ + "kfg - Start up kfgwm\n" \ + "bell - Toggle backspace bell\n" \ + "date - Get the current date\n" \ + "clear - Clear the screen\n" \ + "exit - Exit the shell" -#define PROMPT "[root::osmora]~ " +#define PROMPT "[%s::%s]~ " -static char buf[64]; -static uint8_t i; +static char last_command[INPUT_SIZE]; +static char buf[INPUT_SIZE]; static int running; +static int bell_fd; +static bool bs_bell = true; /* Beep on backspace */ -struct command { +static void cmd_help(int argc, char *argv[]); +static void cmd_echo(int argc, char *argv[]); +static void cmd_exit(int argc, char *argv[]); +static void cmd_bell(int argc, char *argv[]); +static void cmd_clear(int argc, char *argv[]); + +struct builtin_cmd { const char *name; - void (*func)(int fd, int argc, char *argv[]); + void (*func)(int argc, char *argv[]); +}; + +/* + * Results after parsing a command + * + * @bg: Run command in background + */ +struct parse_state { + uint8_t bg : 1; }; -void -cmd_help(int fd, int argc, char *argv[]) +static struct builtin_cmd cmds[] = { + {"help",cmd_help}, + {"exit",cmd_exit}, + {"bell", cmd_bell}, + {"clear", cmd_clear}, + {NULL, NULL} +}; + +static void +cmd_help(int argc, char *argv[]) { - prcons(fd, HELP); + puts(HELP); } -void -cmd_exit(int fd, int argc, char *argv[]) +static void +cmd_exit(int argc, char *argv[]) { running = 0; } -void -cmd_echo(int fd, int argc, char *argv[]) +static void +cmd_clear(int argc, char *argv[]) { - for (i = 1; i < argc; i++) { - prcons(fd, argv[i]); - prcons(fd, " "); + fputs("\033[2J", stdout); +} + +static void +cmd_bell(int argc, char *argv[]) +{ + const char *usage_str = "usage: bell [on/off]"; + const char *arg; + + if (argc < 2) { + puts(usage_str); + return; + } + + arg = argv[1]; + if (strcmp(arg, "on") == 0) { + bs_bell = true; + } else if (strcmp(arg, "off") == 0) { + bs_bell = false; + } else { + puts(usage_str); } - prcons(fd, "\n"); } -int -parse_args(char *input, char *argv[], int max_args) +static int +parse_args(char *input, char *argv[], int max_args, struct parse_state *p) { int argc = 0; + /* ignore comments */ + if (*input == '@') { + return 0; + } + + /* setup default state */ + p->bg = 0; + + /* parse loop */ while (*input != '\0') { /* skip leading spaces */ while (*input == ' ') { @@ -97,11 +165,26 @@ parse_args(char *input, char *argv[], int max_args) break; } + /* comment? */ + if (*input == COMMENT) { + break; + } + + /* run in background? */ + if (*input == '&') { + p->bg = 1; + } + if (argc < max_args) { argv[argc++] = input; /* mark start of the argument */ } /* move forward until next space or end */ while (*input != '\0' && *input != ' ') { + /* ignore comments */ + if (*input == COMMENT) { + return 0; + } + input++; } @@ -115,90 +198,285 @@ parse_args(char *input, char *argv[], int max_args) return argc; } -static char * -getstr(int fd) +/* + * Grab a string from stdin and return + * the resulting offset within the input + * buffer we are at. + */ +static uint8_t +getstr(void) { char c; - uint8_t input; - i = 0; + int input; + uint32_t beep_payload; + uint8_t buf_i = 0; + + /* + * Prepare the beep payload @ 500 Hz + * for 20ms + */ + beep_payload = 500; + beep_payload |= (30 << 16); for (;;) { - if (read(fd, &input, 2) <= 0) { + if ((input = getchar()) < 0) { continue; } - c = input & 0xFF; + c = (char)input; + if (c == '\t') { + continue; + } /* return on newline */ if (c == '\n') { - buf[i] = '\0'; - write(fd, "\n", 1); - return buf; + buf[buf_i] = '\0'; + putchar('\n'); + return buf_i; } /* handle backspaces and DEL */ if (c == '\b' || c == 127) { - if (i > 0) { - i--; - write(fd, "\b \b", 3); + if (buf_i > 0) { + buf_i--; + fputs("\b \b", stdout); + } else if (bell_fd > 0 && bs_bell) { + write(bell_fd, &beep_payload, sizeof(beep_payload)); } - } else if (is_ascii(c) && i < sizeof(buf) - 1) { + } else if (is_printable(c) && buf_i < sizeof(buf) - 1) { /* write to fd and add to buffer */ - buf[i++] = c; - write(fd, &c, 1); + buf[buf_i++] = c; + putchar(c); } } } -struct command cmds[] = { - {"help", cmd_help}, - {"echo", cmd_echo}, - {"exit", cmd_exit}, - {NULL, NULL} -}; +static void +builtin_run(struct builtin_cmd *cmd, int argc, char *argv[]) +{ + if (cmd->func != NULL) { + cmd->func(argc, argv); + return; + } +} -int -main(void) +static int +cmd_run(const char *input, int argc, char *argv[]) +{ + char bin_path[512]; + char *envp[1] = { NULL }; + pid_t child; + + /* + * If we can access the raw input as a file, try to + * spawn it as a program. This case would run if for + * example, the user entered /usr/sbin/foo, or some + * path directly into the console. + */ + if (access(input, F_OK) == 0) { + child = spawn(input, argv, envp, 0); + if (child < 0) { + return child; + } + return child; + } + + snprintf(bin_path, sizeof(bin_path), "/usr/bin/%s", input); + + /* See if we can access it */ + if (access(bin_path, F_OK) != 0) { + return -1; + } + + if ((child = spawn(bin_path, argv, envp, 0)) < 0) { + return child; + } + + return child; +} + +/* + * Match a command with a builtin or binary + * + * @input: Command input + * @argc: Argument count + * @argv: Argument vector + */ +static int +command_match(const char *input, int argc, char *argv[]) +{ + int found = 0; + int i; + pid_t child = -1; + + for (i = 0; cmds[i].name != NULL; i++) { + if (strcmp(input, cmds[i].name) == 0) { + builtin_run(&cmds[i], argc, argv); + found = 1; + } + } + + if (found == 0) { + if ((child = cmd_run(input, argc, argv)) < 0) { + puts("Unrecognized command"); + return -1; + } + } + + return child; +} + +static void +script_skip_comment(int fd) { - int fd, found, argc; - char *input, *argv[16]; char c; - if ((fd = open("/dev/console", O_RDWR)) < 0) { - return fd; + while (c != '\n') { + if (read(fd, &c, 1) <= 0) + break; } +} - i = 0; - running = 1; - found = 0; +/* + * Parse a single line typed in from the + * user. + * + * @input: Input line + */ +static int +parse_line(char *input) +{ + int argc; + char *argv[16]; + struct parse_state state = {0}; + pid_t child; - prcons(fd, WELCOME); - while (running) { - prcons(fd, PROMPT); + /* + * If we are using the REPEAT shorthand, + * repeat the last command. We return -EAGAIN + * to indicate we did not parse a normal command + * so the repeat command isn't pushed into the last + * command buffer and we enter a recursive hell. + */ + if (strcmp(input, REPEAT) == 0) { + parse_line(last_command); + return -EAGAIN; + } - input = getstr(fd); - if (input[0] == '\0') { + /* Ensure the aux vector is zeored */ + memset(argv, 0, sizeof(argv)); + + /* + * Grab args from the user, there should be + * at least one. + */ + argc = parse_args(input, argv, sizeof(argv), &state); + if (argc == 0) { + return -EAGAIN; + } + + child = command_match(input, argc, argv); + if (child > 0 && !state.bg) { + waitpid(child, NULL, 0); + } + + return 0; +} + +static int +open_script(const char *pathname) +{ + int fd, argc, buf_i = 0; + char c, *input; + char buf[256]; + + fd = open(pathname, O_RDONLY); + if (fd < 0) { + printf("osh: failed to open %s\n", pathname); + return fd; + } + + while (read(fd, &c, 1) > 0) { + /* Skip comments */ + if (c == COMMENT) { + script_skip_comment(fd); continue; } - argc = parse_args(input, argv, sizeof(argv)); - if (argc == 0) { + /* Skip blank newlines */ + if (c == '\n' && buf_i == 0) { continue; } - for (i = 0; cmds[i].name != NULL; i++) { - if (strcmp(input, cmds[i].name) == 0) { - cmds[i].func(fd, argc, argv); - found = 1; - break; - } + if (buf_i >= sizeof(buf) - 1) { + buf_i = 0; } - if (found == 0) { - prcons(fd, "Unrecognized command\n"); + if (c == '\n') { + buf[buf_i] = '\0'; + parse_line(buf); + buf_i = 0; + continue; + } + buf[buf_i++] = c; + } + + return 0; +} + +static void +dump_file(const char *pathname) +{ + FILE *file; + char buf[64]; + int fd; + + file = fopen(pathname, "r"); + if (file == NULL) { + return; + } + + while (fgets(buf, sizeof(buf), file) != NULL) { + printf("%s", buf); + } + + fclose(file); +} + +int +main(int argc, char **argv) +{ + int found, prog_argc; + int stdout_fd; + char hostname[128] = "osmora"; + uint8_t buf_i; + char *p; + char c; + pid_t child; + + if (argc > 1) { + return open_script(argv[1]); + } + + running = 1; + bell_fd = open("/dev/beep", O_WRONLY); + dump_file("/etc/motd"); + gethostname(hostname, sizeof(hostname)); + + while (running) { + printf(PROMPT, getlogin(), hostname); + + buf_i = getstr(); + if (buf[0] == '\0') { + continue; + } + + buf[buf_i] = '\0'; + if (parse_line(buf) < 0) { + continue; } - found = 0; + memcpy(last_command, buf, buf_i + 1); buf[0] = '\0'; } return 0; |