summaryrefslogtreecommitdiff
path: root/lume-legacy/src/user
diff options
context:
space:
mode:
Diffstat (limited to 'lume-legacy/src/user')
-rw-r--r--lume-legacy/src/user/cat.cc52
-rw-r--r--lume-legacy/src/user/cp.cc52
-rw-r--r--lume-legacy/src/user/echo.cc26
-rw-r--r--lume-legacy/src/user/entry.S12
-rw-r--r--lume-legacy/src/user/grep.cc72
-rw-r--r--lume-legacy/src/user/init.cc64
-rw-r--r--lume-legacy/src/user/kill.cc27
-rw-r--r--lume-legacy/src/user/ln.cc22
-rw-r--r--lume-legacy/src/user/ls.cc116
-rw-r--r--lume-legacy/src/user/mkdir.cc25
-rw-r--r--lume-legacy/src/user/mv.cc30
-rw-r--r--lume-legacy/src/user/rm.cc28
-rw-r--r--lume-legacy/src/user/sh.cc423
-rw-r--r--lume-legacy/src/user/touch.cc31
-rw-r--r--lume-legacy/src/user/user.ld36
-rw-r--r--lume-legacy/src/user/usys.S38
-rw-r--r--lume-legacy/src/user/wc.cc60
17 files changed, 1114 insertions, 0 deletions
diff --git a/lume-legacy/src/user/cat.cc b/lume-legacy/src/user/cat.cc
new file mode 100644
index 0000000..b518d77
--- /dev/null
+++ b/lume-legacy/src/user/cat.cc
@@ -0,0 +1,52 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2026 Richard QIn
+ */
+#include "ulib/stdio.h"
+#include "ulib/stdlib.h"
+#include "ulib/string.h"
+#include "user/user.h"
+#include "common/fcntl.h"
+
+char buf[512];
+
+void cat(int fd)
+{
+ int n;
+ while ((n = read(fd, buf, sizeof(buf))) > 0)
+ {
+ if (write(1, buf, n) != n)
+ {
+ printf("cat: write error\n");
+ exit(1);
+ }
+ }
+ if (n < 0)
+ {
+ printf("cat: read error\n");
+ exit(1);
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ int fd, i;
+
+ if (argc <= 1)
+ {
+ cat(0);
+ exit(0);
+ }
+
+ for (i = 1; i < argc; i++)
+ {
+ if ((fd = open(argv[i], O_RDONLY)) < 0)
+ {
+ printf("cat: cannot open %s\n", argv[i]);
+ exit(1);
+ }
+ cat(fd);
+ close(fd);
+ }
+ exit(0);
+} \ No newline at end of file
diff --git a/lume-legacy/src/user/cp.cc b/lume-legacy/src/user/cp.cc
new file mode 100644
index 0000000..ca35e82
--- /dev/null
+++ b/lume-legacy/src/user/cp.cc
@@ -0,0 +1,52 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2026 Richard QIn
+ */
+#include "ulib/stdio.h"
+#include "ulib/stdlib.h"
+#include "user/user.h"
+#include "common/fcntl.h"
+
+char buf[512];
+
+int main(int argc, char *argv[])
+{
+ int fd_src, fd_dst, n;
+
+ if (argc != 3)
+ {
+ printf("Usage: cp src dst\n");
+ exit(1);
+ }
+
+ if ((fd_src = open(argv[1], O_RDONLY)) < 0)
+ {
+ printf("cp: cannot open source %s\n", argv[1]);
+ exit(1);
+ }
+
+ if ((fd_dst = open(argv[2], O_WRONLY | O_CREATE | O_TRUNC)) < 0)
+ {
+ printf("cp: cannot create destination %s\n", argv[2]);
+ close(fd_src);
+ exit(1);
+ }
+
+ while ((n = read(fd_src, buf, sizeof(buf))) > 0)
+ {
+ if (write(fd_dst, buf, n) != n)
+ {
+ printf("cp: write error\n");
+ exit(1);
+ }
+ }
+
+ if (n < 0)
+ {
+ printf("cp: read error\n");
+ }
+
+ close(fd_src);
+ close(fd_dst);
+ exit(0);
+} \ No newline at end of file
diff --git a/lume-legacy/src/user/echo.cc b/lume-legacy/src/user/echo.cc
new file mode 100644
index 0000000..cc9dc2a
--- /dev/null
+++ b/lume-legacy/src/user/echo.cc
@@ -0,0 +1,26 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2026 Richard QIn
+ */
+#include "user/user.h"
+#include "ulib/stdio.h"
+#include "ulib/string.h"
+
+int main(int argc, char* argv[])
+{
+ int i;
+
+ for (i = 1; i < argc; i++)
+ {
+ write(1, argv[i], strlen(argv[i]));
+ if (i + 1 < argc)
+ {
+ write(1, " ", 1);
+ }
+ else
+ {
+ write(1, "\n", 1);
+ }
+ }
+ exit(0);
+} \ No newline at end of file
diff --git a/lume-legacy/src/user/entry.S b/lume-legacy/src/user/entry.S
new file mode 100644
index 0000000..41e0964
--- /dev/null
+++ b/lume-legacy/src/user/entry.S
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright (C) 2026 Richard Qin
+
+.section .text
+.global _start
+
+_start:
+ call main
+
+ call exit
+
+1: j 1b \ No newline at end of file
diff --git a/lume-legacy/src/user/grep.cc b/lume-legacy/src/user/grep.cc
new file mode 100644
index 0000000..1e1c42d
--- /dev/null
+++ b/lume-legacy/src/user/grep.cc
@@ -0,0 +1,72 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2026 Richard QIn
+ */
+#include "ulib/stdio.h"
+#include "ulib/stdlib.h"
+#include "ulib/string.h"
+#include "user/user.h"
+#include "common/fcntl.h"
+
+char buf[1024];
+
+void grep(char *pattern, int fd)
+{
+ int n, m;
+ char *p, *q;
+
+ m = 0;
+ while ((n = read(fd, buf + m, sizeof(buf) - m - 1)) > 0)
+ {
+ m += n;
+ buf[m] = '\0';
+ p = buf;
+ while ((q = strchr(p, '\n')) != 0)
+ {
+ *q = 0;
+ if (strstr(p, pattern) != 0)
+ {
+ printf("%s\n", p);
+ }
+ p = q + 1;
+ }
+ if (p == buf)
+ m = 0;
+ if (m > 0)
+ {
+ m -= p - buf;
+ memmove(buf, p, m);
+ }
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ int fd, i;
+ char *pattern;
+
+ if (argc < 2)
+ {
+ printf("Usage: grep pattern [file...]\n");
+ exit(1);
+ }
+ pattern = argv[1];
+
+ if (argc == 2)
+ {
+ grep(pattern, 0); // stdin
+ exit(0);
+ }
+
+ for (i = 2; i < argc; i++)
+ {
+ if ((fd = open(argv[i], O_RDONLY)) < 0)
+ {
+ printf("grep: cannot open %s\n", argv[i]);
+ exit(1);
+ }
+ grep(pattern, fd);
+ close(fd);
+ }
+ exit(0);
+} \ No newline at end of file
diff --git a/lume-legacy/src/user/init.cc b/lume-legacy/src/user/init.cc
new file mode 100644
index 0000000..376e697
--- /dev/null
+++ b/lume-legacy/src/user/init.cc
@@ -0,0 +1,64 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2026 Richard QIn
+ */
+#include "ulib/stdio.h"
+#include "ulib/stdlib.h"
+#include "ulib/string.h"
+#include "user/user.h"
+#include "drivers/uart_color.h"
+
+int main()
+{
+ char *argv[] = {(char *)"sh", 0};
+
+ printf(ANSI_BLUE "\n[Init] Starting Lume OS Shell..." ANSI_RESET);
+ printf("\n");
+
+ while (1)
+ {
+ // printf("fork start\n");
+ int pid = fork();
+ // printf("fork suceess, pid=%i\n", pid);
+
+ if (pid < 0)
+ {
+ printf("[Init] fork failed!\n");
+ exit(1);
+ }
+
+ if (pid == 0)
+ {
+
+ char sh_path[] = "/sh";
+ exec(sh_path, argv);
+
+ printf("[Init] exec sh failed\n");
+ return 0;
+ exit(1);
+ }
+ else
+ {
+ // break;
+ int status;
+ int wpid;
+ while (1)
+ {
+ wpid = wait((uint64)&status);
+
+ if (wpid == pid)
+ {
+ printf("Shell Exit");
+ break;
+ }
+ if (wpid < 0)
+ {
+ printf("[Init] wait error or no child\n");
+
+ break;
+ }
+ }
+ }
+ }
+ return 0;
+} \ No newline at end of file
diff --git a/lume-legacy/src/user/kill.cc b/lume-legacy/src/user/kill.cc
new file mode 100644
index 0000000..d38cdd6
--- /dev/null
+++ b/lume-legacy/src/user/kill.cc
@@ -0,0 +1,27 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2026 Richard QIn
+ */
+#include "ulib/stdio.h"
+#include "ulib/stdlib.h"
+#include "user/user.h"
+
+int main(int argc, char *argv[])
+{
+ int i;
+
+ if (argc < 2)
+ {
+ printf("Usage: kill pid...\n");
+ exit(1);
+ }
+ for (i = 1; i < argc; i++)
+ {
+ int pid = atoi(argv[i]);
+ if (kill(pid) < 0)
+ {
+ printf("kill: failed to kill %d\n", pid);
+ }
+ }
+ exit(0);
+} \ No newline at end of file
diff --git a/lume-legacy/src/user/ln.cc b/lume-legacy/src/user/ln.cc
new file mode 100644
index 0000000..a21ddf9
--- /dev/null
+++ b/lume-legacy/src/user/ln.cc
@@ -0,0 +1,22 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2026 Richard QIn
+ */
+#include "ulib/stdio.h"
+#include "ulib/stdlib.h"
+#include "user/user.h"
+
+int main(int argc, char *argv[])
+{
+ if (argc != 3)
+ {
+ printf("Usage: ln old new\n");
+ exit(1);
+ }
+ if (link(argv[1], argv[2]) < 0)
+ {
+ printf("link %s %s failed\n", argv[1], argv[2]);
+ exit(1);
+ }
+ exit(0);
+} \ No newline at end of file
diff --git a/lume-legacy/src/user/ls.cc b/lume-legacy/src/user/ls.cc
new file mode 100644
index 0000000..c44c799
--- /dev/null
+++ b/lume-legacy/src/user/ls.cc
@@ -0,0 +1,116 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2026 Richard QIn
+ */
+#include "ulib/stdio.h"
+#include "ulib/stdlib.h"
+#include "ulib/string.h"
+#include "user/user.h"
+#include "common/fcntl.h"
+
+
+struct FAT32_DirEnt
+{
+ char name[11];
+ uint8 attr;
+ uint8 lcase;
+ uint8 ctime_cs;
+ uint16 ctime;
+ uint16 cdate;
+ uint16 adate;
+ uint16 fstClusHI;
+ uint16 wtime;
+ uint16 wdate;
+ uint16 fstClusLO;
+ uint32 fileSize;
+} __attribute__((packed));
+
+#define ATTR_DIRECTORY 0x10
+#define ATTR_LONG_NAME 0x0F
+
+void fmt_name(char *dst, const char *src)
+{
+ int i;
+ char *d = dst;
+
+ for (i = 0; i < 8; i++)
+ {
+ if (src[i] == ' ')
+ break;
+ *d++ = src[i];
+ }
+
+ if (src[8] != ' ')
+ {
+ *d++ = '.';
+ for (i = 8; i < 11; i++)
+ {
+ if (src[i] == ' ')
+ break;
+ *d++ = src[i];
+ }
+ }
+ *d = 0;
+
+ for (d = dst; *d; d++)
+ {
+ if (*d >= 'A' && *d <= 'Z')
+ *d += 32;
+ }
+}
+
+void ls(const char *path)
+{
+ int fd;
+ struct FAT32_DirEnt de;
+ char name_buf[16];
+
+ if ((fd = open(path, O_RDONLY)) < 0)
+ {
+ printf("ls: cannot open %s\n", path);
+ return;
+ }
+
+ while (read(fd, &de, sizeof(de)) == sizeof(de))
+ {
+ if (de.name[0] == 0)
+ {
+ break;
+ }
+
+ if ((unsigned char)de.name[0] == 0xE5)
+ {
+ continue;
+ }
+
+ if ((de.attr & ATTR_LONG_NAME) == ATTR_LONG_NAME)
+ {
+ continue;
+ }
+
+ fmt_name(name_buf, de.name);
+
+ printf("%s %d\t%s\n",
+ (de.attr & ATTR_DIRECTORY) ? "<DIR>" : " ",
+ de.fileSize,
+ name_buf);
+ }
+
+ close(fd);
+}
+
+int main(int argc, char *argv[])
+{
+ if (argc < 2)
+ {
+ ls(".");
+ }
+ else
+ {
+ for (int i = 1; i < argc; i++)
+ {
+ ls(argv[i]);
+ }
+ }
+ exit(0);
+} \ No newline at end of file
diff --git a/lume-legacy/src/user/mkdir.cc b/lume-legacy/src/user/mkdir.cc
new file mode 100644
index 0000000..6da8b49
--- /dev/null
+++ b/lume-legacy/src/user/mkdir.cc
@@ -0,0 +1,25 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2026 Richard QIn
+ */
+#include "ulib/stdio.h"
+#include "user/user.h"
+
+int main(int argc, char* argv[])
+{
+ if (argc < 2)
+ {
+ printf("Usage: mkdir <dirname> ...\n");
+ exit(1);
+ }
+
+ for (int i = 1; i < argc; i++)
+ {
+ if (mkdir(argv[i]) < 0)
+ {
+ printf("mkdir: cannot create directory '%s'\n", argv[i]);
+ }
+ }
+
+ exit(0);
+} \ No newline at end of file
diff --git a/lume-legacy/src/user/mv.cc b/lume-legacy/src/user/mv.cc
new file mode 100644
index 0000000..a196145
--- /dev/null
+++ b/lume-legacy/src/user/mv.cc
@@ -0,0 +1,30 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2026 Richard QIn
+ */
+#include "ulib/stdio.h"
+#include "ulib/stdlib.h"
+#include "user/user.h"
+
+int main(int argc, char *argv[])
+{
+ if (argc != 3)
+ {
+ printf("Usage: mv old new\n");
+ exit(1);
+ }
+
+ if (link(argv[1], argv[2]) < 0)
+ {
+ printf("mv: failed to link %s to %s\n", argv[1], argv[2]);
+ exit(1);
+ }
+
+ if (unlink(argv[1]) < 0)
+ {
+ printf("mv: failed to unlink %s\n", argv[1]);
+ exit(1);
+ }
+
+ exit(0);
+} \ No newline at end of file
diff --git a/lume-legacy/src/user/rm.cc b/lume-legacy/src/user/rm.cc
new file mode 100644
index 0000000..4777ade
--- /dev/null
+++ b/lume-legacy/src/user/rm.cc
@@ -0,0 +1,28 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2026 Richard QIn
+ */
+#include "ulib/stdio.h"
+#include "ulib/stdlib.h"
+#include "user/user.h"
+
+int main(int argc, char *argv[])
+{
+ int i;
+
+ if (argc < 2)
+ {
+ printf("Usage: rm files...\n");
+ exit(1);
+ }
+
+ for (i = 1; i < argc; i++)
+ {
+ if (unlink(argv[i]) < 0)
+ {
+ printf("rm: %s failed to delete\n", argv[i]);
+ exit(1);
+ }
+ }
+ exit(0);
+} \ No newline at end of file
diff --git a/lume-legacy/src/user/sh.cc b/lume-legacy/src/user/sh.cc
new file mode 100644
index 0000000..47aad9e
--- /dev/null
+++ b/lume-legacy/src/user/sh.cc
@@ -0,0 +1,423 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2026 Richard QIn
+ */
+#include "ulib/stdio.h"
+#include "ulib/stdlib.h"
+#include "ulib/string.h"
+#include "user/user.h"
+#include "common/fcntl.h"
+#include "drivers/uart_color.h"
+
+constexpr int MAX_ARGS = 10;
+const char *WHITESPACE = " \t\r\n\v";
+const char *SYMBOLS = "<|>&;()";
+
+void panic(const char *s)
+{
+ printf("shell panic: %s\n", s);
+ exit(1);
+}
+
+int fork_or_panic()
+{
+ int pid = fork();
+ if (pid == -1)
+ panic("fork");
+ return pid;
+}
+
+struct Command
+{
+ virtual ~Command() = default;
+
+ virtual void run() = 0;
+};
+
+struct ExecCommand : public Command
+{
+ char *argv[MAX_ARGS];
+
+ ExecCommand()
+ {
+ memset(argv, 0, sizeof(argv));
+ }
+
+ void run() override
+ {
+ if (argv[0] == nullptr)
+ exit(1);
+
+ exec(argv[0], argv);
+
+ printf("exec %s failed\n", argv[0]);
+ exit(1);
+ }
+};
+
+struct RedirCommand : public Command
+{
+ Command *sub_cmd;
+ char *file;
+ char *efile; // file name end pointer (used by parser)
+ int mode;
+ int fd;
+
+ RedirCommand(Command *c, char *f, char *ef, int m, int fd_in)
+ : sub_cmd(c), file(f), efile(ef), mode(m), fd(fd_in) {}
+
+ ~RedirCommand() { delete sub_cmd; }
+
+ void run() override
+ {
+ close(fd);
+
+ int newfd = open(file, mode);
+
+ if (newfd < 0)
+ {
+ printf("open %s failed\n", file);
+ exit(1);
+ }
+
+ if (newfd != fd)
+ {
+
+ if (dup(newfd) != fd)
+ {
+ printf("Redirection failed: could not move fd %d to %d\n", newfd, fd);
+ exit(1);
+ }
+ close(newfd);
+ }
+
+ sub_cmd->run();
+ }
+};
+
+struct PipeCommand : public Command
+{
+ Command *left;
+ Command *right;
+
+ PipeCommand(Command *l, Command *r) : left(l), right(r) {}
+ ~PipeCommand()
+ {
+ delete left;
+ delete right;
+ }
+
+ void run() override
+ {
+ int p[2];
+ if (pipe(p) < 0)
+ panic("pipe");
+
+ if (fork_or_panic() == 0)
+ {
+ // Left child: write to pipe
+ close(1); // close stdout
+ dup(p[1]); // dup write-end to stdout
+ close(p[0]);
+ close(p[1]);
+ left->run();
+ }
+
+ if (fork_or_panic() == 0)
+ {
+ // Right child: read from pipe
+ close(0); // close stdin
+ dup(p[0]); // dup read-end to stdin
+ close(p[0]);
+ close(p[1]);
+ right->run();
+ }
+
+ close(p[0]);
+ close(p[1]);
+ wait(0);
+ wait(0);
+ exit(0);
+ }
+};
+
+struct ListCommand : public Command
+{
+ Command *left;
+ Command *right;
+
+ ListCommand(Command *l, Command *r) : left(l), right(r) {}
+ ~ListCommand()
+ {
+ delete left;
+ delete right;
+ }
+
+ void run() override
+ {
+ if (fork_or_panic() == 0)
+ {
+ left->run();
+ }
+ wait(0);
+ right->run();
+ }
+};
+
+struct BackCommand : public Command
+{
+ Command *sub_cmd;
+
+ BackCommand(Command *c) : sub_cmd(c) {}
+ ~BackCommand() { delete sub_cmd; }
+
+ void run() override
+ {
+ if (fork_or_panic() == 0)
+ {
+ sub_cmd->run();
+ }
+ // Parent doesn't wait
+ exit(0);
+ }
+};
+
+class Parser
+{
+public:
+ static Command *parse(char *s)
+ {
+ char *es = s + strlen(s);
+ Command *cmd = parse_line(&s, es);
+ peek(&s, es, "");
+ if (s != es)
+ {
+ printf("leftovers: %s\n", s);
+ // panic("syntax"); // strict syntax check
+ }
+ terminate_strings(cmd);
+ return cmd;
+ }
+
+private:
+ // Helper: NUL-terminate strings found during parsing
+ static void terminate_strings(Command *cmd)
+ {
+ if (!cmd)
+ return;
+ }
+
+ static char get_token(char **ps, char *es, char **q, char **eq)
+ {
+ char *s;
+ int ret;
+
+ s = *ps;
+ while (s < es && strchr(WHITESPACE, *s))
+ s++;
+ if (q)
+ *q = s;
+ ret = *s;
+ switch (*s)
+ {
+ case 0:
+ break;
+ case '|':
+ case '(':
+ case ')':
+ case ';':
+ case '&':
+ case '<':
+ s++;
+ break;
+ case '>':
+ s++;
+ if (*s == '>')
+ {
+ ret = '+';
+ s++;
+ }
+ break;
+ default:
+ ret = 'a';
+ while (s < es && !strchr(WHITESPACE, *s) && !strchr(SYMBOLS, *s))
+ s++;
+ break;
+ }
+ if (eq)
+ *eq = s;
+
+ while (s < es && strchr(WHITESPACE, *s))
+ s++;
+ *ps = s;
+ return ret;
+ }
+
+ static int peek(char **ps, char *es, const char *toks)
+ {
+ char *s = *ps;
+ while (s < es && strchr(WHITESPACE, *s))
+ s++;
+ *ps = s;
+ return *s && strchr(toks, *s);
+ }
+
+ // Recursive Descent Parsers
+
+ static Command *parse_line(char **ps, char *es)
+ {
+ Command *cmd = parse_pipe(ps, es);
+ while (peek(ps, es, "&"))
+ {
+ get_token(ps, es, 0, 0);
+ cmd = new BackCommand(cmd);
+ }
+ if (peek(ps, es, ";"))
+ {
+ get_token(ps, es, 0, 0);
+ cmd = new ListCommand(cmd, parse_line(ps, es));
+ }
+ return cmd;
+ }
+
+ static Command *parse_pipe(char **ps, char *es)
+ {
+ Command *cmd = parse_exec(ps, es);
+ if (peek(ps, es, "|"))
+ {
+ get_token(ps, es, 0, 0);
+ cmd = new PipeCommand(cmd, parse_pipe(ps, es));
+ }
+ return cmd;
+ }
+
+ static Command *parse_exec(char **ps, char *es)
+ {
+ char *q, *eq;
+ int tok, argc;
+ ExecCommand *cmd = new ExecCommand();
+ Command *ret = cmd;
+
+ argc = 0;
+ ret = parse_redirs(ret, ps, es);
+ while (!peek(ps, es, "|)&;"))
+ {
+ if ((tok = get_token(ps, es, &q, &eq)) == 0)
+ break;
+ if (tok != 'a')
+ panic("syntax");
+
+ cmd->argv[argc] = q;
+ *eq = 0; // Temp
+
+ argc++;
+ if (argc >= MAX_ARGS)
+ panic("too many args");
+ ret = parse_redirs(ret, ps, es);
+ }
+ cmd->argv[argc] = 0;
+ return ret;
+ }
+
+ static Command *parse_redirs(Command *cmd, char **ps, char *es)
+ {
+ int tok;
+ char *q, *eq;
+
+ while (peek(ps, es, "<>"))
+ {
+ tok = get_token(ps, es, 0, 0);
+ if (get_token(ps, es, &q, &eq) != 'a')
+ panic("missing file for redirection");
+
+ *eq = 0; // Null terminate filename immediately
+
+ switch (tok)
+ {
+ case '<':
+ cmd = new RedirCommand(cmd, q, eq, O_RDONLY, 0);
+ break;
+ case '>':
+ cmd = new RedirCommand(cmd, q, eq, O_WRONLY | O_CREATE | O_TRUNC, 1);
+ break;
+ case '+': // >>
+ cmd = new RedirCommand(cmd, q, eq, O_WRONLY | O_CREATE, 1); // Append not fully supported in simple OS yet?
+ break;
+ }
+ }
+ return cmd;
+ }
+};
+
+int getcmd(char *buf, int nbuf)
+{
+ write(1, "$ ", 2);
+ memset(buf, 0, nbuf);
+
+ int n = read(0, buf, nbuf - 1);
+
+ if (n < 0)
+ return -1;
+
+ buf[n] = 0;
+ return 0;
+}
+
+int main()
+{
+ static char buf[100];
+
+
+ printf(ANSI_GREEN "\n[Lume Shell] Welcome! \n" ANSI_RESET);
+ printf(ANSI_YELLOW "[Lume Shell] Start your journey of exploration!" ANSI_RESET);
+ printf("\n");
+
+ while (getcmd(buf, sizeof(buf)) >= 0)
+ {
+ int len = strlen(buf);
+ if (len > 0 && buf[len - 1] == '\n')
+ {
+ buf[len - 1] = 0;
+ }
+
+ if (buf[0] == 'c' && buf[1] == 'd' && (buf[2] == ' ' || buf[2] == '\n' || buf[2] == 0))
+ {
+ if (buf[strlen(buf) - 1] == '\n')
+ buf[strlen(buf) - 1] = 0;
+
+ char *path = buf + 3;
+ if (buf[2] == '\n' || buf[2] == 0)
+ {
+ printf("cd: argument missing\n");
+ continue;
+ }
+
+ if (chdir(path) < 0)
+ {
+ printf("cannot cd %s\n", path);
+ }
+ continue;
+ }
+ if (strcmp(buf, "shutdown") == 0 || strcmp(buf, "poweroff") == 0)
+ {
+ printf(ANSI_BLUE "System shutting down..." ANSI_RESET);
+ printf("\n");
+ shutdown();
+
+ continue;
+ }
+
+ if (fork_or_panic() == 0)
+ {
+ Command *cmd = Parser::parse(buf);
+ if (cmd)
+ {
+ cmd->run();
+ delete cmd;
+ }
+ exit(0);
+ }
+ wait(0);
+ }
+ return 0;
+} \ No newline at end of file
diff --git a/lume-legacy/src/user/touch.cc b/lume-legacy/src/user/touch.cc
new file mode 100644
index 0000000..2f9f9f6
--- /dev/null
+++ b/lume-legacy/src/user/touch.cc
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2026 Richard QIn
+ */
+#include "ulib/stdio.h"
+#include "ulib/stdlib.h"
+#include "user/user.h"
+#include "common/fcntl.h"
+
+int main(int argc, char *argv[])
+{
+ int i, fd;
+
+ if (argc < 2)
+ {
+ printf("Usage: touch files...\n");
+ exit(1);
+ }
+
+ for (i = 1; i < argc; i++)
+ {
+ fd = open(argv[i], O_CREATE | O_RDWR);
+ if (fd < 0)
+ {
+ printf("touch: cannot touch %s\n", argv[i]);
+ exit(1);
+ }
+ close(fd);
+ }
+ exit(0);
+} \ No newline at end of file
diff --git a/lume-legacy/src/user/user.ld b/lume-legacy/src/user/user.ld
new file mode 100644
index 0000000..f0a4b27
--- /dev/null
+++ b/lume-legacy/src/user/user.ld
@@ -0,0 +1,36 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright (C) 2026 Richard Qin
+
+OUTPUT_ARCH( "riscv" )
+ENTRY( _start )
+
+SECTIONS
+{
+ . = 0x0;
+
+ .text : {
+ *(.text.entry)
+ *(.text .text.*)
+ }
+
+ .rodata : {
+ . = ALIGN(16);
+ *(.srodata .srodata.*)
+ . = ALIGN(16);
+ *(.rodata .rodata.*)
+ }
+
+ .data : {
+ . = ALIGN(16);
+ *(.sdata .sdata.*)
+ . = ALIGN(16);
+ *(.data .data.*)
+ }
+
+ .bss : {
+ . = ALIGN(16);
+ *(.sbss .sbss.*)
+ . = ALIGN(16);
+ *(.bss .bss.*)
+ }
+} \ No newline at end of file
diff --git a/lume-legacy/src/user/usys.S b/lume-legacy/src/user/usys.S
new file mode 100644
index 0000000..1e373d3
--- /dev/null
+++ b/lume-legacy/src/user/usys.S
@@ -0,0 +1,38 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright (C) 2026 Richard Qin
+
+#include "kernel/syscall.h"
+
+#define SYSCALL_NAME(name, sysname) \
+ .global name; \
+ name: \
+ li a7, SYS_ ## sysname; \
+ ecall; \
+ ret
+
+SYSCALL_NAME(sys_openat, openat)
+SYSCALL_NAME(sys_mknodat, mknodat)
+SYSCALL_NAME(sys_mkdirat, mkdirat)
+SYSCALL_NAME(sys_unlinkat, unlinkat)
+SYSCALL_NAME(sys_linkat, linkat)
+SYSCALL_NAME(sys_chdir, chdir)
+SYSCALL_NAME(sys_dup, dup)
+SYSCALL_NAME(sys_fstat, fstat)
+SYSCALL_NAME(sys_read, read)
+SYSCALL_NAME(sys_write, write)
+SYSCALL_NAME(sys_close, close)
+SYSCALL_NAME(sys_pipe2, pipe2)
+SYSCALL_NAME(sys_clone, clone)
+SYSCALL_NAME(sys_execve, execve)
+SYSCALL_NAME(sys_wait4, wait4)
+SYSCALL_NAME(sys_exit, exit)
+SYSCALL_NAME(sys_getpid, getpid)
+SYSCALL_NAME(sys_brk, brk)
+SYSCALL_NAME(sys_kill, kill)
+SYSCALL_NAME(sys_nanosleep, nanosleep)
+SYSCALL_NAME(sys_reboot, reboot)
+SYSCALL_NAME(sys_lseek, lseek)
+
+# === Custom / Debug Syscalls ===
+SYSCALL_NAME(sys_putc, putc)
+SYSCALL_NAME(sys_disk_test, disk_test) \ No newline at end of file
diff --git a/lume-legacy/src/user/wc.cc b/lume-legacy/src/user/wc.cc
new file mode 100644
index 0000000..fbe9735
--- /dev/null
+++ b/lume-legacy/src/user/wc.cc
@@ -0,0 +1,60 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2026 Richard QIn
+ */
+#include "ulib/stdio.h"
+#include "ulib/stdlib.h"
+#include "ulib/string.h"
+#include "user/user.h"
+#include "common/fcntl.h"
+
+char buf[512];
+
+void wc(int fd, char *name)
+{
+ int i, n;
+ int l, w, c, inword;
+
+ l = w = c = 0;
+ inword = 0;
+ while ((n = read(fd, buf, sizeof(buf))) > 0)
+ {
+ for (i = 0; i < n; i++)
+ {
+ c++;
+ if (buf[i] == '\n')
+ l++;
+ if (strchr(" \r\t\n\v", buf[i]))
+ inword = 0;
+ else if (!inword)
+ {
+ w++;
+ inword = 1;
+ }
+ }
+ }
+ printf("%d %d %d %s\n", l, w, c, name);
+}
+
+int main(int argc, char *argv[])
+{
+ int fd, i;
+
+ if (argc <= 1)
+ {
+ wc(0, "");
+ exit(0);
+ }
+
+ for (i = 1; i < argc; i++)
+ {
+ if ((fd = open(argv[i], O_RDONLY)) < 0)
+ {
+ printf("wc: cannot open %s\n", argv[i]);
+ exit(1);
+ }
+ wc(fd, argv[i]);
+ close(fd);
+ }
+ exit(0);
+} \ No newline at end of file