summaryrefslogtreecommitdiff
path: root/usr.bin/osh/osh.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr.bin/osh/osh.c')
-rw-r--r--usr.bin/osh/osh.c416
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;