diff --git a/Makefile b/Makefile index c68ca9f..c9f35ce 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,8 @@ KERNEL_HDRS?=kernel-hdr CC=x86_64-elf-yggdrasil-gcc DIRS=$(O) \ - $(STAGE) + $(STAGE) \ + $(O)/sh HDRS=$(shell find $(S) -type f -name "*.h") STAGE_BIN=$(STAGE)/init \ $(STAGE)/bin/hexd \ @@ -14,7 +15,15 @@ STAGE_BIN=$(STAGE)/init \ $(STAGE)/bin/date \ $(STAGE)/bin/uname \ $(STAGE)/bin/mount \ - $(STAGE)/bin/umount + $(STAGE)/bin/umount \ + $(STAGE)/bin/sh \ + $(STAGE)/bin/rm \ + $(STAGE)/bin/mkdir \ + $(STAGE)/bin/login +sh_OBJS=$(O)/sh/sh.o \ + $(O)/sh/readline.o \ + $(O)/sh/builtin.o \ + $(O)/sh/cmd.o usr_CFLAGS=-msse \ -msse2 \ @@ -49,3 +58,9 @@ $(STAGE)/init: init.c $(STAGE)/bin/%: core/bin/%.c $(CC) -o $@ $(usr_CFLAGS) $(usr_LDFLAGS) $< + +$(STAGE)/bin/sh: $(sh_OBJS) + $(CC) -o $@ $(usr_LDFLAGS) $(sh_OBJS) + +$(O)/sh/%.o: sh/%.c $(shell find sh -name "*.h") + $(CC) -c -o $@ $(usr_CFLAGS) $< diff --git a/core/bin/login.c b/core/bin/login.c new file mode 100644 index 0000000..82c879e --- /dev/null +++ b/core/bin/login.c @@ -0,0 +1,183 @@ +#include +#include +#include +#include + +struct spwd { + char *sp_namp; + char *sp_pwdp; +}; + +static ssize_t getline(char *buf, size_t lim, char pwchr) { + size_t c = 0; + char chr; + + while (1) { + if (c == lim) { + return -1; + } + + if (read(STDIN_FILENO, &chr, 1) != 1) { + return -1; + } + + switch (chr) { + case '\n': + putchar('\n'); + buf[c] = 0; + return c; + case '\b': + if (c) { + buf[--c] = 0; + puts2("\033[D \033[D"); + } + break; + default: + if (chr >= ' ' && chr < 255) { + if (pwchr) { + putchar(pwchr); + } else { + putchar(chr); + } + buf[c++] = chr; + } + break; + } + } +} + +static int getspnam_r(const char *name, struct spwd *sp, char *buf, size_t buf_size) { + int fd = open("/etc/shadow", O_RDONLY, 0); + if (fd < 0) { + return fd; + } + + while (gets_safe(fd, buf, buf_size) > 0) { + char *p0 = strchr(buf, ':'); + if (!p0) { + break; + } + char *p1 = strchr(p0 + 1, ':'); + if (!p1) { + break; + } + + *p0 = 0; + *p1 = 0; + + if (strcmp(buf, name)) { + continue; + } + + close(fd); + sp->sp_namp = buf; + sp->sp_pwdp = p0 + 1; + return 0; + } + + close(fd); + errno = ENOENT; + return -1; +} + +static int loginuid(uid_t uid, gid_t gid, const char *sh) { + int sh_pid = fork(); + + if (sh_pid < 0) { + return -1; + } + + if (sh_pid == 0) { + if (setuid(uid) || setgid(gid)) { + perror("login"); + exit(-1); + } + + const char *argp[] = { sh, NULL }; + exit(execve(sh, argp, NULL)); + } else { + int st; + waitpid(sh_pid, &st); + return 0; + } +} + +static int loginas(const char *name) { + char pwbuf[512]; + struct passwd pwd; + struct passwd *res; + const char *shell; + + if (getpwnam_r(name, &pwd, pwbuf, sizeof(pwbuf), &res) != 0) { + perror("getpwnam_r()"); + return -1; + } + + shell = res->pw_shell; + + if (access(shell, X_OK) != 0) { + perror(shell); + printf("Using /bin/sh instead\n"); + shell = "/bin/sh"; + } + + return loginuid(res->pw_uid, res->pw_gid, shell); +} + +int main(int argc, char **argv) { + // Arguments are ignored + if (getuid() != 0) { + printf("login must be run as root\n"); + return -1; + } + + int attempt = 0; + char line_buf[64]; + char spnam_buf[128]; + struct spwd sp; + + while (access("/etc/shadow", R_OK) != 0) { + perror("/etc/shadow"); + // Login as passwordless root? + if (loginuid(0, 0, "/bin/sh") != 0) { + return -1; + } + // Maybe after this session /etc/shadow becomes available + } + + while (1) { + if (attempt == 3) { + puts2("\033[2J\033[1;1f"); + attempt = 0; + } + + printf("login: "); + if (getline(line_buf, sizeof(line_buf), 0) < 0) { + break; + } + + if (getspnam_r(line_buf, &sp, spnam_buf, sizeof(spnam_buf)) != 0) { + perror("getspnam_r()"); + ++attempt; + continue; + } + + printf("password: "); + if (getline(line_buf, sizeof(line_buf), '*') < 0) { + ++attempt; + continue; + } + + // No hashing yet, so just compare passwords + if (!strcmp(line_buf, sp.sp_pwdp)) { + loginas(sp.sp_namp); + // After returning from shell, go here + attempt = 3; + continue; + } else { + ++attempt; + } + } + + return -1; +} diff --git a/core/bin/mkdir.c b/core/bin/mkdir.c new file mode 100644 index 0000000..138fe2a --- /dev/null +++ b/core/bin/mkdir.c @@ -0,0 +1,16 @@ +#include + +int main(int argc, char **argv) { + if (argc <= 1) { + printf("usage: mkdir ...\n"); + return -1; + } + + int res = 0; + + for (int i = 1; i < argc; ++i) { + res += mkdir(argv[i], 0755); + } + + return res; +} diff --git a/core/bin/rm.c b/core/bin/rm.c new file mode 100644 index 0000000..df26d20 --- /dev/null +++ b/core/bin/rm.c @@ -0,0 +1,88 @@ +#include +#include +#include + +#define RM_RECURSIVE (1 << 0) + +static int rm_node(const char *path, int flags) { + struct stat st; + + if (stat(path, &st) != 0) { + perror(path); + return -1; + } + + if ((st.st_mode & S_IFMT) == S_IFDIR) { + if (!(flags & RM_RECURSIVE)) { + printf("%s: Is a directory\n", path); + return -1; + } + + DIR *dir = opendir(path); + if (!dir) { + perror(path); + return -1; + } + + struct dirent *ent; + char child_path[256]; + int yes = 1; + + while ((ent = readdir(dir))) { + if (strcmp(ent->d_name, ".") && strcmp(ent->d_name, "..")) { + snprintf(child_path, sizeof(child_path), "%s/%s", path, ent->d_name); + + if (rm_node(child_path, flags) != 0) { + yes = 0; + } + } + } + + closedir(dir); + + if (!yes) { + printf("%s: Is a directory\n", path); + return -1; + } + + if (rmdir(path) != 0) { + perror(path); + return -1; + } + return 0; + } else { + if (unlink(path) != 0) { + perror(path); + return -1; + } + return 0; + } +} + +int main(int argc, char **argv) { + // 1 - recurse + int flags = 0; + int c; + + if (argc <= 1) { + printf("usage: rm [-r] ...\n"); + return -1; + } + + while ((c = getopt(argc, argv, "r")) != -1) { + switch (c) { + case 'r': + flags |= RM_RECURSIVE; + break; + default: + printf("usage: rm [-r] ..."); + return -1; + } + } + + for (int i = optind; i < argc; ++i) { + rm_node(argv[i], flags); + } + + return 0; +} diff --git a/etc/shadow b/etc/shadow new file mode 100644 index 0000000..2bb9d48 --- /dev/null +++ b/etc/shadow @@ -0,0 +1,2 @@ +root:toor: +alnyan:password: diff --git a/init.c b/init.c index e07c2cb..ac2ded6 100644 --- a/init.c +++ b/init.c @@ -1,956 +1,14 @@ -#include -#include -#include -#include -#include #include -#include -#include -#include -#include -#include #include -#include -// Cursor helpers - -#define curs_save() \ - write(STDOUT_FILENO, "\033[s", 3) -#define curs_unsave() \ - write(STDOUT_FILENO, "\033[u", 3) -#define curs_set(row, col) \ - printf("\033[%d;%df", row, col) -#define clear() \ - write(STDOUT_FILENO, "\033[2J", 4) - -#define BOX_ANGLE_UL "\332" -#define BOX_ANGLE_UR "\277" -#define BOX_ANGLE_LL "\300" -#define BOX_ANGLE_LR "\331" -#define BOX_HOR "\304" -#define BOX_VERT "\263" - -// - -struct builtin { - const char *name; - const char *desc; - int (*run) (const char *arg); -}; - -static struct utsname _utsname; -static uid_t sh_uid; -static char sh_user[64]; - -static void update_prompt(void); - -static int atoi(const char *in) { - int r = 0; - char c; - while ((c = *in)) { - if (c > '9' || c < '0') { - break; - } - - r *= 10; - r += c - '0'; - - ++in; - } - return r; -} - -static int from_oct(int *res, const char *in) { - int r = 0; - while (*in) { - if (*in < '0' || *in > '7') { - return -1; - } - r <<= 3; - r |= *in - '0'; - ++in; - } - *res = r; - return 0; -} - -static int readline(char *buf, size_t lim) { - int len = 0; - int cnt; - char c; - - while (1) { - if ((cnt = read(STDIN_FILENO, &c, 1)) != 1) { - return -1; - } - - if (len == lim) { - printf("Input line is too long\n"); - return -1; - } - - if (c == '\n') { - putchar(c); - buf[len] = 0; - break; - } else if (c == '\b') { - if (len) { - buf[--len] = 0; - printf("\033[D \033[D"); - } - } else if (c >= ' ') { - putchar(c); - buf[len++] = c; - } - } - - return len; -} - -static int b_cd(const char *path); -static int b_pwd(const char *_); -static int b_cat(const char *path); -static int b_sleep(const char *arg); -static int b_help(const char *arg); -static int b_clear(const char *arg); -static int b_wr(const char *arg); -static int b_rm(const char *arg); -static int b_mkdir(const char *arg); -static int b_dir(const char *arg); -static int b_fork(const char *arg); -static int b_whoami(const char *arg); -static int b_abort(const char *arg); -static int b_stat(const char *arg); -static int b_chmown(const char *arg); -static int b_drop(const char *arg); -static int b_app(const char *arg); -static int b_cp(const char *arg); - -static struct builtin builtins[] = { - { - "cd", - "Change working directory", - b_cd, - }, - { - "pwd", - "Print working directory", - b_pwd, - }, - { - "cat", - "Print file contents" /* Concatenate files */, - b_cat - }, - { - "wr", - "", - b_wr, - }, - { - "rm", - "Remove file/directory", - b_rm - }, - { - "mkdir", - "Create a directory", - b_mkdir - }, - { - "sleep", - "Sleep N seconds", - b_sleep - }, - { - "clear", - "Clear terminal", - b_clear, - }, - { - "dir", - "Print directory listing", - b_dir, - }, - { - "fork", - "Test fork", - b_fork, - }, - { - "whoami", - "Who am I?", - b_whoami, - }, - { - "abort", - "Suicide", - b_abort, - }, - { - "chmown", - "chmod + chown", - b_chmown, - }, - { - "stat", - "Display file/directory information", - b_stat, - }, - { - "drop", - "Become a peasant", - b_drop, - }, - { - "app", - "Append a file", - b_app, - }, - { - "cp", - "Copy a file", - b_cp, - }, - { - "help", - "Please help me", - b_help - }, - { NULL, NULL, NULL } -}; - -static int b_cd(const char *arg) { - int res; - if (!arg) { - arg = ""; - } - - if ((res = chdir(arg)) < 0) { - perror(arg); - } - - return res; -} - -static int b_pwd(const char *arg) { - char buf[512]; - if (!getcwd(buf, sizeof(buf))) { - perror("getcwd()"); - return -1; - } else { - puts(buf); - } - return 0; -} - -static int rm_node(const char *path) { - DIR *dir; - struct dirent *ent; - struct stat st; - int res; - - if ((res = stat(path, &st)) != 0) { - perror(path); - return res; - } - - if ((st.st_mode & S_IFMT) == S_IFDIR) { - char child_path[512]; - - if (!(dir = opendir(path))) { - perror(path); - return -1; - } - - res = 0; - while ((ent = readdir(dir))) { - if (strcmp(ent->d_name, ".") && strcmp(ent->d_name, "..")) { - snprintf(child_path, sizeof(child_path), "%s/%s", path, ent->d_name); - if ((res = rm_node(child_path)) != 0) { - break; - } - } - } - - closedir(dir); - - if (res == 0) { - if ((res = rmdir(path)) != 0) { - perror(path); - } - } - - return res; - } else { - if ((res = unlink(path)) != 0) { - perror(path); - } - - return res; - } -} - -static int b_fork(const char *arg) { - int pid = fork(); - - if (pid < 0) { - perror("fork()"); - return pid; - } - - if (pid == 0) { - printf("Child sleeps\n"); - usleep(10000000); - printf("Child is done sleeping\n"); - exit(0); - } - - printf("Child pid: %d\n", pid); - - return 0; -} - -static int b_chmown(const char *arg) { - mode_t accmode = 0; - uid_t uid = 0; - gid_t gid = 0; - - char buf[64]; - int res; - - if (!arg) { - return -1; - } - - // Check if file exists - if ((res = access(arg, F_OK)) != 0) { - perror(arg); - return res; - } - - printf("Mode: "); - if ((res = readline(buf, sizeof(buf))) < 0) { - return res; - } - if ((res = from_oct((int *) &accmode, buf)) != 0) { - return res; - } - - printf("UID: "); - if ((res = readline(buf, sizeof(buf))) < 0) { - return res; - } - uid = atoi(buf); - - printf("GID: "); - if ((res = readline(buf, sizeof(buf))) < 0) { - return res; - } - gid = atoi(buf); - - if ((res = chown(arg, uid, gid)) != 0) { - perror(arg); - return res; - } - - if ((res = chmod(arg, accmode)) != 0) { - perror(arg); - return res; - } - - return 0; -} - -static char *basename(char *path) { - char *p = strrchr(path, '/'); - if (!p) { - return path; - } else { - return p + 1; - } -} - -static int b_cp(const char *arg) { - char src_path[256]; - char dst_path[256]; - struct stat st; - int fd_src, fd_dst; - int res; - - const char *spc = strchr(arg, ' '); - if (!spc) { - printf("Usage: cp "); - return -1; - } - - strncpy(src_path, arg, spc - arg); - strcpy(dst_path, spc + 1); - - // Check that source is a file - if ((res = stat(src_path, &st)) != 0) { - perror(src_path); - return res; - } else if ((st.st_mode & S_IFMT) != S_IFREG) { - printf("Not a file: %s\n", src_path); - return -1; - } - - // Check if destination is a directory - if ((res = stat(dst_path, &st)) == 0) { - if ((st.st_mode & S_IFMT) == S_IFDIR) { - const char *name = basename(src_path); - size_t len = strlen(dst_path); - dst_path[len++] = '/'; - strcpy(&dst_path[len], name); - // TODO: check that this file is not some kind of a special device - } else if ((st.st_mode & S_IFMT) != S_IFREG) { - printf("Invalid destination: %s\n", dst_path); - return -1; - } - } - - if ((fd_src = open(src_path, O_RDONLY, 0)) < 0) { - perror(src_path); - return -1; - } - - // TODO: does cp copy file mode? - if ((fd_dst = open(dst_path, O_WRONLY | O_TRUNC | O_CREAT, 0644)) < 0) { - close(fd_src); - perror(dst_path); - return -1; - } - - char buf[512]; - ssize_t bread; - ssize_t bwrite; - - while ((bread = read(fd_src, buf, sizeof(buf))) > 0) { - if ((bwrite = write(fd_dst, buf, bread)) != bread) { - printf("Write failed\n"); - break; - } - } - - close(fd_dst); - close(fd_src); - - return 0; -} - -static int b_app(const char *arg) { - if (!arg) { - return -1; - } - - int opt = O_APPEND | O_WRONLY; - - while (1) { - while (isspace(*arg)) { - ++arg; - } - if (arg[0] == '-' && isalpha(arg[1])) { - // Some flag - switch (arg[1]) { - case 'c': - opt |= O_CREAT; - break; - case 't': - opt |= O_TRUNC; - break; - default: - printf("Unknown flag: -%c\n", arg[1]); - return -1; - } - arg += 2; - } else { - break; - } - } - - int fd = open(arg, opt, 0644); - if (fd < 0) { - perror(arg); - return fd; - } - - printf("Type \"EOF\" to stop writing\n"); - - char buf[512]; - int line_len; - - while (1) { - if ((line_len = readline(buf, sizeof(buf))) < 0) { - break; - } - - if (!strcmp(buf, "EOF")) { - break; - } - - if (write(fd, buf, line_len) < 0) { - printf("Write failed\n"); - break; - } - *(char *) &line_len = '\n'; - write(fd, &line_len, 1); - } - - close(fd); - - return 0; -} - -static int b_rm(const char *arg) { - int res; - int is_r = 0; - if (!arg) { - return -1; - } - - if (!strncmp(arg, "-r ", 3)) { - arg += 3; - is_r = 1; - } - - if (!is_r) { - if ((res = unlink(arg)) < 0) { - perror(arg); - } - } else { - rm_node(arg); - } - - return res; -} - -static int b_mkdir(const char *arg) { - int res; - if (!arg) { - return -1; - } - - if ((res = mkdir(arg, 0755)) < 0) { - perror(arg); - } - - return res; -} - -static int b_dir(const char *arg) { - assert(arg); - DIR *dir; - struct dirent *ent; - - if (!(dir = opendir(arg))) { - perror(arg); - return -1; - } - - while ((ent = readdir(dir))) { - printf("%c %s\n", ent->d_type == DT_DIR ? 'd' : 'f', ent->d_name); - } - - closedir(dir); - - return 0; -} - -static int b_abort(const char *arg) { - printf("Before SIGUSR1\n"); - raise(SIGUSR1); - printf("After SIGUSR1\n"); - abort(); - printf("This code should not run\n"); - return 0; -} - -static int b_stat(const char *arg) { - if (!arg) { - return -1; - } - - struct stat st; - int res; - - if ((res = stat(arg, &st)) != 0) { - perror(arg); - return res; - } - - printf("dev %u\n", st.st_dev); - printf("rdev %u\n", st.st_rdev); - - printf("inode %u\n", st.st_ino); - - printf("mode %u\n", st.st_mode); - printf("nlink %u\n", st.st_nlink); - - printf("uid %u\n", st.st_uid); - printf("gid %u\n", st.st_gid); - - printf("size %u\n", st.st_size); - printf("blocks %u\n", st.st_blocks); - printf("blksize %u\n", st.st_blksize); - - printf("atime %u\n", st.st_atime); - printf("mtime %u\n", st.st_mtime); - printf("ctime %u\n", st.st_ctime); - - return 0; -} - -static int b_wr(const char *arg) { - int fd; - char buf[512]; - int line_len; - - if (!arg) { - return -1; - } - - if ((fd = open(arg, O_WRONLY | O_TRUNC | O_CREAT, 0644)) < 0) { - perror(arg); - return fd; - } - - printf("Type \"EOF\" to stop writing\n"); - - while (1) { - if ((line_len = readline(buf, sizeof(buf))) < 0) { - break; - } - - if (!strcmp(buf, "EOF")) { - break; - } - - if (write(fd, buf, line_len) != line_len) { - break; - } - char c = '\n'; - write(fd, &c, 1); - } - - close(fd); - - return 0; -} - -static int b_drop(const char *arg) { - int res; - - if ((res = setuid(1000)) != 0) { - perror("setuid()"); - return res; - } - if ((res = setgid(1000)) != 0) { - perror("setgid()"); - return res; - } - - update_prompt(); - - return 0; -} - -static int b_cat(const char *arg) { - char buf[512]; - int fd; - ssize_t bread; - - if (!arg) { - return -1; - } - - if ((fd = open(arg, O_RDONLY, 0)) < 0) { - perror(arg); - return fd; - } - - while ((bread = read(fd, buf, sizeof(buf))) > 0) { - write(STDOUT_FILENO, buf, bread); - } - - close(fd); - - return 0; -} - -static int b_sleep(const char *arg) { - if (!arg) { - return -1; - } - int seconds = 0; - while (*arg) { - seconds *= 10; - seconds += *arg - '0'; - ++arg; - } - - struct timespec ts = { seconds, 0 }; - if ((seconds = nanosleep(&ts, NULL))) { - perror("nanosleep()"); - return seconds; - } - - return 0; -} - -static int b_clear(const char *arg) { - clear(); - curs_set(1, 1); - return 0; -} - -static int b_whoami(const char *arg) { - uid_t uid = getuid(); - gid_t gid = getgid(); - - printf("%d:%d\n", uid, gid); - - return 0; -} - -static int b_help(const char *arg) { - if (arg) { - // Describe a specific command - for (size_t i = 0; builtins[i].run; ++i) { - if (!strcmp(arg, builtins[i].name)) { - printf("%s: %s\n", builtins[i].name, builtins[i].desc); - return 0; - } - } - - printf("%s: Unknown command\n", arg); - return -1; - } else { - for (size_t i = 0; builtins[i].run; ++i) { - printf("%s: %s\n", builtins[i].name, builtins[i].desc); - } - - return 0; - } -} - -static void prompt(void) { - char cwd[512]; - if (!getcwd(cwd, sizeof(cwd))) { - cwd[0] = '?'; - cwd[1] = 0; - } - - if (sh_user[0]) { - printf("\033[35m%s\033[0m@\033[36m%s\033[0m %s > ", sh_user, _utsname.nodename, cwd); - } else { - printf("\033[35m%u\033[0m@\033[36m%s\033[0m %s > ", sh_uid, _utsname.nodename, cwd); - } -} - -static int cmd_subproc_exec(const char *abs_path, const char *cmd, const char *e) { - // Maximum of 8 arguments 64 chars each (63) - // Split input argument into pieces by space - char args[64 * 8] = {0}; - const char *p = e; - const char *t = NULL; - size_t argc = 0; - while (p) { - t = strchr(p, ' '); - if (!t) { - // Last argument - assert(strlen(p) < 64); - strcpy(&args[argc++ * 64], p); - break; - } else { - assert(t - p < 64); - strncpy(&args[argc * 64], p, t - p); - args[(argc++ * 64) + (t - p)] = 0; - p = t + 1; - while (*p == ' ') { - ++p; - } - if (!*p) { - break; - } - } - } - - // Fill arg pointer array - const char *argp[10] = { cmd }; - for (size_t i = 0; i < argc; ++i) { - argp[i + 1] = &args[i * 64]; - } - argp[argc + 1] = NULL; - - int pid = fork(); - int status; - - switch (pid) { - case -1: - perror("fork()"); - return -1; - case 0: - if (execve(abs_path, argp, NULL) != 0) { - perror("execve()"); - } - exit(-1); - default: - if (waitpid(pid, &status) != 0) { - perror("waitpid()"); - } - if (status != 0) { - printf("%d finished with status: %d\n", pid, status); - } - return 0; - } -} - -static int cmd_exec(const char *line) { - char cmd[64]; - const char *e = strchr(line, ' '); - - if (!e) { - assert(strlen(line) < 64); - strcpy(cmd, line); - } else { - assert(e - line < 64); - strncpy(cmd, line, e - line); - cmd[e - line] = 0; - ++e; - while (*e == ' ') { - ++e; - } - } - - if (e && !*e) { - e = NULL; - } - - for (size_t i = 0; builtins[i].run; ++i) { - if (!strcmp(cmd, builtins[i].name)) { - return builtins[i].run(e); - } - } - - // If command starts with ./ or /, try to execute it - if (((cmd[0] == '.' && cmd[1] == '/') || cmd[0] == '/') && access(cmd, X_OK) == 0) { - return cmd_subproc_exec(cmd, cmd, e); - } - // Try to execute binary from /bin - // if name has no slashes - if (!strchr(cmd, '/')) { - char path_buf[512]; - snprintf(path_buf, sizeof(path_buf), "/bin/%s", cmd); - if (access(path_buf, X_OK) == 0) { - return cmd_subproc_exec(path_buf, cmd, e); - } - } - - printf("%s: Unknown command\n", cmd); - return -1; -} - -static void sigusr1_handler(int signum) { - printf("SIGUSR1 received and handled\n"); -} - -#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 +// The only thing init does now +static int start_login(void) { + const char *login = "/bin/login"; + const char *const argp[] = { + login, NULL }; - 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) < 0) { - return -1; - } - - if (c == '\033') { - int r = escape_read(); - - if (r > 0) { - return r; - } - } - - return c; -} - -void update_prompt(void) { - struct passwd pwd; - char pwdbuf[512]; - struct passwd *res; - - if (uname(&_utsname) != 0) { - perror("uname()"); - _utsname.nodename[0] = '?'; - _utsname.nodename[1] = 0; - } - - sh_uid = getuid(); - - if (getpwuid_r(sh_uid, &pwd, pwdbuf, sizeof(pwdbuf), &res) != 0) { - perror("getpwuid()"); - sh_user[0] = 0; - } else { - strcpy(sh_user, res->pw_name); - } + // No fork needed, I guess + return execve(login, argp, NULL); } int main(int argc, char **argv) { @@ -958,73 +16,5 @@ int main(int argc, char **argv) { printf("Won't work if PID is not 1\n"); return -1; } - - signal(SIGUSR1, sigusr1_handler); - - char linebuf[512]; - int c; - size_t l = 0; - int res; - - //setuid(1000); - //setgid(1000); - - update_prompt(); - prompt(); - -#if 0 - if ((res = fork()) < 0) { - perror("fork()"); - return -1; - } else if (res == 0) { - if (execve("/time", NULL, NULL) != 0) { - perror("execve()"); - } - exit(-1); - } -#endif - - while (1) { - if ((c = getch()) < 0) { - break; - } - - if (c == '\033') { - for (int i = 0; i < l; ++i) { - printf("\033[D \033[D"); - } - linebuf[0] = 0; - l = 0; - continue; - } - if (c == '\b') { - if (l) { - linebuf[--l] = 0; - printf("\033[D \033[D"); - } - continue; - } - if (c == '\n') { - write(STDOUT_FILENO, &c, 1); - linebuf[l] = 0; - - if (!strcmp(linebuf, "exit")) { - break; - } - - l = 0; - if ((res = cmd_exec(linebuf)) != 0) { - printf("\033[31m= %d\033[0m\n", res); - } - prompt(); - continue; - } - - if (c >= ' ' && c < 255) { - linebuf[l++] = c; - write(STDOUT_FILENO, &c, 1); - } - } - - return -1; + return start_login(); } diff --git a/sh/builtin.c b/sh/builtin.c new file mode 100644 index 0000000..0a03ed9 --- /dev/null +++ b/sh/builtin.c @@ -0,0 +1,96 @@ +#include +#include +#include +#include "builtin.h" +#include "cmd.h" + +#define DEF_BUILTIN(b_name) \ + static int __bcmd_##b_name(const struct cmd_exec *cmd) +#define DECL_BUILTIN(b_name) \ + { .name = #b_name, .exec = __bcmd_##b_name } + +struct sh_builtin { + const char *name; + int (*exec) (const struct cmd_exec *cmd); +}; + +static struct sh_builtin __builtins[]; + +//// + +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(clear) { + puts2("\033[2J\033[1;1f"); + 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(cd), + DECL_BUILTIN(clear), + DECL_BUILTIN(exit), + DECL_BUILTIN(setid), + {NULL} +}; + + +int builtin_exec(const struct cmd_exec *cmd, int *cmd_res) { + for (size_t i = 0; i < sizeof(__builtins) / sizeof(__builtins[0]); ++i) { + if (!strcmp(__builtins[i].name, cmd->args[0])) { + *cmd_res = __builtins[i].exec(cmd); + return 0; + } + } + + return -1; +} diff --git a/sh/builtin.h b/sh/builtin.h new file mode 100644 index 0000000..ff8acfd --- /dev/null +++ b/sh/builtin.h @@ -0,0 +1,5 @@ +#pragma once + +struct cmd_exec; + +int builtin_exec(const struct cmd_exec *cmd, int *res); diff --git a/sh/cmd.c b/sh/cmd.c new file mode 100644 index 0000000..cdef469 --- /dev/null +++ b/sh/cmd.c @@ -0,0 +1,121 @@ +#include +#include +#include +#include + +#include "builtin.h" +#include "config.h" +#include "cmd.h" + +static int make_cmd(char *input, struct cmd_exec *ex) { + char *p = strchr(input, ' '); + char *e; + + if (!p) { + ex->args[0] = input; + ex->args[1] = NULL; + ex->argc = 1; + return 0; + } + + *p++ = 0; + ex->args[0] = input; + ex->argc = 1; + + while (1) { + while (isspace(*p)) { + ++p; + } + if (!*p) { + break; + } + + e = strchr(p, ' '); + + if (!e) { + ex->args[ex->argc++] = p; + break; + } else { + *e++ = 0; + ex->args[ex->argc++] = p; + p = e; + } + } + ex->args[ex->argc] = NULL; + + return 0; +} + +// + +static int cmd_spawn(const char *path, const struct cmd_exec *cmd, int *cmd_res) { + int pid; + + if ((pid = fork()) < 0) { + perror("fork()"); + return -1; + } + + if (pid == 0) { + exit(execve(path, (const char *const *) cmd->args, NULL)); + } else { + if (waitpid(pid, cmd_res) != 0) { + perror("waitpid()"); + } + return 0; + } +} + +static int cmd_exec_binary(const struct cmd_exec *cmd, int *cmd_res) { + 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(path_path, cmd, cmd_res); + } + + snprintf(path_path, sizeof(path_path), "%s/%s", PATH, cmd->args[0]); + if ((res = access(path_path, X_OK)) == 0) { + return cmd_spawn(path_path, cmd, cmd_res); + } + return -1; +} + +static int cmd_exec(const struct cmd_exec *cmd) { + int res, cmd_res; + + if ((res = builtin_exec(cmd, &cmd_res)) == 0) { + return cmd_res; + } + + if ((res = cmd_exec_binary(cmd, &cmd_res)) == 0) { + return cmd_res; + } + + printf("sh: command not found: %s\n", cmd->args[0]); + return -1; +} + +int eval(char *str) { + struct cmd_exec cmd; + + while (isspace(*str)) { + ++str; + } + + if (!*str) { + return 0; + } + + if (make_cmd(str, &cmd) != 0) { + abort(); + } + + return cmd_exec(&cmd); +} diff --git a/sh/cmd.h b/sh/cmd.h new file mode 100644 index 0000000..54cea7c --- /dev/null +++ b/sh/cmd.h @@ -0,0 +1,8 @@ +#pragma once + +struct cmd_exec { + size_t argc; + char *args[12]; +}; + +int eval(char *str); diff --git a/sh/config.h b/sh/config.h new file mode 100644 index 0000000..d8b59cf --- /dev/null +++ b/sh/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/sh/readline.c b/sh/readline.c new file mode 100644 index 0000000..3d01be1 --- /dev/null +++ b/sh/readline.c @@ -0,0 +1,145 @@ +#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) < 0) { + return -1; + } + + if (c == '\033') { + int r = escape_read(); + + if (r > 0) { + return r; + } + } + + return c; +} + +int readline(char *buf, size_t lim) { + 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/sh/readline.h b/sh/readline.h new file mode 100644 index 0000000..61bbeaa --- /dev/null +++ b/sh/readline.h @@ -0,0 +1,3 @@ +#pragma once + +int readline(char *buf, size_t lim); diff --git a/sh/sh.c b/sh/sh.c new file mode 100644 index 0000000..fe87e8b --- /dev/null +++ b/sh/sh.c @@ -0,0 +1,93 @@ +#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[512]; + + p_uid = getuid(); + + if (getcwd(p_cwd, sizeof(p_cwd)) == NULL) { + perror("getcwd()"); + p_cwd[0] = 0; + } + + if (uname(&_u) != 0) { + perror("uname()"); + p_hostname[0] = 0; + } 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': + puts2(p_hostname); + break; + case 'u': + puts2(p_username); + break; + case 'd': + puts2(p_cwd); + break; + default: + putchar('%'); + putchar(*p); + break; + } + } else { + putchar(*p); + } + + ++p; + } +} + +int main(int argc, char **argv) { + char linebuf[256]; + int res; + + 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; +}