diff --git a/utils/.gitignore b/utils/.gitignore index a09eb63c..279805f0 100644 --- a/utils/.gitignore +++ b/utils/.gitignore @@ -36,6 +36,7 @@ rm rmdir sh sort +sortix-sh tail time tr diff --git a/utils/Makefile b/utils/Makefile index 1448e551..7388409f 100644 --- a/utils/Makefile +++ b/utils/Makefile @@ -47,6 +47,7 @@ rm \ rmdir \ sh \ sort \ +sortix-sh \ tail \ time \ tr \ diff --git a/utils/sh.cpp b/utils/sh.cpp index 4e44b189..5df3aea2 100644 --- a/utils/sh.cpp +++ b/utils/sh.cpp @@ -1,6 +1,6 @@ /******************************************************************************* - Copyright(C) Jonas 'Sortie' Termansen 2011, 2012, 2013, 2014. + Copyright(C) Jonas 'Sortie' Termansen 2014. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free @@ -16,620 +16,114 @@ this program. If not, see . sh.cpp - A simple and hacky Sortix shell. + Forward execution to the best shell. *******************************************************************************/ -#include #include #include -#include -#include #include -#include -#include #include #include #include -#include #include -int status = 0; - const char* getenv_safe(const char* name, const char* def = "") { const char* ret = getenv(name); return ret ? ret : def; } -void on_sigint(int /*signum*/) +bool is_existing_shell(const char* candidate) { - printf("^C\n"); -} - -void updatepwd() -{ - const size_t CWD_SIZE = 512; - char cwd[CWD_SIZE]; - const char* wd = getcwd(cwd, CWD_SIZE); - if ( !wd ) { wd = "?"; } - setenv("PWD", wd, 1); -} - -void updateenv() -{ - char str[128]; - struct winsize ws; - if ( tcgetwinsize(0, &ws) == 0 ) - { - sprintf(str, "%zu", ws.ws_col); - setenv("COLUMNS", str, 1); - sprintf(str, "%zu", ws.ws_row); - setenv("LINES", str, 1); - } -} - -bool matches_simple_pattern(const char* string, const char* pattern) -{ - size_t wildcard_index = strcspn(pattern, "*"); - if ( !pattern[wildcard_index] ) - return strcmp(string, pattern) == 0; - if ( pattern[0] == '*' && string[0] == '.' ) + pid_t child_pid = fork(); + if ( child_pid < 0 ) return false; - size_t string_length = strlen(string); - size_t pattern_length = strlen(pattern); - size_t pattern_last = pattern_length - (wildcard_index + 1); - return strncmp(string, pattern, wildcard_index) == 0 && - strcmp(string + string_length - pattern_last, - pattern + wildcard_index + 1) == 0; -} - -int runcommandline(const char** tokens, bool* exitexec, bool interactive) -{ - int result = 127; - size_t cmdnext = 0; - size_t cmdstart; - size_t cmdend; - bool lastcmd = false; - int pipein = 0; - int pipeout = 1; - int pipeinnext = 0; - char** argv; - size_t cmdlen; - const char* execmode; - const char* outputfile; - pid_t childpid; - pid_t pgid = -1; - bool internal; - int internalresult; - size_t num_tokens = 0; - while ( tokens[num_tokens] ) - num_tokens++; -readcmd: - // Collect any pending zombie processes. - while ( 0 < waitpid(-1, NULL, WNOHANG) ); - - cmdstart = cmdnext; - for ( cmdend = cmdstart; true; cmdend++ ) - { - const char* token = tokens[cmdend]; - if ( !token || - strcmp(token, ";") == 0 || - strcmp(token, "&") == 0 || - strcmp(token, "|") == 0 || - strcmp(token, ">") == 0 || - strcmp(token, ">>") == 0 || - false ) - { - break; - } - } - - cmdlen = cmdend - cmdstart; - if ( !cmdlen ) { fprintf(stderr, "expected command\n"); goto out; } - execmode = tokens[cmdend]; - if ( !execmode ) { lastcmd = true; execmode = ";"; } - tokens[cmdend] = NULL; - - if ( strcmp(execmode, "|") == 0 ) - { - int pipes[2]; - if ( pipe(pipes) ) { perror("pipe"); goto out; } - if ( pipeout != 1 ) { close(pipeout); } pipeout = pipes[1]; - if ( pipeinnext != 0 ) { close(pipeinnext); } pipeinnext = pipes[0]; - } - - outputfile = NULL; - if ( strcmp(execmode, ">") == 0 || strcmp(execmode, ">>") == 0 ) - { - outputfile = tokens[cmdend+1]; - if ( !outputfile ) { fprintf(stderr, "expected filename\n"); goto out; } - const char* nexttok = tokens[cmdend+2]; - if ( nexttok ) { fprintf(stderr, "too many filenames\n"); goto out; } - } - - for ( size_t i = cmdstart; i < cmdend; i++ ) - { - const char* pattern = tokens[i]; - size_t wildcard_pos = strcspn(pattern, "*"); - if ( !pattern[wildcard_pos] ) - continue; - bool found_slash = false; - size_t last_slash = 0; - for ( size_t i = 0; i < wildcard_pos; i++ ) - if ( pattern[i] == '/' ) - last_slash = i, found_slash = true; - size_t match_from = found_slash ? last_slash + 1 : 0; - DIR* dir; - size_t pattern_prefix = 0; - if ( !found_slash ) - { - if ( !(dir = opendir(".")) ) - continue; - } - else - { - char* dirpath = strdup(pattern); - if ( !dirpath ) - continue; - dirpath[last_slash] = '\0'; - pattern_prefix = last_slash + 1; - dir = opendir(dirpath); - free(dirpath); - if ( !dir ) - continue; - } - size_t num_inserted = 0; - size_t last_inserted_index = i; - while ( struct dirent* entry = readdir(dir) ) - { - if ( !matches_simple_pattern(entry->d_name, pattern + match_from) ) - continue; - // TODO: Memory leak. - char* name = (char*) malloc(pattern_prefix + strlen(entry->d_name) + 1); - memcpy(name, pattern, pattern_prefix); - strcpy(name + pattern_prefix, entry->d_name); - if ( !name ) - continue; - if ( num_inserted ) - { - // TODO: Reckless modification of the tokens array. - for ( size_t n = num_tokens; n != last_inserted_index; n-- ) - tokens[n+1] = tokens[n]; - num_tokens++; - cmdend++; - } - // TODO: Reckless modification of the tokens array. - tokens[last_inserted_index = i + num_inserted++] = name; - } - closedir(dir); - } - - cmdnext = cmdend + 1; - argv = (char**) (tokens + cmdstart); - - updateenv(); - char statusstr[32]; - sprintf(statusstr, "%i", status); - setenv("?", statusstr, 1); - - for ( char** argp = argv; *argp; argp++ ) - { - char* arg = *argp; - if ( arg[0] != '$' ) - continue; - arg = getenv(arg+1); - if ( !arg ) - arg = (char*) ""; - *argp = arg; - } - - internal = false; - internalresult = 0; - if ( strcmp(argv[0], "cd") == 0 ) - { - internal = true; - const char* newdir = getenv_safe("HOME", "/"); - if ( argv[1] ) { newdir = argv[1]; } - if ( chdir(newdir) ) - { - error(0, errno, "cd: %s", newdir); - internalresult = 1; - } - updatepwd(); - } - if ( strcmp(argv[0], "exit") == 0 ) - { - int exitcode = argv[1] ? atoi(argv[1]) : 0; - *exitexec = true; - return exitcode; - } - if ( strcmp(argv[0], "unset") == 0 ) - { - internal = true; - unsetenv(argv[1] ? argv[1] : ""); - } - if ( strcmp(argv[0], "clearenv") == 0 ) - { - internal = true; - clearenv(); - } - - childpid = internal ? getpid() : fork(); - if ( childpid < 0 ) { perror("fork"); goto out; } - if ( childpid ) - { - if ( !internal ) - { - if ( pgid == -1 ) - pgid = childpid; - setpgid(childpid, pgid); - } - - if ( pipein != 0 ) { close(pipein); pipein = 0; } - if ( pipeout != 1 ) { close(pipeout); pipeout = 1; } - if ( pipeinnext != 0 ) { pipein = pipeinnext; pipeinnext = 0; } - - if ( strcmp(execmode, "&") == 0 && !tokens[cmdnext] ) - { - result = 0; goto out; - } - - if ( strcmp(execmode, "&") == 0 ) - pgid = -1; - - if ( strcmp(execmode, "&") == 0 || strcmp(execmode, "|") == 0 ) - { - goto readcmd; - } - - status = internalresult; - int exitstatus; - tcsetpgrp(0, pgid); - if ( !internal && waitpid(childpid, &exitstatus, 0) < 0 ) - { - perror("waitpid"); - return 127; - } - tcsetpgrp(0, getpgid(0)); - - // TODO: HACK: Most signals can't kill processes yet. - if ( WEXITSTATUS(exitstatus) == 128 + SIGINT ) - printf("^C\n"); - if ( WTERMSIG(status) == SIGKILL ) - printf("Killed\n"); - - status = WEXITSTATUS(exitstatus); - - if ( strcmp(execmode, ";") == 0 && tokens[cmdnext] && !lastcmd ) - { - goto readcmd; - } - - result = status; - goto out; - } - - setpgid(0, pgid != -1 ? pgid : 0); - - if ( pipeinnext != 0 ) { close(pipeinnext); } - - if ( pipein != 0 ) + if ( !child_pid ) { close(0); - dup(pipein); - close(pipein); - } - - if ( pipeout != 1 ) - { close(1); - dup(pipeout); - close(pipeout); + close(2); + open("/dev/null", O_RDONLY); + open("/dev/null", O_WRONLY); + open("/dev/null", O_WRONLY); + execlp("which", "which", "--", candidate, (char*) NULL); + exit(127); } - - if ( outputfile ) - { - close(1); - // TODO: Is this logic right or wrong? - int flags = O_CREAT | O_WRONLY; - if ( strcmp(execmode, ">") == 0 ) - flags |= O_TRUNC; - if ( strcmp(execmode, ">>") == 0 ) - flags |= O_APPEND; - if ( open(outputfile, flags, 0666) < 0 ) - { - error(127, errno, "%s", outputfile); - } - } - - execvp(argv[0], argv); - if ( interactive && errno == ENOENT ) - { - int errno_saved = errno; - execlp("command-not-found", "command-not-found", argv[0], NULL); - errno = errno_saved; - } - error(127, errno, "%s", argv[0]); - return 127; - -out: - if ( pipein != 0 ) { close(pipein); } - if ( pipeout != 1 ) { close(pipeout); } - if ( pipeinnext != 0 ) { close(pipeout); } - return result; + int status; + waitpid(child_pid, &status, 0); + return WIFEXITED(status) && WEXITSTATUS(status) == 0; } -int get_and_run_command(FILE* fp, const char* fpname, bool interactive, - bool exit_on_error, bool* exitexec) +char* search_for_proper_shell() { - int fd = fileno(fp); - - if ( interactive ) + if ( getenv("SORTIX_SH_BACKEND") ) { - unsigned termmode = TERMMODE_UNICODE - | TERMMODE_SIGNAL - | TERMMODE_UTF8 - | TERMMODE_LINEBUFFER - | TERMMODE_ECHO; - settermmode(fd, termmode); - const char* print_username = getlogin(); - if ( !print_username ) - print_username = getuid() == 0 ? "root" : "?"; - const char* print_hostname = getenv_safe("HOSTNAME", "sortix"); - const char* print_dir = getenv_safe("PWD", "?"); - const char* home_dir = getenv_safe("HOME", ""); - size_t home_dir_len = strlen(home_dir); - printf("\e[32m"); - printf("%s", print_username); - printf("@"); - printf("%s", print_hostname); - printf(" "); - printf("\e[36m"); - if ( home_dir_len && strncmp(print_dir, home_dir, home_dir_len) == 0 ) - printf("~%s", print_dir + home_dir_len); - else - printf("%s", print_dir); - printf(" "); - printf("#"); - printf("\e[37m "); - fflush(stdout); + if ( !getenv("SORTIX_SH_BACKEND")[0] ) + return NULL; + if ( is_existing_shell(getenv("SORTIX_SH_BACKEND")) ) + return strdup(getenv("SORTIX_SH_BACKEND")); } - static char* command = NULL; - static size_t commandlen = 1024; - if ( !command ) - commandlen = 1024, - command = (char*) malloc((commandlen+1) * sizeof(char)); - if ( !command ) - error(64, errno, "malloc"); + const char* backends_dir_path = + getenv_safe("SORTIX_SH_BACKENDS_DIR", "/etc/proper-shells"); - size_t commandused = 0; - bool commented = false; - bool escaped = false; - while (true) + struct dirent** shell_entries; + int num_shell_entries = scandir(backends_dir_path, &shell_entries, NULL, alphasort); + if ( 0 <= num_shell_entries ) { - char c; - if ( fd < 0 ) + char* result = NULL; + for ( int i = 0; i < num_shell_entries; i++ ) { - int ic = fgetc(fp); - if ( ic == EOF ) + struct dirent* entry = shell_entries[i]; + const char* name = entry->d_name; + if ( !strcmp(name, ".") || !strcmp(name, "..") ) + continue; + size_t path_length = strlen(backends_dir_path) + 1 + strlen(name); + char* path = (char*) malloc(path_length + 1); + if ( !path ) + continue; + stpcpy(stpcpy(stpcpy(path, backends_dir_path), "/"), name); + FILE* fp = fopen(path, "r"); + free(path); + if ( !fp ) + continue; + size_t result_size = 0; + ssize_t result_length = getline(&result, &result_size, fp); + fclose(fp); + if ( result_length < 0 ) + continue; + if ( result_length && result[result_length-1] == '\n' ) + result[--result_length] = '\0'; + if ( !is_existing_shell(result) ) { - if ( ferror(fp) ) - error(64, errno, "fgetc %s", fpname); - if ( commandused ) - break; - return *exitexec = true, status; - } - c = (char) (unsigned char) ic; - } - else - { - ssize_t bytesread = read(fd, &c, sizeof(c)); - if ( bytesread < 0 && errno == EINTR ) - return status; - if ( bytesread < 0 ) - error(64, errno, "read %s", fpname); - if ( !bytesread && !interactive ) - return *exitexec = true, status; - if ( !bytesread && interactive ) - { - const char* init_pid_str = getenv("INIT_PID"); - if ( !init_pid_str ) - init_pid_str = "1"; - pid_t init_pid = (pid_t) strtoimax(init_pid_str, NULL, 10); - if ( !init_pid ) - init_pid = 1; - if ( getppid() == init_pid ) - { - printf("\nType exit to shutdown the system.\n"); - return status; - } - printf("exit\n"); - return *exitexec = true, status; - } - } - if ( !c ) - continue; - if ( c == '\n' && !escaped ) - break; - if ( commented ) - continue; - if ( c == '\\' && !escaped ) - { - escaped = true; - continue; - } - if ( !escaped ) - { - if ( c == '#' ) - { - commented = true; + free(result); + result = NULL; continue; } + break; } - escaped = false; - if ( commandused == commandlen ) - { - size_t newlen = commandlen * 2; - size_t newsize = (newlen+1) * sizeof(char); - char* newcommand = (char*) realloc(command, newsize); - if ( !newcommand ) - error(64, errno, "realloc"); - command = newcommand; - commandlen = newsize; - } - command[commandused++] = c; + for ( int i = 0; i < num_shell_entries; i++ ) + free(shell_entries[i]); + free(shell_entries); + if ( result ) + return result; } - command[commandused] = '\0'; - - if ( command[0] == '\0' ) - return status; - - if ( strchr(command, '=') && !strchr(command, ' ') && !strchr(command, '\t') ) - { - const char* key = command; - char* equal = strchr(command, '='); - *equal = '\0'; - const char* value = equal + 1; - if ( setenv(key, value, 1) < 0 ) - error(1, errno, "setenv"); - return status = 0; - } - - int argc = 0; - const size_t ARGV_MAX_LENGTH = 2048; - const char* argv[ARGV_MAX_LENGTH]; - argv[0] = NULL; - - bool lastwasspace = true; - escaped = false; - for ( size_t i = 0; i <= commandused; i++ ) - { - switch ( command[i] ) - { - case '\\': - if ( !escaped ) - { - memmove(command + i, command + i + 1, commandused+1 - (i-1)); - i--; - commandused--; - escaped = true; - break; - } - case '\0': - case ' ': - case '\t': - case '\n': - if ( !command[i] || !escaped ) - { - command[i] = 0; - lastwasspace = true; - break; - } - default: - escaped = false; - if ( lastwasspace ) - { - if ( argc == ARGV_MAX_LENGTH ) - { - fprintf(stderr, "argv max length of %zu entries hit!\n", - ARGV_MAX_LENGTH); - abort(); - } - argv[argc++] = command + i; - } - lastwasspace = false; - } - } - - if ( !argv[0] ) - return status; - - argv[argc] = NULL; - status = runcommandline(argv, exitexec, interactive); - if ( status && exit_on_error ) - *exitexec = true; - return status; -} - -void load_argv_variables(int argc, char* argv[]) -{ - char varname[sizeof(int) * 3]; - for ( int i = 0; i < argc; i++ ) - sprintf(varname, "%i", i), - setenv(varname, argv[i], 1); -} - -int run(FILE* fp, int argc, char* argv[], const char* name, bool interactive, - bool exit_on_error) -{ - load_argv_variables(argc, argv); - bool exitexec = false; - int exitstatus; - do - exitstatus = get_and_run_command(fp, name, interactive, exit_on_error, - &exitexec); - while ( !exitexec ); - return exitstatus; -} - -int run_interactive(int argc, char* argv[], bool exit_on_error) -{ - signal(SIGINT, on_sigint); - return run(stdin, argc, argv, "", true, exit_on_error); -} - -int run_stdin(int argc, char* argv[], bool exit_on_error) -{ - return run(stdin, argc, argv, "", false, exit_on_error); -} - -int run_string(int argc, char* argv[], const char* str, bool exit_on_error) -{ - FILE* fp = fmemopen((void*) str, strlen(str), "r"); - if ( !fp ) { error(0, errno, "fmemopen"); return 1; } - int ret = run(fp, argc, argv, "", false, exit_on_error); - fclose(fp); - return ret; -} - -int run_script(const char* path, int argc, char* argv[], bool exit_on_error) -{ - FILE* fp = fopen(path, "r"); - if ( !fp ) { error(0, errno, "%s", path); return 127; } - int ret = run(fp, argc, argv, path, false, exit_on_error); - fclose(fp); - return ret; + return NULL; } int main(int argc, char* argv[]) { - bool exit_on_error = false; - if ( 2 <= argc && !strcmp(argv[1], "-e") ) - { - exit_on_error = true; - argc--; - for ( int i = 1; i < argc; i++ ) - argv[i] = argv[i+1]; - argv[argc] = NULL; - } - char pidstr[3 * sizeof(pid_t)]; - char ppidstr[3 * sizeof(pid_t)]; - sprintf(pidstr, "%ji", (intmax_t) getpid()); - sprintf(ppidstr, "%ji", (intmax_t) getppid()); - setenv("SHELL", argv[0], 1); - setenv("$", pidstr, 1); - setenv("PPID", ppidstr, 1); - setenv("?", "0", 1); - updatepwd(); - if ( 2 <= argc && !strcmp(argv[1], "-c") ) - return run_string(argc, argv, argv[2], exit_on_error); - if ( 1 < argc ) - return run_script(argv[1], argc-1, argv+1, exit_on_error); - if ( isatty(0) ) - return run_interactive(argc, argv, exit_on_error); - return run_stdin(argc, argv, exit_on_error); + if ( argc == 1 && isatty(0) && isatty(1) ) + execvp("sortix-sh", argv); + + char* proper_shell = search_for_proper_shell(); + if ( proper_shell ) + execvp(proper_shell, argv); + free(proper_shell); + + execvp("sortix-sh", argv); + return 127; } diff --git a/utils/sortix-sh.cpp b/utils/sortix-sh.cpp new file mode 100644 index 00000000..f14b53c6 --- /dev/null +++ b/utils/sortix-sh.cpp @@ -0,0 +1,635 @@ +/******************************************************************************* + + Copyright(C) Jonas 'Sortie' Termansen 2011, 2012, 2013, 2014. + + This program is free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + more details. + + You should have received a copy of the GNU General Public License along with + this program. If not, see . + + sortix-sh.cpp + A hacky Sortix shell. + +*******************************************************************************/ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int status = 0; + +const char* getenv_safe(const char* name, const char* def = "") +{ + const char* ret = getenv(name); + return ret ? ret : def; +} + +void on_sigint(int /*signum*/) +{ + printf("^C\n"); +} + +void updatepwd() +{ + const size_t CWD_SIZE = 512; + char cwd[CWD_SIZE]; + const char* wd = getcwd(cwd, CWD_SIZE); + if ( !wd ) { wd = "?"; } + setenv("PWD", wd, 1); +} + +void updateenv() +{ + char str[128]; + struct winsize ws; + if ( tcgetwinsize(0, &ws) == 0 ) + { + sprintf(str, "%zu", ws.ws_col); + setenv("COLUMNS", str, 1); + sprintf(str, "%zu", ws.ws_row); + setenv("LINES", str, 1); + } +} + +bool matches_simple_pattern(const char* string, const char* pattern) +{ + size_t wildcard_index = strcspn(pattern, "*"); + if ( !pattern[wildcard_index] ) + return strcmp(string, pattern) == 0; + if ( pattern[0] == '*' && string[0] == '.' ) + return false; + size_t string_length = strlen(string); + size_t pattern_length = strlen(pattern); + size_t pattern_last = pattern_length - (wildcard_index + 1); + return strncmp(string, pattern, wildcard_index) == 0 && + strcmp(string + string_length - pattern_last, + pattern + wildcard_index + 1) == 0; +} + +int runcommandline(const char** tokens, bool* exitexec, bool interactive) +{ + int result = 127; + size_t cmdnext = 0; + size_t cmdstart; + size_t cmdend; + bool lastcmd = false; + int pipein = 0; + int pipeout = 1; + int pipeinnext = 0; + char** argv; + size_t cmdlen; + const char* execmode; + const char* outputfile; + pid_t childpid; + pid_t pgid = -1; + bool internal; + int internalresult; + size_t num_tokens = 0; + while ( tokens[num_tokens] ) + num_tokens++; +readcmd: + // Collect any pending zombie processes. + while ( 0 < waitpid(-1, NULL, WNOHANG) ); + + cmdstart = cmdnext; + for ( cmdend = cmdstart; true; cmdend++ ) + { + const char* token = tokens[cmdend]; + if ( !token || + strcmp(token, ";") == 0 || + strcmp(token, "&") == 0 || + strcmp(token, "|") == 0 || + strcmp(token, ">") == 0 || + strcmp(token, ">>") == 0 || + false ) + { + break; + } + } + + cmdlen = cmdend - cmdstart; + if ( !cmdlen ) { fprintf(stderr, "expected command\n"); goto out; } + execmode = tokens[cmdend]; + if ( !execmode ) { lastcmd = true; execmode = ";"; } + tokens[cmdend] = NULL; + + if ( strcmp(execmode, "|") == 0 ) + { + int pipes[2]; + if ( pipe(pipes) ) { perror("pipe"); goto out; } + if ( pipeout != 1 ) { close(pipeout); } pipeout = pipes[1]; + if ( pipeinnext != 0 ) { close(pipeinnext); } pipeinnext = pipes[0]; + } + + outputfile = NULL; + if ( strcmp(execmode, ">") == 0 || strcmp(execmode, ">>") == 0 ) + { + outputfile = tokens[cmdend+1]; + if ( !outputfile ) { fprintf(stderr, "expected filename\n"); goto out; } + const char* nexttok = tokens[cmdend+2]; + if ( nexttok ) { fprintf(stderr, "too many filenames\n"); goto out; } + } + + for ( size_t i = cmdstart; i < cmdend; i++ ) + { + const char* pattern = tokens[i]; + size_t wildcard_pos = strcspn(pattern, "*"); + if ( !pattern[wildcard_pos] ) + continue; + bool found_slash = false; + size_t last_slash = 0; + for ( size_t i = 0; i < wildcard_pos; i++ ) + if ( pattern[i] == '/' ) + last_slash = i, found_slash = true; + size_t match_from = found_slash ? last_slash + 1 : 0; + DIR* dir; + size_t pattern_prefix = 0; + if ( !found_slash ) + { + if ( !(dir = opendir(".")) ) + continue; + } + else + { + char* dirpath = strdup(pattern); + if ( !dirpath ) + continue; + dirpath[last_slash] = '\0'; + pattern_prefix = last_slash + 1; + dir = opendir(dirpath); + free(dirpath); + if ( !dir ) + continue; + } + size_t num_inserted = 0; + size_t last_inserted_index = i; + while ( struct dirent* entry = readdir(dir) ) + { + if ( !matches_simple_pattern(entry->d_name, pattern + match_from) ) + continue; + // TODO: Memory leak. + char* name = (char*) malloc(pattern_prefix + strlen(entry->d_name) + 1); + memcpy(name, pattern, pattern_prefix); + strcpy(name + pattern_prefix, entry->d_name); + if ( !name ) + continue; + if ( num_inserted ) + { + // TODO: Reckless modification of the tokens array. + for ( size_t n = num_tokens; n != last_inserted_index; n-- ) + tokens[n+1] = tokens[n]; + num_tokens++; + cmdend++; + } + // TODO: Reckless modification of the tokens array. + tokens[last_inserted_index = i + num_inserted++] = name; + } + closedir(dir); + } + + cmdnext = cmdend + 1; + argv = (char**) (tokens + cmdstart); + + updateenv(); + char statusstr[32]; + sprintf(statusstr, "%i", status); + setenv("?", statusstr, 1); + + for ( char** argp = argv; *argp; argp++ ) + { + char* arg = *argp; + if ( arg[0] != '$' ) + continue; + arg = getenv(arg+1); + if ( !arg ) + arg = (char*) ""; + *argp = arg; + } + + internal = false; + internalresult = 0; + if ( strcmp(argv[0], "cd") == 0 ) + { + internal = true; + const char* newdir = getenv_safe("HOME", "/"); + if ( argv[1] ) { newdir = argv[1]; } + if ( chdir(newdir) ) + { + error(0, errno, "cd: %s", newdir); + internalresult = 1; + } + updatepwd(); + } + if ( strcmp(argv[0], "exit") == 0 ) + { + int exitcode = argv[1] ? atoi(argv[1]) : 0; + *exitexec = true; + return exitcode; + } + if ( strcmp(argv[0], "unset") == 0 ) + { + internal = true; + unsetenv(argv[1] ? argv[1] : ""); + } + if ( strcmp(argv[0], "clearenv") == 0 ) + { + internal = true; + clearenv(); + } + + childpid = internal ? getpid() : fork(); + if ( childpid < 0 ) { perror("fork"); goto out; } + if ( childpid ) + { + if ( !internal ) + { + if ( pgid == -1 ) + pgid = childpid; + setpgid(childpid, pgid); + } + + if ( pipein != 0 ) { close(pipein); pipein = 0; } + if ( pipeout != 1 ) { close(pipeout); pipeout = 1; } + if ( pipeinnext != 0 ) { pipein = pipeinnext; pipeinnext = 0; } + + if ( strcmp(execmode, "&") == 0 && !tokens[cmdnext] ) + { + result = 0; goto out; + } + + if ( strcmp(execmode, "&") == 0 ) + pgid = -1; + + if ( strcmp(execmode, "&") == 0 || strcmp(execmode, "|") == 0 ) + { + goto readcmd; + } + + status = internalresult; + int exitstatus; + tcsetpgrp(0, pgid); + if ( !internal && waitpid(childpid, &exitstatus, 0) < 0 ) + { + perror("waitpid"); + return 127; + } + tcsetpgrp(0, getpgid(0)); + + // TODO: HACK: Most signals can't kill processes yet. + if ( WEXITSTATUS(exitstatus) == 128 + SIGINT ) + printf("^C\n"); + if ( WTERMSIG(status) == SIGKILL ) + printf("Killed\n"); + + status = WEXITSTATUS(exitstatus); + + if ( strcmp(execmode, ";") == 0 && tokens[cmdnext] && !lastcmd ) + { + goto readcmd; + } + + result = status; + goto out; + } + + setpgid(0, pgid != -1 ? pgid : 0); + + if ( pipeinnext != 0 ) { close(pipeinnext); } + + if ( pipein != 0 ) + { + close(0); + dup(pipein); + close(pipein); + } + + if ( pipeout != 1 ) + { + close(1); + dup(pipeout); + close(pipeout); + } + + if ( outputfile ) + { + close(1); + // TODO: Is this logic right or wrong? + int flags = O_CREAT | O_WRONLY; + if ( strcmp(execmode, ">") == 0 ) + flags |= O_TRUNC; + if ( strcmp(execmode, ">>") == 0 ) + flags |= O_APPEND; + if ( open(outputfile, flags, 0666) < 0 ) + { + error(127, errno, "%s", outputfile); + } + } + + execvp(argv[0], argv); + if ( interactive && errno == ENOENT ) + { + int errno_saved = errno; + execlp("command-not-found", "command-not-found", argv[0], NULL); + errno = errno_saved; + } + error(127, errno, "%s", argv[0]); + return 127; + +out: + if ( pipein != 0 ) { close(pipein); } + if ( pipeout != 1 ) { close(pipeout); } + if ( pipeinnext != 0 ) { close(pipeout); } + return result; +} + +int get_and_run_command(FILE* fp, const char* fpname, bool interactive, + bool exit_on_error, bool* exitexec) +{ + int fd = fileno(fp); + + if ( interactive ) + { + unsigned termmode = TERMMODE_UNICODE + | TERMMODE_SIGNAL + | TERMMODE_UTF8 + | TERMMODE_LINEBUFFER + | TERMMODE_ECHO; + settermmode(fd, termmode); + const char* print_username = getlogin(); + if ( !print_username ) + print_username = getuid() == 0 ? "root" : "?"; + const char* print_hostname = getenv_safe("HOSTNAME", "sortix"); + const char* print_dir = getenv_safe("PWD", "?"); + const char* home_dir = getenv_safe("HOME", ""); + size_t home_dir_len = strlen(home_dir); + printf("\e[32m"); + printf("%s", print_username); + printf("@"); + printf("%s", print_hostname); + printf(" "); + printf("\e[36m"); + if ( home_dir_len && strncmp(print_dir, home_dir, home_dir_len) == 0 ) + printf("~%s", print_dir + home_dir_len); + else + printf("%s", print_dir); + printf(" "); + printf("#"); + printf("\e[37m "); + fflush(stdout); + } + + static char* command = NULL; + static size_t commandlen = 1024; + if ( !command ) + commandlen = 1024, + command = (char*) malloc((commandlen+1) * sizeof(char)); + if ( !command ) + error(64, errno, "malloc"); + + size_t commandused = 0; + bool commented = false; + bool escaped = false; + while (true) + { + char c; + if ( fd < 0 ) + { + int ic = fgetc(fp); + if ( ic == EOF ) + { + if ( ferror(fp) ) + error(64, errno, "fgetc %s", fpname); + if ( commandused ) + break; + return *exitexec = true, status; + } + c = (char) (unsigned char) ic; + } + else + { + ssize_t bytesread = read(fd, &c, sizeof(c)); + if ( bytesread < 0 && errno == EINTR ) + return status; + if ( bytesread < 0 ) + error(64, errno, "read %s", fpname); + if ( !bytesread && !interactive ) + return *exitexec = true, status; + if ( !bytesread && interactive ) + { + const char* init_pid_str = getenv("INIT_PID"); + if ( !init_pid_str ) + init_pid_str = "1"; + pid_t init_pid = (pid_t) strtoimax(init_pid_str, NULL, 10); + if ( !init_pid ) + init_pid = 1; + if ( getppid() == init_pid ) + { + printf("\nType exit to shutdown the system.\n"); + return status; + } + printf("exit\n"); + return *exitexec = true, status; + } + } + if ( !c ) + continue; + if ( c == '\n' && !escaped ) + break; + if ( commented ) + continue; + if ( c == '\\' && !escaped ) + { + escaped = true; + continue; + } + if ( !escaped ) + { + if ( c == '#' ) + { + commented = true; + continue; + } + } + escaped = false; + if ( commandused == commandlen ) + { + size_t newlen = commandlen * 2; + size_t newsize = (newlen+1) * sizeof(char); + char* newcommand = (char*) realloc(command, newsize); + if ( !newcommand ) + error(64, errno, "realloc"); + command = newcommand; + commandlen = newsize; + } + command[commandused++] = c; + } + + command[commandused] = '\0'; + + if ( command[0] == '\0' ) + return status; + + if ( strchr(command, '=') && !strchr(command, ' ') && !strchr(command, '\t') ) + { + const char* key = command; + char* equal = strchr(command, '='); + *equal = '\0'; + const char* value = equal + 1; + if ( setenv(key, value, 1) < 0 ) + error(1, errno, "setenv"); + return status = 0; + } + + int argc = 0; + const size_t ARGV_MAX_LENGTH = 2048; + const char* argv[ARGV_MAX_LENGTH]; + argv[0] = NULL; + + bool lastwasspace = true; + escaped = false; + for ( size_t i = 0; i <= commandused; i++ ) + { + switch ( command[i] ) + { + case '\\': + if ( !escaped ) + { + memmove(command + i, command + i + 1, commandused+1 - (i-1)); + i--; + commandused--; + escaped = true; + break; + } + case '\0': + case ' ': + case '\t': + case '\n': + if ( !command[i] || !escaped ) + { + command[i] = 0; + lastwasspace = true; + break; + } + default: + escaped = false; + if ( lastwasspace ) + { + if ( argc == ARGV_MAX_LENGTH ) + { + fprintf(stderr, "argv max length of %zu entries hit!\n", + ARGV_MAX_LENGTH); + abort(); + } + argv[argc++] = command + i; + } + lastwasspace = false; + } + } + + if ( !argv[0] ) + return status; + + argv[argc] = NULL; + status = runcommandline(argv, exitexec, interactive); + if ( status && exit_on_error ) + *exitexec = true; + return status; +} + +void load_argv_variables(int argc, char* argv[]) +{ + char varname[sizeof(int) * 3]; + for ( int i = 0; i < argc; i++ ) + sprintf(varname, "%i", i), + setenv(varname, argv[i], 1); +} + +int run(FILE* fp, int argc, char* argv[], const char* name, bool interactive, + bool exit_on_error) +{ + load_argv_variables(argc, argv); + bool exitexec = false; + int exitstatus; + do + exitstatus = get_and_run_command(fp, name, interactive, exit_on_error, + &exitexec); + while ( !exitexec ); + return exitstatus; +} + +int run_interactive(int argc, char* argv[], bool exit_on_error) +{ + signal(SIGINT, on_sigint); + return run(stdin, argc, argv, "", true, exit_on_error); +} + +int run_stdin(int argc, char* argv[], bool exit_on_error) +{ + return run(stdin, argc, argv, "", false, exit_on_error); +} + +int run_string(int argc, char* argv[], const char* str, bool exit_on_error) +{ + FILE* fp = fmemopen((void*) str, strlen(str), "r"); + if ( !fp ) { error(0, errno, "fmemopen"); return 1; } + int ret = run(fp, argc, argv, "", false, exit_on_error); + fclose(fp); + return ret; +} + +int run_script(const char* path, int argc, char* argv[], bool exit_on_error) +{ + FILE* fp = fopen(path, "r"); + if ( !fp ) { error(0, errno, "%s", path); return 127; } + int ret = run(fp, argc, argv, path, false, exit_on_error); + fclose(fp); + return ret; +} + +int main(int argc, char* argv[]) +{ + bool exit_on_error = false; + if ( 2 <= argc && !strcmp(argv[1], "-e") ) + { + exit_on_error = true; + argc--; + for ( int i = 1; i < argc; i++ ) + argv[i] = argv[i+1]; + argv[argc] = NULL; + } + char pidstr[3 * sizeof(pid_t)]; + char ppidstr[3 * sizeof(pid_t)]; + sprintf(pidstr, "%ji", (intmax_t) getpid()); + sprintf(ppidstr, "%ji", (intmax_t) getppid()); + setenv("SHELL", argv[0], 1); + setenv("$", pidstr, 1); + setenv("PPID", ppidstr, 1); + setenv("?", "0", 1); + updatepwd(); + if ( 2 <= argc && !strcmp(argv[1], "-c") ) + return run_string(argc, argv, argv[2], exit_on_error); + if ( 1 < argc ) + return run_script(argv[1], argc-1, argv+1, exit_on_error); + if ( isatty(0) ) + return run_interactive(argc, argv, exit_on_error); + return run_stdin(argc, argv, exit_on_error); +}