summaryrefslogtreecommitdiff
path: root/usr.bin/osh
diff options
context:
space:
mode:
Diffstat (limited to 'usr.bin/osh')
-rw-r--r--usr.bin/osh/Makefile6
-rw-r--r--usr.bin/osh/osh.c414
2 files changed, 420 insertions, 0 deletions
diff --git a/usr.bin/osh/Makefile b/usr.bin/osh/Makefile
new file mode 100644
index 0000000..1505412
--- /dev/null
+++ b/usr.bin/osh/Makefile
@@ -0,0 +1,6 @@
+include user.mk
+
+CFILES = $(shell find . -name "*.c")
+
+$(ROOT)/base/usr/bin/osh:
+ gcc $(CFILES) -o $@ $(INTERNAL_CFLAGS)
diff --git a/usr.bin/osh/osh.c b/usr.bin/osh/osh.c
new file mode 100644
index 0000000..dd92378
--- /dev/null
+++ b/usr.bin/osh/osh.c
@@ -0,0 +1,414 @@
+/*
+ * Copyright (c) 2023-2025 Ian Marco Moffett and the Osmora Team.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Hyra nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <sys/cdefs.h>
+#include <sys/reboot.h>
+#include <sys/spawn.h>
+#include <fcntl.h>
+#include <stddef.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdio.h>
+
+#define is_ascii(C) ((C) >= 0 && (C) <= 128)
+#define COMMENT '@'
+#define WELCOME \
+ ":::::::::::::::::::::::::::::::::::::::\n" \
+ ":: OSMORA GATEWAY ~ Every key echos ::\n" \
+ ":: ..... Proceed with purpose ..... ::\n" \
+ ":::::::::::::::::::::::::::::::::::::::"
+
+#define HELP \
+ "Default commands:\n" \
+ "help - Display this help message\n" \
+ "echo - Print the arguments to the console\n" \
+ "reboot - Reboot the machine\n" \
+ "shutdown - Power off 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]~ "
+
+static char buf[64];
+static uint8_t buf_i;
+static int running;
+static int bell_fd;
+static bool bs_bell = true; /* Beep on backspace */
+
+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_reboot(int argc, char *argv[]);
+static void cmd_shutdown(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 argc, char *argv[]);
+};
+
+static struct builtin_cmd cmds[] = {
+ {"help",cmd_help},
+ {"exit",cmd_exit},
+ {"reboot",cmd_reboot},
+ {"shutdown", cmd_shutdown},
+ {"bell", cmd_bell},
+ {"clear", cmd_clear},
+ {NULL, NULL}
+};
+
+static void
+cmd_help(int argc, char *argv[])
+{
+ puts(HELP);
+}
+
+static void
+cmd_exit(int argc, char *argv[])
+{
+ running = 0;
+}
+
+static void
+cmd_reboot(int argc, char *argv[])
+{
+ cpu_reboot(REBOOT_RESET);
+}
+
+static void
+cmd_shutdown(int argc, char *argv[])
+{
+ cpu_reboot(REBOOT_POWEROFF | REBOOT_HALT);
+}
+
+static void
+cmd_clear(int argc, char *argv[])
+{
+ fputs("\033[H", 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);
+ }
+}
+
+static int
+parse_args(char *input, char *argv[], int max_args)
+{
+ int argc = 0;
+
+ /* ignore comments */
+ if (*input == '@') {
+ return 0;
+ }
+
+ while (*input != '\0') {
+ /* skip leading spaces */
+ while (*input == ' ') {
+ input++;
+ }
+
+ /* check if empty */
+ if (*input == '\0') {
+ break;
+ }
+
+ /* comment? */
+ if (*input == COMMENT) {
+ break;
+ }
+
+ 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++;
+ }
+
+ /* end */
+ if (*input != '\0') {
+ *input = '\0';
+ input++;
+ }
+ }
+
+ return argc;
+}
+
+static char *
+getstr(void)
+{
+ char c;
+ int input;
+ uint32_t beep_payload;
+
+ buf_i = 0;
+
+ /*
+ * Prepare the beep payload @ 500 Hz
+ * for 20ms
+ */
+ beep_payload = 500;
+ beep_payload |= (30 << 16);
+
+ for (;;) {
+ if ((input = getchar()) < 0) {
+ continue;
+ }
+
+ c = (char)input;
+ if (c == '\t') {
+ continue;
+ }
+
+ /* return on newline */
+ if (c == '\n') {
+ buf[buf_i] = '\0';
+ putchar('\n');
+ return buf;
+ }
+
+ /* handle backspaces and DEL */
+ if (c == '\b' || c == 127) {
+ 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) && buf_i < sizeof(buf) - 1) {
+ /* write to fd and add to buffer */
+ buf[buf_i++] = c;
+ putchar(c);
+ }
+ }
+}
+
+static void
+builtin_run(struct builtin_cmd *cmd, int argc, char *argv[])
+{
+ if (cmd->func != NULL) {
+ cmd->func(argc, argv);
+ return;
+ }
+}
+
+static int
+cmd_run(const char *input, int argc, char *argv[], bool wait)
+{
+ char bin_path[512];
+ char *envp[1] = { NULL };
+ int error, spawn_flags = 0;
+
+ /* Should we wait or daemonize? */
+ if (wait) {
+ spawn_flags |= SPAWN_WAIT;
+ }
+
+ /*
+ * 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) {
+ error = spawn(input, argv, envp, spawn_flags);
+ if (error < 0) {
+ return error;
+ }
+ return 0;
+ }
+
+ 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 ((error = spawn(bin_path, argv, envp, spawn_flags)) < 0) {
+ return error;
+ }
+
+ return 0;
+}
+
+/*
+ * Match a command with a builtin or binary
+ *
+ * @input: Command input
+ * @argc: Argument count
+ * @argv: Argument vector
+ * @wait: If false, program will be daemonized
+ */
+static void
+command_match(const char *input, int argc, char *argv[], bool wait)
+{
+ int found = 0;
+ int i;
+
+ for (i = 0; cmds[i].name != NULL; i++) {
+ if (strcmp(input, cmds[i].name) == 0) {
+ builtin_run(&cmds[i], argc, argv);
+ found = 1;
+ break;
+ }
+ }
+
+ if (found == 0) {
+ if (cmd_run(input, argc, argv, wait) < 0) {
+ puts("Unrecognized command");
+ }
+ }
+}
+
+static void
+script_skip_comment(int fd)
+{
+ char c;
+
+ while (c != '\n') {
+ if (read(fd, &c, 1) <= 0)
+ break;
+ }
+}
+
+static int
+open_script(const char *pathname)
+{
+ int fd, argc, buf_i = 0;
+ char c, *input, *argv[16];
+ 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;
+ }
+
+ /* Skip blank newlines */
+ if (c == '\n' && buf_i == 0) {
+ continue;
+ }
+
+ if (buf_i >= sizeof(buf) - 1) {
+ buf_i = 0;
+ }
+
+ if (c == '\n') {
+ buf[buf_i] = '\0';
+ argc = parse_args(buf, argv, sizeof(argv));
+ command_match(buf, argc, argv, true);
+
+ argv[0] = NULL;
+ argv[1] = NULL;
+ buf_i = 0;
+ continue;
+ }
+ buf[buf_i++] = c;
+ }
+
+ return 0;
+}
+
+int
+main(int argc, char **argv)
+{
+ int found, prog_argc;
+ int stdout_fd;
+ char *input, *prog_argv[16], *p;
+ char c;
+
+ if (argc > 1) {
+ return open_script(argv[1]);
+ }
+
+ buf_i = 0;
+ running = 1;
+ found = 0;
+ bell_fd = open("/dev/beep", O_WRONLY);
+
+ puts(WELCOME);
+ while (running) {
+ memset(prog_argv, 0, sizeof(prog_argv));
+ fputs(PROMPT, stdout);
+
+ input = getstr();
+ if (input[0] == '\0') {
+ continue;
+ }
+
+ prog_argc = parse_args(input, prog_argv, sizeof(prog_argv));
+ if (prog_argc == 0) {
+ continue;
+ }
+
+ command_match(input, prog_argc, prog_argv, true);
+ found = 0;
+ buf[0] = '\0';
+ }
+ return 0;
+}