From 74abc79434fe895d0ca863e4d1d6c5c16b54f296 Mon Sep 17 00:00:00 2001 From: Niklas Halle Date: Mon, 1 Jun 2020 15:46:28 +0200 Subject: trying to connect server and shell, weird --- 02_exercise/CMakeLists.txt | 2 +- 03_exercise/.srv_pid | 1 + 03_exercise/cli/client | Bin 22904 -> 23056 bytes 03_exercise/cli/client.c | 14 ++-- 03_exercise/srv/builtins.c | 120 +++++++++++++++++++++++++++++ 03_exercise/srv/builtins.h | 7 ++ 03_exercise/srv/process.c | 169 +++++++++++++++++++++++++++++++++++++++++ 03_exercise/srv/process.h | 30 ++++++++ 03_exercise/srv/prompt_utils.c | 90 ++++++++++++++++++++++ 03_exercise/srv/prompt_utils.h | 10 +++ 03_exercise/srv/server | Bin 26256 -> 58760 bytes 03_exercise/srv/server.c | 12 ++- 03_exercise/srv/shell.c | 121 +++++++++++++++++++++++++++++ 03_exercise/srv/shell.h | 6 ++ 14 files changed, 573 insertions(+), 9 deletions(-) create mode 100644 03_exercise/.srv_pid create mode 100644 03_exercise/srv/builtins.c create mode 100644 03_exercise/srv/builtins.h create mode 100644 03_exercise/srv/process.c create mode 100644 03_exercise/srv/process.h create mode 100644 03_exercise/srv/prompt_utils.c create mode 100644 03_exercise/srv/prompt_utils.h create mode 100644 03_exercise/srv/shell.c create mode 100644 03_exercise/srv/shell.h diff --git a/02_exercise/CMakeLists.txt b/02_exercise/CMakeLists.txt index e47f0ee..82df5d5 100644 --- a/02_exercise/CMakeLists.txt +++ b/02_exercise/CMakeLists.txt @@ -20,7 +20,7 @@ add_sanitizers(shell) add_library(builtin builtins.c) target_compile_options(builtin INTERFACE ${PROJECT_WARNINGS}) -add_library(array array.c) +add_library(array array.c ../03_exercise/srv/shell.h) add_library(prompt_utils prompt_utils.c) diff --git a/03_exercise/.srv_pid b/03_exercise/.srv_pid new file mode 100644 index 0000000..4e6d37a --- /dev/null +++ b/03_exercise/.srv_pid @@ -0,0 +1 @@ +1768723 diff --git a/03_exercise/cli/client b/03_exercise/cli/client index eb8470f..53ba0d8 100755 Binary files a/03_exercise/cli/client and b/03_exercise/cli/client differ diff --git a/03_exercise/cli/client.c b/03_exercise/cli/client.c index a98942d..527c67c 100644 --- a/03_exercise/cli/client.c +++ b/03_exercise/cli/client.c @@ -10,7 +10,7 @@ #define PORT 9000 #define HOST "127.0.0.1" -#define BUF_SIZE 256 +#define BUF_SIZE 1024 static inline void die(const char *msg) { perror(msg); @@ -51,20 +51,24 @@ int main() { line[length - 1] = '\0'; // cut the line feed - if (strcmp(line, "exit") == 0) { - break; - } - strncpy(buf, line, strlen(line)); if (write(cfd, buf, BUF_SIZE) < 0) die("Could not send message"); + if (strcmp(line, "exit") == 0) { + free(line); + line = NULL; + break; + } + if (read(cfd, buf, BUF_SIZE) < 0) die("Could not receive message"); printf("%s\n", buf); memset(buf, 0, BUF_SIZE); + free(line); + line = NULL; } close(cfd); diff --git a/03_exercise/srv/builtins.c b/03_exercise/srv/builtins.c new file mode 100644 index 0000000..f5497a3 --- /dev/null +++ b/03_exercise/srv/builtins.c @@ -0,0 +1,120 @@ +#include +#include +#include +#include +#include +#include + + +#include "array.h" +#include "builtins.h" + +void handle(int sig) { + /* Do nothing. */ +} + +typedef struct { + pid_t pid; + bool done; +} hack_struct; + +void builtin_wait(process p, bool bind) { + struct sigaction handler; + handler.sa_handler = handle; + sigemptyset(&handler.sa_mask); + handler.sa_flags = 0; + + hack_struct *pcs; + arrayInit(pcs); + + size_t i = 1; + bool error = false; + while (!error && p.argv[i] != NULL) { + char *end; + hack_struct pc; + pc.pid = strtol(p.argv[i], &end, 10); + pc.done = false; + if (*end != '\0') { + fprintf(stderr, "Not a valid pid: %s\n", p.argv[i]); + error = true; + break; + } + arrayPush(pcs) = pc; + ++i; + } + + if (error || arrayLen(pcs) == 0) { + printf("plz giv pid\n"); + arrayRelease(pcs); + return; + } + + int options = WUNTRACED; + if (!bind) { + options |= WNOHANG; + } + + int done_count = 0; + while (done_count != arrayLen(pcs)) { + for (int j = 0; j < arrayLen(pcs); ++j) { + int status; + hack_struct *current_pc = &j[pcs]; + if (current_pc->done) { + continue; + } + + if (bind) { + printf("Resuming %ld...\n", (long) current_pc->pid); + }/* else { + printf("Waiting for %ld...\n", (long) current_pc->pid); + }*/ + // install signal handler without SA_RESTART, so waitpid gets interrupted + sigaction(SIGINT, &handler, NULL); + pid_t res; + if ((res = waitpid(current_pc->pid, &status, options)) < 0) { + if (EINTR == errno) { + // cancelled by ctrl-c + if (bind) { + current_pc->done = true; + ++done_count; + kill(current_pc->pid, SIGKILL); + waitpid(current_pc->pid, &status, options); + printf("Killed [%ld]\n", (long) current_pc->pid); + } else { + kill(current_pc->pid, SIGCONT); + } + } else { + perror("Could not wait for process"); + ++done_count; + current_pc->done = true; + } + } else if (res == 0) { + // wait terminated because nohang + } else { + current_pc->done = true; + ++done_count; + if (bind) { + printf("[%i] ", WEXITSTATUS(status)); + } else { + printf("[%i] TERMINATED\n", current_pc->pid); + if (WIFEXITED(status)) { + printf("[%i] exited normally with status: %i\n", current_pc->pid, + WEXITSTATUS(status)); + } else if (WIFSTOPPED(status)) { + printf("[%i] was stopped by signal: %s\n", current_pc->pid, + strsignal(WSTOPSIG(status))); + } else if (WIFSIGNALED(status)) { + printf("[%i] was terminated by signal: %s\n", current_pc->pid, + strsignal(WTERMSIG(status))); + } else { + printf("[%i] exited (not sure why), exit status: %i\n", current_pc->pid, + WEXITSTATUS(status)); + } + } + } + signal(SIGINT, SIG_IGN); + } + } + + arrayRelease(pcs); +} diff --git a/03_exercise/srv/builtins.h b/03_exercise/srv/builtins.h new file mode 100644 index 0000000..43571f7 --- /dev/null +++ b/03_exercise/srv/builtins.h @@ -0,0 +1,7 @@ +#ifndef BUILTINS_H +#define BUILTINS_H + +#include "process.h" +void builtin_wait(process p, bool bind); + +#endif \ No newline at end of file diff --git a/03_exercise/srv/process.c b/03_exercise/srv/process.c new file mode 100644 index 0000000..c5be8c3 --- /dev/null +++ b/03_exercise/srv/process.c @@ -0,0 +1,169 @@ +#include +#include +#include +#include +#include + +#include "array.h" +#include "process.h" + + +// given a substring of a line tries to parse out as much information as possible +int parse_command(char const *line, char const *end, process *p) { + char *part; + char **local_parts; + if (arrayInit(part) != 0 || arrayInit(local_parts) != 0) { + fprintf(stderr, "Failed to prepare new part / parts array whilst parsing line"); + return -1; + } + + for (size_t i = 0; line + i < end; ++i) { + char c; + switch (c = line[i]) { + default: + arrayPush(part) = c; + break; + case ' ': + case '\t': + case '\0': + if (arrayLen(part) == 0) + continue; + + arrayPush(part) = '\0'; + arrayPush(local_parts) = part; + arrayInit(part); + } + } + + if (arrayLen(part) == 0) { + arrayRelease(part); + } else { + arrayPush(part) = '\0'; + arrayPush(local_parts) = part; + } + + arrayPush(local_parts) = NULL; + p->argc = arrayLen(local_parts) - 1; + p->argv = local_parts; + + return 0; +} + +int parse_line(char const *const line, process **processes) { + // splits the line at | and then parses the commands + int ret_code; + + if (arrayInit(*processes) != 0) { + perror("Failed to initialize processes array"); + return -1; + } + + bool done = false; + char const *cursor = line; + while (!done) { + process p = {.argv = NULL, .argc = 0, .in_fd = 0, .out_fd = 0, .pid = 0, .blocking = true}; + char const *end = strchr(cursor, '|'); + + if (end == NULL) { + done = true; + end = line + strlen(line); + } + + if ((ret_code = parse_command(cursor, end, &p)) != 0) { + return ret_code; + } + + arrayPush(*processes) = p; + cursor = end + 1; + } + + size_t p_len = arrayLen(*processes); + process *last = *processes + (p_len - 1); + + // linking up all processes as we currently only have pipes for multiple commands + for (size_t i = 0; i < p_len - 1; ++i) { + int fds[2]; + if (pipe(fds) != 0) { + perror("Failed to create pipe"); + return -1; + } + (*processes)[i].out_fd = fds[1]; + (*processes)[i + 1].in_fd = fds[0]; + } + + // setting all processes to non blocking when + if (strcmp(last->argv[last->argc - 1], "&") == 0) { + arrayPop(last->argv) = NULL; + last->argc = last->argc - 1; + last->argv[last->argc] = NULL; + + for (size_t i = 0; i < p_len; ++i) { + (*processes)[i].blocking = false; + } + } + + return ret_code; +} + +int exec_command(process p) { + int status; + + if ((p.pid = fork()) == 0) { + if (p.in_fd != 0) { + dup2(p.in_fd, 0); + close(p.in_fd); + } + if(p.in_fd == 0 && !p.blocking) { + close(0); + } + + if (p.out_fd != 0) { + dup2(p.out_fd, 1); + close(p.out_fd); + } + + execvp(p.argv[0], p.argv); + fprintf(stderr, "command not found: \"%s\"\n", p.argv[0]); + fflush(stderr); + exit(-1); + } + + if (p.pid < 0) { + fprintf(stderr, "no fork\n"); + exit(-2); + } + + if (p.in_fd != 0) { + close(p.in_fd); + } + + if (p.out_fd != 0) { + close(p.out_fd); + } + + if (!p.blocking) + printf("[%i]\n", p.pid); + + if (waitpid(p.pid, &status, p.blocking ? 0 : WNOHANG) < 0) { + fprintf(stderr, "wait error'ed out\n"); + } + + return WEXITSTATUS(status); +} + +void free_processes(process **pr) { + process *processes = *pr; + while (!arrayIsEmpty(processes)) { + process p = arrayPop(processes); + while (!arrayIsEmpty(p.argv)) { + char *tmp = arrayTop(p.argv); + if (tmp) { + arrayRelease(tmp); + } + arrayPop(p.argv) = NULL; + } + arrayRelease(p.argv); + } + arrayRelease(processes); + *pr = NULL; +} \ No newline at end of file diff --git a/03_exercise/srv/process.h b/03_exercise/srv/process.h new file mode 100644 index 0000000..6e9208c --- /dev/null +++ b/03_exercise/srv/process.h @@ -0,0 +1,30 @@ +#ifndef SHELL_PROCESS_H +#define SHELL_PROCESS_H + +#include +#include + +typedef struct { + char **argv; + size_t argc; + int in_fd; + int out_fd; + int pid; + bool blocking; +} process; + +/* + * Parses the given line and creates an array of processes at *processes + * Expects tail -F file | grep panic & to mean that both processes should + * run in the background + */ +int parse_line(char const *line, process **processes); + +/* + * returns the return code of the executed program + */ +int exec_command(process p); + +void free_processes(process **processes); + +#endif // SHELL_PROCESS_H diff --git a/03_exercise/srv/prompt_utils.c b/03_exercise/srv/prompt_utils.c new file mode 100644 index 0000000..751a81b --- /dev/null +++ b/03_exercise/srv/prompt_utils.c @@ -0,0 +1,90 @@ +#include +#include + +#include "array.h" +#include "prompt_utils.h" + +char const *relative_path(char const *const from_dir, char const *const to_dir) { + // easiest cases first + { + if (strcmp(from_dir, to_dir) == 0) { + char *return_value = malloc(2); + strcpy(return_value, "."); + return return_value; + } + } + { + if (strcmp("/", from_dir) == 0) { + char *return_value = malloc(strlen(to_dir) + 2); + return_value[0] = '.'; + memcpy(return_value + 1, to_dir, strlen(to_dir) + 1); + return return_value; + } + } + + // splitting path into pieces so we can do strcmp on each of them + size_t const *const from_dir_indices = get_separator_indices(from_dir, '/'); + size_t const *const to_dir_indices = get_separator_indices(to_dir, '/'); + + size_t from_dir_len = arrayLen(from_dir_indices); + size_t to_dir_len = arrayLen(to_dir_indices); + + // finding the longest common substring + size_t array_len = from_dir_len < to_dir_len ? from_dir_len : to_dir_len; + size_t i = 0; + size_t common_position = 0; + for (; i < array_len - 1; ++i) { + if (from_dir_indices[i + 1] != to_dir_indices[i + 1]) { + break; + } + size_t index = from_dir_indices[i]; + size_t count = from_dir_indices[i + 1] - from_dir_indices[i]; + if (strncmp(from_dir + index, to_dir + index, count) != 0) { + break; + } + common_position = from_dir_indices[i + 1]; + } + + size_t levels_up = from_dir_len - i - 1; + char *return_value; + if (levels_up == 0) { + // equal dirs for whole length of path and not equal => subdir + size_t length = strlen(to_dir + common_position) + strlen("./") + 1; + return_value = malloc(length * sizeof(char)); + strcpy(return_value, "."); + strcat(return_value, to_dir + common_position); + } else { + char const *const go_up = "/.."; + size_t length = strlen(to_dir + common_position) + strlen("..") + + strlen(go_up) * (levels_up - 1) + 1; + return_value = malloc(length * sizeof(char)); + strcpy(return_value, ".."); + for (size_t j = 0; j < levels_up - 1; ++j) { + strcat(return_value, go_up); + } + if (strcmp("/", to_dir) != 0) { + strcat(return_value, to_dir + common_position); + } + } + + arrayRelease((void *)from_dir_indices); + arrayRelease((void *)to_dir_indices); + + return return_value; +} + +size_t *get_separator_indices(char const *const text, char seperator) { + size_t *indices; + arrayInit(indices); + char const *current = text; + if (strchr(current, seperator) == NULL) { + arrayRelease(indices); + return NULL; + } + while ((current = strchr(current, seperator)) != NULL) { + arrayPush(indices) = (size_t) (current - text); + ++current; + } + arrayPush(indices) = strlen(text); + return indices; +} diff --git a/03_exercise/srv/prompt_utils.h b/03_exercise/srv/prompt_utils.h new file mode 100644 index 0000000..36d065d --- /dev/null +++ b/03_exercise/srv/prompt_utils.h @@ -0,0 +1,10 @@ +#ifndef PROMPT_UTILS_H_INCLUDED +#define PROMPT_UTILS_H_INCLUDED + +// returns the relative path to get from the `from_dir` to the `to_dir` +char const *relative_path(char const *from_dir, char const *to_dir); + +// returns the position of each occurrence of the separator +size_t *get_separator_indices(char const *text, char separator); + +#endif // PROMPT_UTILS_H_INCLUDED diff --git a/03_exercise/srv/server b/03_exercise/srv/server index 2402d7b..232330a 100755 Binary files a/03_exercise/srv/server and b/03_exercise/srv/server differ diff --git a/03_exercise/srv/server.c b/03_exercise/srv/server.c index 818036a..5c23196 100644 --- a/03_exercise/srv/server.c +++ b/03_exercise/srv/server.c @@ -7,6 +7,8 @@ #include #include +#include "shell.h" + #define PORT 9000 #define BUF_SIZE 256 @@ -21,9 +23,9 @@ int main() { int sockopt = 1; socklen_t sad_sz = sizeof(struct sockaddr_in); int sfd, cfd; - ssize_t bytes; + /*ssize_t bytes; char in_buf[BUF_SIZE]; - char out_buf[BUF_SIZE]; + char out_buf[BUF_SIZE];*/ srv_addr.sin_family = AF_INET; srv_addr.sin_port = htons(PORT); @@ -46,6 +48,8 @@ int main() { printf("srv: connected: %s\n", inet_ntoa(cli_addr.sin_addr)); + int status = shell(cfd); + /* while ((bytes = read(cfd, in_buf, BUF_SIZE)) != 0) { if (bytes < 0) die("Couldn't receive message"); @@ -69,10 +73,12 @@ int main() { memset(in_buf, 0, BUF_SIZE); memset(out_buf, 0, BUF_SIZE); - } + }*/ printf("srv: closing down\n"); close(cfd); close(sfd); + + return status; } diff --git a/03_exercise/srv/shell.c b/03_exercise/srv/shell.c new file mode 100644 index 0000000..a32fc5f --- /dev/null +++ b/03_exercise/srv/shell.c @@ -0,0 +1,121 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "array.h" +#include "process.h" +#include "prompt_utils.h" +#include "builtins.h" + +#define BUF_SIZE 256 + +process *processes; + +const char * const get_current_dir_name(); + +void signal_handler(int signal) { + if (signal == SIGINT) { + for (size_t i = 0; i < arrayLen(processes); ++i) { + pid_t pid = processes[i].pid; + if (pid != 0) { + kill(pid, SIGINT); + } + } + } +} + +int shell(int in_fd) { + setvbuf(stderr, NULL, _IONBF, 0); + setvbuf(stdout, NULL, _IONBF, 0); + + dup2(in_fd, 1); + dup2(in_fd, 2); + + // I don't think the shell should exit on SIG_TERM + if (signal(SIGINT, SIG_IGN) == SIG_ERR) { + perror("Couldn't ignore sigterm"); + exit(errno); + } + + printf("Welcome! Available built-ins are:\n" + "cd: `cd ` - if no path is given, return to the current dir\n" + "wait: `wait pid1 ... pidN` - wait on the processes and report their exit conditions\n" + "fg: `fg pid` - pulls a process from the background back in the foreground\n" + "\n" + "You can put processes in the background using `&`\n" + "And with `|` you can pipe the output from one process to the input of another\n" + ); + + char const *const original_wd = get_current_dir_name(); + + bool done = false; + while (!done) { + char line[BUF_SIZE]; + + for (int i = 0; i < BUF_SIZE; ++i) { + read(in_fd, line + i, 1); + if (line[i] == '\n') { + line[i] = 0; + break; + } + } + + if (strspn(line, " \n\t") == strlen(line)) { + // skip empty lines - empty being just spaces or tabs + continue; + } + + processes = NULL; + parse_line(line, &processes); + + if (strcmp(processes[0].argv[0], "cd") == 0) { + if (arrayLen(processes) != 1) { + perror("Can't chain cd with other processes"); + } + int ret; + switch (arrayLen(processes[0].argv)) { + case 3: + ret = chdir(processes[0].argv[1]); + break; + case 2: + ret = chdir(original_wd); + break; + default: + fprintf(stderr, "usage: cd \n"); + fflush(stderr); + ret = -1; + } + + if (ret) + printf("[%i] ", ret); + + } else if (strcmp(processes[0].argv[0], "exit") == 0) { + done = true; + } else if (strcmp(processes[0].argv[0], "wait") == 0) { + builtin_wait(processes[0], false); + } else if (strcmp(processes[0].argv[0], "fg") == 0) { + // same behaviour as wait, just bind to shell again (i.e. terminate process on ctrl-c) + builtin_wait(processes[0], true); + } else { + if (arrayLen(processes) != 0 && processes[arrayLen(processes) - 1].blocking) { + signal(SIGINT, signal_handler); + } + for (size_t i = 0; i < arrayLen(processes); ++i) { + int ret = exec_command(processes[i]); + if (ret) + printf("[%i] ", ret); + } + signal(SIGINT, SIG_IGN); + } + + free_processes(&processes); + } + + free((void *) original_wd); + + return 0; +} diff --git a/03_exercise/srv/shell.h b/03_exercise/srv/shell.h new file mode 100644 index 0000000..3c79844 --- /dev/null +++ b/03_exercise/srv/shell.h @@ -0,0 +1,6 @@ +#ifndef BETRIEBSYSTEME_SHELL_H +#define BETRIEBSYSTEME_SHELL_H + +int shell(int in_fd); + +#endif //BETRIEBSYSTEME_SHELL_H -- cgit v1.2.3-54-g00ecf