#include #include #include #include #include #include #include #include #include static int status = 0; static char* username = NULL; typedef struct { char* buffer; size_t size; size_t capacity; int interactive; } command_t; char** split_command_into_argv(const char* cmd) { size_t argc = 1; char* ptr = strdup(cmd); char* endptr; char** arr = calloc(sizeof(char*), argc); for (;;) { endptr = strchr(ptr, ' '); arr[argc - 1] = ptr; if (endptr == NULL) break; *endptr = 0; ptr = endptr + 1; if (*ptr) { argc++; arr = realloc(arr, sizeof(char*) * argc); } else break; } argc++; arr = realloc(arr, sizeof(char*) * argc); arr[argc - 1] = NULL; return arr; } char* shell_concat_path(const char* dirname, const char* basename) { char* buf = malloc(strlen(basename) + strlen(dirname) + 6); strlcpy(buf, dirname, strlen(dirname) + 1); strncat(buf, basename, strlen(basename)); return buf; } void shell_execvp(char* pathname, char* const argv[]) { char* new_path; if (access(pathname, F_OK) == 0) { execv(pathname, argv); return; } if (pathname[0] == '/') return; // We do not want to lookup absolute paths new_path = shell_concat_path("/bin/", pathname); if (access(new_path, F_OK) == 0) { execv(new_path, argv); return; } free(new_path); new_path = shell_concat_path("/usr/bin/", pathname); execv(new_path, argv); int saved = errno; free(new_path); errno = saved; } void show_prompt() { if (WEXITSTATUS(status)) { printf("%d [%s]> ", WEXITSTATUS(status), username); } else printf("[%s]> ", username); } int command_matches(command_t* cmd, const char* string) { if (cmd->size <= strlen(string)) // cmd->size includes null terminator return 0; return strncmp(cmd->buffer, string, strlen(string)) == 0; } int command_matches_exactly(command_t* cmd, const char* string) { if (cmd->size <= strlen(string)) // cmd->size includes null terminator return 0; if (cmd->size > (strlen(string) + 1)) return 0; return strncmp(cmd->buffer, string, strlen(string)) == 0; } int command_match_builtins(command_t* cmd) { if (command_matches(cmd, "exit ")) { exit(atoi(cmd->buffer + 5)); } if (command_matches_exactly(cmd, "exit")) { exit(0); } if (command_matches_exactly(cmd, "id")) { printf("pid %ld, ppid %ld, uid %d (%s), gid %d\n", getpid(), getppid(), getuid(), username, getgid()); return 1; } if (command_matches_exactly(cmd, "clear")) { fputs("\033@", stdout); // clear screen. for now, escape sequences in luna are non-standard. return 1; } return 0; } void command_expand(command_t* cmd, long new_capacity) { char* buffer = realloc(cmd->buffer, new_capacity); if (!buffer) { perror("realloc"); exit(1); } cmd->buffer = buffer; cmd->capacity = new_capacity; } void command_push(command_t* cmd, char c) { if (cmd->size == cmd->capacity) command_expand(cmd, cmd->capacity + 8); cmd->buffer[cmd->size] = c; cmd->size++; } void command_pop(command_t* cmd) { cmd->size--; } void command_init(command_t* cmd) { cmd->buffer = malloc(5); cmd->capacity = 5; cmd->size = 0; } void command_clear(command_t* cmd) { free(cmd->buffer); return command_init(cmd); } void process_execute_command(const char* command) { char** argv = split_command_into_argv(command); shell_execvp(argv[0], argv); perror(argv[0]); exit(127); } void command_execute(command_t* cmd) { command_push(cmd, '\0'); if (command_match_builtins(cmd)) { command_clear(cmd); if (cmd->interactive) show_prompt(); return; } pid_t child = fork(); if (child < 0) { perror("fork"); command_clear(cmd); if (cmd->interactive) show_prompt(); return; } if (child == 0) process_execute_command(cmd->buffer); pid_t result = waitpid(child, &status, 0); if (result < 0) { perror("waitpid"); command_clear(cmd); if (cmd->interactive) show_prompt(); return; } int exit_status = WEXITSTATUS(status); if (exit_status == -2 || exit_status == -3) printf("(PID %ld) Segmentation fault\n", result); if (exit_status == -1) printf("(PID %ld) Aborted\n", result); command_clear(cmd); if (cmd->interactive) show_prompt(); } void command_concat_char(command_t* cmd, char c) { if (c == '\b') { if (cmd->size != 0) { if (cmd->interactive) putchar(c); command_pop(cmd); } } else if (c == '\n') { if (cmd->interactive) putchar(c); if (cmd->size == 0) { status = 0; if (cmd->interactive) show_prompt(); } else command_execute(cmd); } else { if (cmd->interactive) putchar(c); command_push(cmd, c); } } void command_concat(command_t* cmd, const char* str) { while (*str) { command_concat_char(cmd, *str); str++; } } void shell_interactive() { show_prompt(); command_t shell_command; command_init(&shell_command); shell_command.interactive = 1; while (1) { int c = getchar(); if (c == EOF) { if (ferror(stdin)) { perror("getchar"); exit(EXIT_FAILURE); } if (feof(stdin)) exit(EXIT_SUCCESS); assert(false); // we should never get here } command_concat_char(&shell_command, (char)c); } } void shell_read_from_file(const char* pathname) { FILE* fp = fopen(pathname, "r"); if (!fp) { perror("sh"); exit(EXIT_FAILURE); } command_t file_command; command_init(&file_command); file_command.interactive = 0; char buffer[BUFSIZ]; while (fgets(buffer, BUFSIZ, fp)) { command_concat(&file_command, buffer); if (feof(fp)) break; } if (file_command.size > 0) // last line of file, does not end with newline { command_execute(&file_command); } fclose(fp); } void shell_execute_command(const char* command) { command_t cmd; cmd.buffer = strdup(command); cmd.size = strlen(command) + 1; if (command_match_builtins(&cmd)) return; command_clear(&cmd); process_execute_command(command); } void fetch_username() { struct passwd* user = getpwuid(getuid()); if (!user) { perror("getpwuid"); exit(EXIT_FAILURE); } username = user->pw_name; endpwent(); } int main(int argc, char** argv) { fetch_username(); if (argc == 1) shell_interactive(); else if (argc == 2) { if (!strcmp(argv[1], "-v") || !strcmp(argv[1], "--version")) { puts("Luna sh version 0.1"); // FIXME: Store the version somewhere, or use the kernel's version. return 0; } if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) { printf("To use interactively: %s\n", argv[0]); printf("To run a script: %s [script-name]\n", argv[0]); printf("To get help: %s --help\n", argv[0]); printf("To show the version: %s --version\n", argv[0]); printf("To run a command: %s -c [command]\n", argv[0]); return 0; } if (!strcmp(argv[1], "-c") || !strcmp(argv[1], "--command")) { fprintf(stderr, "Usage: %s %s [command]\n", argv[0], argv[1]); fprintf(stderr, "Use the --help flag for more help.\n"); return 1; } shell_read_from_file(argv[1]); } else if (argc == 3) { if (!strcmp(argv[1], "-c") || !strcmp(argv[1], "--command")) shell_execute_command(argv[2]); else { fprintf(stderr, "%s: too many arguments\n", argv[0]); fprintf(stderr, "Use the --help flag for more help.\n"); return 1; } } else { fprintf(stderr, "%s: too many arguments\n", argv[0]); fprintf(stderr, "Use the --help flag for more help.\n"); return 1; } }