diff --git a/6-RShell/bats/assignment_tests.sh b/6-RShell/bats/assignment_tests.sh new file mode 100644 index 0000000000000000000000000000000000000000..5246ee441c5e049315e4d3191569dd6314c40bb9 --- /dev/null +++ b/6-RShell/bats/assignment_tests.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bats + +############################ DO NOT EDIT THIS FILE ##################################### +# File: assignement_tests.sh +# +# DO NOT EDIT THIS FILE +# +# Add/Edit Student tests in student_tests.sh +# +# All tests in this file must pass - it is used as part of grading! +######################################################################################## + +@test "Pipes" { + run "./dsh" <<EOF +ls | grep dshlib.c +EOF + + # Strip all whitespace (spaces, tabs, newlines) from the output + stripped_output=$(echo "$output" | tr -d '[:space:]') + + # Expected output with all whitespace removed for easier matching + expected_output="dshlib.clocalmodedsh4>dsh4>cmdloopreturned0" + + # These echo commands will help with debugging and will only print + #if the test fails + echo "Captured stdout:" + echo "Output: $output" + echo "Exit Status: $status" + echo "${stripped_output} -> ${expected_output}" + + # Check exact match + [ "$stripped_output" = "$expected_output" ] + + # Assertions + [ "$status" -eq 0 ] +} diff --git a/6-RShell/bats/student_tests.sh b/6-RShell/bats/student_tests.sh new file mode 100644 index 0000000000000000000000000000000000000000..638bc341446f7580a80c2aff52971b8023407ea8 --- /dev/null +++ b/6-RShell/bats/student_tests.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bats + +# File: student_tests.sh +# +# Create your unit tests suit in this file + +@test "Example: check ls runs without errors" { + run ./dsh <<EOF +ls +EOF + + # Assertions + [ "$status" -eq 0 ] +} diff --git a/6-RShell/dragon.txt b/6-RShell/dragon.txt new file mode 100644 index 0000000000000000000000000000000000000000..a9177fd41bb14d2fa0fd439742e74d3f61f60d2d --- /dev/null +++ b/6-RShell/dragon.txt @@ -0,0 +1,38 @@ + @%%%% + %%%%%% + %%%%%% + % %%%%%%% @ + %%%%%%%%%% %%%%%%% + %%%%%%% %%%%@ %%%%%%%%%%%%@ %%%%%% @%%%% + %%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%% + %%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%% %%%%%%%%%%%%%%% + %%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%% %%% + %%%%%%%%%%%%%%%%%%%%%%%%%%%%@ @%%%%%%%%%%%%%%%%%% %% + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%% + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%@%%%%%%@ + %%%%%%%%@ %%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%% %% + %%%%%%%%%%%%% %%@%%%%%%%%%%%% %%%%%%%%%%% %%%%%%%%%%%% @% + %%%%%%%%%% %%% %%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%% + %%%%%%%%% % %%%%%%%%%%%%% %%%%%%%%%%%%@%%%%%%%%%%% +%%%%%%%%%@ % %%%%%%%%%%%%% @%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%@ %%@%%%%%%%%%%%% @%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%@ %%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%% %%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%% +%%%%%%%%%@ @%%%%%%%%%%%%%% %%%%%%%%%%%%@ %%%% %%%%%%%%%%%%%%%%% %%%%%%%% +%%%%%%%%%% %%%%%%%%%%%%%%%%% %%%%%%%%%%%%% %%%%%%%%%%%%%%%%%% %%%%%%%%% +%%%%%%%%%@%%@ %%%%%%%%%%%%%%%%@ %%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%% %% + %%%%%%%%%% % %%%%%%%%%%%%%%@ %%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%% %% + %%%%%%%%%%%% @ %%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% + %%%%%%%%%%%%% %% % %@ %%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% + %%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%% @%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%% + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% + @%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%% + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%% %%%%%%% + %%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%% @%%%%%%%%% + %%%%%%%%%%%%%%%%%%%% @%@% @%%%%%%%%%%%%%%%%%% %%% + %%%%%%%%%%%%%%% %%%%%%%%%% %%%%%%%%%%%%%%% % + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%% + %%%%%%%%%%%%%%%%%%%%%%%%%% %%%% %%% %%%%%%%%%% %%%@ + %%%%%%%%%%%%%%%%%%% %%%%%% %% %%%%%%%%%%%%%@ + %%%%%%%@ diff --git a/6-RShell/dsh b/6-RShell/dsh new file mode 100755 index 0000000000000000000000000000000000000000..4a114e1042aaab4e10a5c2019c72fe1a06536bff Binary files /dev/null and b/6-RShell/dsh differ diff --git a/6-RShell/dsh_cli.c b/6-RShell/dsh_cli.c new file mode 100644 index 0000000000000000000000000000000000000000..3cf5a244c9f7e66b80557be2e6b283220c85d0d7 --- /dev/null +++ b/6-RShell/dsh_cli.c @@ -0,0 +1,149 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <argp.h> +#include <getopt.h> + +#include "dshlib.h" +#include "rshlib.h" + + +/* + * Used to pass startup parameters back to main + */ +#define MODE_LCLI 0 //Local client +#define MODE_SCLI 1 //Socket client +#define MODE_SSVR 2 //Socket server + +typedef struct cmd_args{ + int mode; + char ip[16]; //e.g., 192.168.100.101\0 + int port; + int threaded_server; +}cmd_args_t; + + + +//You dont really need to understand this but the C runtime library provides +//an getopt() service to simplify handling command line arguments. This +//code will help setup dsh to handle triggering client or server mode along +//with passing optional connection parameters. + +void print_usage(const char *progname) { + printf("Usage: %s [-c | -s] [-i IP] [-p PORT] [-x] [-h]\n", progname); + printf(" Default is to run %s in local mode\n", progname); + printf(" -c Run as client\n"); + printf(" -s Run as server\n"); + printf(" -i IP Set IP/Interface address (only valid with -c or -s)\n"); + printf(" -p PORT Set port number (only valid with -c or -s)\n"); + printf(" -x Enable threaded mode (only valid with -s)\n"); + printf(" -h Show this help message\n"); + exit(0); +} + +void parse_args(int argc, char *argv[], cmd_args_t *cargs) { + int opt; + memset(cargs, 0, sizeof(cmd_args_t)); + + //defaults + cargs->mode = MODE_LCLI; + cargs->port = RDSH_DEF_PORT; + + while ((opt = getopt(argc, argv, "csi:p:xh")) != -1) { + switch (opt) { + case 'c': + if (cargs->mode != MODE_LCLI) { + fprintf(stderr, "Error: Cannot use both -c and -s\n"); + exit(EXIT_FAILURE); + } + cargs->mode = MODE_SCLI; + strncpy(cargs->ip, RDSH_DEF_CLI_CONNECT, sizeof(cargs->ip) - 1); + break; + case 's': + if (cargs->mode != MODE_LCLI) { + fprintf(stderr, "Error: Cannot use both -c and -s\n"); + exit(EXIT_FAILURE); + } + cargs->mode = MODE_SSVR; + strncpy(cargs->ip, RDSH_DEF_SVR_INTFACE, sizeof(cargs->ip) - 1); + break; + case 'i': + if (cargs->mode == MODE_LCLI) { + fprintf(stderr, "Error: -i can only be used with -c or -s\n"); + exit(EXIT_FAILURE); + } + strncpy(cargs->ip, optarg, sizeof(cargs->ip) - 1); + cargs->ip[sizeof(cargs->ip) - 1] = '\0'; // Ensure null termination + break; + case 'p': + if (cargs->mode == MODE_LCLI) { + fprintf(stderr, "Error: -p can only be used with -c or -s\n"); + exit(EXIT_FAILURE); + } + cargs->port = atoi(optarg); + if (cargs->port <= 0) { + fprintf(stderr, "Error: Invalid port number\n"); + exit(EXIT_FAILURE); + } + break; + case 'x': + if (cargs->mode != MODE_SSVR) { + fprintf(stderr, "Error: -x can only be used with -s\n"); + exit(EXIT_FAILURE); + } + cargs->threaded_server = 1; + break; + case 'h': + print_usage(argv[0]); + break; + default: + print_usage(argv[0]); + } + } + + if (cargs->threaded_server && cargs->mode != MODE_SSVR) { + fprintf(stderr, "Error: -x can only be used with -s\n"); + exit(EXIT_FAILURE); + } +} + + + +/* DO NOT EDIT + * main() logic fully implemented to: + * 1. run locally (no parameters) + * 2. start the server with the -s option + * 3. start the client with the -c option +*/ +int main(int argc, char *argv[]){ + cmd_args_t cargs; + int rc; + + memset(&cargs, 0, sizeof(cmd_args_t)); + parse_args(argc, argv, &cargs); + + switch(cargs.mode){ + case MODE_LCLI: + printf("local mode\n"); + rc = exec_local_cmd_loop(); + break; + case MODE_SCLI: + printf("socket client mode: addr:%s:%d\n", cargs.ip, cargs.port); + rc = exec_remote_cmd_loop(cargs.ip, cargs.port); + break; + case MODE_SSVR: + printf("socket server mode: addr:%s:%d\n", cargs.ip, cargs.port); + if (cargs.threaded_server){ + printf("-> Multi-Threaded Mode\n"); + } else { + printf("-> Single-Threaded Mode\n"); + } + rc = start_server(cargs.ip, cargs.port, cargs.threaded_server); + break; + default: + printf("error unknown mode\n"); + exit(EXIT_FAILURE); + } + + printf("cmd loop returned %d\n", rc); +} diff --git a/6-RShell/dshlib.c b/6-RShell/dshlib.c new file mode 100644 index 0000000000000000000000000000000000000000..5cc3320f37818d0f8f8e343f52a40180be76b0e1 --- /dev/null +++ b/6-RShell/dshlib.c @@ -0,0 +1,448 @@ +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <ctype.h> +#include <stdbool.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/wait.h> + +#include "dshlib.h" + +/**** + **** FOR REMOTE SHELL USE YOUR SOLUTION FROM SHELL PART 3 HERE + **** THE MAIN FUNCTION CALLS THIS ONE AS ITS ENTRY POINT TO + **** EXECUTE THE SHELL LOCALLY + **** + */ + +/* + * Implement your exec_local_cmd_loop function by building a loop that prompts the + * user for input. Use the SH_PROMPT constant from dshlib.h and then + * use fgets to accept user input. + * + * while(1){ + * printf("%s", SH_PROMPT); + * if (fgets(cmd_buff, ARG_MAX, stdin) == NULL){ + * printf("\n"); + * break; + * } + * //remove the trailing \n from cmd_buff + * cmd_buff[strcspn(cmd_buff,"\n")] = '\0'; + * + * //IMPLEMENT THE REST OF THE REQUIREMENTS + * } + * + * Also, use the constants in the dshlib.h in this code. + * SH_CMD_MAX maximum buffer size for user input + * EXIT_CMD constant that terminates the dsh program + * SH_PROMPT the shell prompt + * OK the command was parsed properly + * WARN_NO_CMDS the user command was empty + * ERR_TOO_MANY_COMMANDS too many pipes used + * ERR_MEMORY dynamic memory management failure + * + * errors returned + * OK No error + * ERR_MEMORY Dynamic memory management failure + * WARN_NO_CMDS No commands parsed + * ERR_TOO_MANY_COMMANDS too many pipes used + * + * console messages + * CMD_WARN_NO_CMD print on WARN_NO_CMDS + * CMD_ERR_PIPE_LIMIT print on ERR_TOO_MANY_COMMANDS + * CMD_ERR_EXECUTE print on execution failure of external command + * + * Standard Library Functions You Might Want To Consider Using (assignment 1+) + * malloc(), free(), strlen(), fgets(), strcspn(), printf() + * + * Standard Library Functions You Might Want To Consider Using (assignment 2+) + * fork(), execvp(), exit(), chdir() + */ +int numInstanceOf(char *str, const char c) { + int count = 0; + while (*str != '\0') { + if (*str == c) count++; + str++; + } + return count; +} + +void print_dragon(){ + FILE *dragon = fopen("dragon.txt", "r"); + if (dragon == NULL) { + return; + } + + char *s = NULL; + size_t nbyte; + ssize_t nchar; + + while (1) { + nchar = getline(&s, &nbyte, dragon); + if (nchar == -1) { + break; + } + if (nchar == 0) { + continue; + } + if (s == NULL) { + exit(1); + } + if (s[nchar - 1] == '\n') { + s[nchar - 1] = '\0'; + nchar--; + } + + printf("%s\n", s); + } + free(s); + fclose(dragon); +} + +char* trim_whitespace(char *str) { + int start = 0; + while (isspace((unsigned char)str[start])) { + start++; + } + + int end = strlen(str) - 1; + while (end > start && isspace((unsigned char)str[end])) { + end--; + } + + int j = 0; + for (int i = start; i <= end; i++) { + str[j++] = str[i]; + } + str[j] = '\0'; + + return str; +} + +int alloc_cmd_buff(cmd_buff_t *cmd_buff) { + if (cmd_buff == NULL) return ERR_MEMORY; + + cmd_buff->argc = 0; + + cmd_buff->argv = (char**)malloc(CMD_ARGV_MAX * sizeof(char *)); + if (cmd_buff->argv == NULL) return ERR_MEMORY; + + for (int i = 0; i < CMD_ARGV_MAX; i++) { + cmd_buff->argv[i] = (char *)malloc(ARG_MAX * sizeof(char)); + if (cmd_buff->argv[i] == NULL) { + for (int j = 0; j < i; j++) free(cmd_buff->argv[j]); + free(cmd_buff->argv); + return ERR_MEMORY; + } + } + + cmd_buff->_cmd_buffer = (char *)malloc(SH_CMD_MAX * sizeof(char)); + if (cmd_buff->_cmd_buffer == NULL) { + for (int i = 0; i < CMD_ARGV_MAX; i++) free(cmd_buff->argv[i]); + free(cmd_buff->argv); + return ERR_MEMORY; + } + + return OK; +} + +void free_cmd_buff(cmd_buff_t *cmd_buff) { + if (cmd_buff != NULL) { + if (cmd_buff->_cmd_buffer != NULL) { + free(cmd_buff->_cmd_buffer); + cmd_buff->_cmd_buffer = NULL; + } + + if (cmd_buff->argv != NULL) { + for (int i = 0; i < CMD_ARGV_MAX; i++) { + if (cmd_buff->argv[i] != NULL) { + free(cmd_buff->argv[i]); + cmd_buff->argv[i] = NULL; + } + } + free(cmd_buff->argv); + cmd_buff->argv = NULL; + } + memset(cmd_buff, 0, sizeof(cmd_buff_t)); + } +} + +void clear_cmd_buff(cmd_buff_t *cmd_buff) { + if (cmd_buff != NULL) { + cmd_buff->argc = 0; + if (cmd_buff->_cmd_buffer) memset(cmd_buff->_cmd_buffer, 0, strlen(cmd_buff->_cmd_buffer)); + for (int i = 0; i < CMD_ARGV_MAX; i++) cmd_buff->argv[i] = NULL; + } +} + +int build_cmd_buff(char *cmd_line, cmd_buff_t *cmd_buff) { + if ((int)strlen(cmd_line) > SH_CMD_MAX) return ERR_CMD_OR_ARGS_TOO_BIG; + + if ((int)strlen(cmd_line) == 0) return WARN_NO_CMDS; + + if (cmd_buff->_cmd_buffer != NULL) { + free(cmd_buff->_cmd_buffer); + cmd_buff->_cmd_buffer = strdup(trim_whitespace(cmd_line)); + } else return ERR_MEMORY; + + char *token = cmd_buff->_cmd_buffer; + bool quotes = false; + char *p = NULL; + + while (*token) { + if (*token == DOUBLE_QUOTE_CHAR) { + quotes = !quotes; + if (quotes) p = token + 1; + else *token = '\0'; + } else if (!quotes && (*token == SPACE_CHAR || *token == '\t')) { + *token = '\0'; + + if (p != NULL) { + cmd_buff->argv[cmd_buff->argc++] = p; + p = NULL; + } + } else if (p == NULL) { + p = token; + } + token++; + } + + if (p != NULL) { + if (cmd_buff->argc >= CMD_ARGV_MAX - 1) return ERR_CMD_OR_ARGS_TOO_BIG; + cmd_buff->argv[cmd_buff->argc++] = p; + } + + cmd_buff->argv[cmd_buff->argc] = NULL; + return OK; +} + +int alloc_cmd_list(command_list_t *clist, int rc) { + if (clist == NULL) return ERR_MEMORY; + clist->num = 0; + clist->commands = (cmd_buff_t **)calloc(CMD_MAX, sizeof(cmd_buff_t *)); + if (clist->commands == NULL) return ERR_MEMORY; + + for (int i = 0; i < CMD_MAX; i++) { + cmd_buff_t *cmd = (cmd_buff_t *)malloc(sizeof(cmd_buff_t)); + if (cmd == NULL) { + rc = ERR_MEMORY; + break; + } + + memset(cmd, 0, sizeof(cmd_buff_t)); + + if ((rc = alloc_cmd_buff(cmd)) != OK_EXIT) { + free(cmd); + break; + } + clist->commands[i] = cmd; + } + + return rc; +} + +int build_cmd_list(char *cmd_line, command_list_t *clist, int rc) { + if (numInstanceOf(cmd_line, PIPE_CHAR) > CMD_MAX-1) return ERR_TOO_MANY_COMMANDS; + + if ((int)strlen(cmd_line) > SH_CMD_MAX) return ERR_CMD_OR_ARGS_TOO_BIG; + + char *outer_saveptr = NULL; + + char *outer_token = strtok_r(cmd_line, PIPE_STRING, &outer_saveptr); + + while (outer_token != NULL) { + if (clist->num > CMD_MAX) return ERR_TOO_MANY_COMMANDS; + + cmd_buff_t *cmd = malloc(sizeof(cmd_buff_t)); + + if ((rc = alloc_cmd_buff(cmd)) != OK) { + free(cmd); + return rc; + } + + if ((rc = build_cmd_buff(outer_token, cmd)) != OK) { + free(cmd); + return rc; + } + + clist->commands[clist->num] = cmd; + clist->num++; + + outer_token = strtok_r(NULL, PIPE_STRING, &outer_saveptr); + } + + return OK; +} + +void free_cmd_list(command_list_t *clist) { + if (clist != NULL) { + if (clist->commands != NULL) { + for (int i = 0; i < CMD_MAX; i++) { + if (clist->commands[i] != NULL) { + free_cmd_buff(clist->commands[i]); + free(clist->commands[i]); + clist->commands[i] = NULL; + } + } + free(clist->commands); + clist->commands = NULL; + } + } +} + +void clear_cmd_list(command_list_t *clist) { + if (clist != NULL) { + clist->num = 0; + for (int i = 0; i < CMD_MAX; i++) clear_cmd_buff(clist->commands[i]); + } +} + +int execute_pipeline(command_list_t *clist) { + int pipes[clist->num - 1][2]; + pid_t pids[clist->num]; + + for (int i = 0; i < clist->num - 1; i++) { + if (pipe(pipes[i]) == -1) { + perror("pipe"); + exit(EXIT_FAILURE); + } + } + + for (int i = 0; i < clist->num; i++) { + pids[i] = fork(); + if (pids[i] == -1) { + perror("fork"); + exit(EXIT_FAILURE); + } + + if (pids[i] == 0) { + if (i > 0) dup2(pipes[i-1][0], STDIN_FILENO); + + if (i < clist->num - 1) dup2(pipes[i][1], STDOUT_FILENO); + + for (int j = 0; j < clist->num - 1; j++) { + close(pipes[j][0]); + close(pipes[j][1]); + } + + execvp(clist->commands[i]->argv[0], clist->commands[i]->argv); + perror("execvp"); + exit(EXIT_FAILURE); + } + } + + for (int i = 0; i < clist->num - 1; i++) { + close(pipes[i][0]); + close(pipes[i][1]); + } + + for (int i = 0; i < clist->num; i++) waitpid(pids[i], NULL, 0); + + return OK; +} + +int exec_one_cmd(command_list_t *clist, int rc) { + cmd_buff_t *cmd = clist->commands[0]; + if (strcmp(cmd->argv[0], "dragon") == 0) { + print_dragon(); + return OK; + } + + if (strcmp(cmd->argv[0], "rc") == 0) { + printf("%d\n", rc); + return OK; + } + + if (strcmp(cmd->argv[0], EXIT_CMD) == 0) { + printf("exiting...\n"); + return(OK_EXIT); + } + + cmd->argv[cmd->argc] = 0; + + if (strcmp(cmd->argv[0], "cd") == 0) chdir(cmd->argv[1]); + else { + int f_result, c_result; + f_result = fork(); + + if (f_result < 0) perror("fork failed"); + else if (f_result == 0) { + rc = execvp(cmd->argv[0], cmd->argv); + perror("execvp failed"); + exit(EXIT_FAILURE); + } else { + wait(&c_result); + } + } + return OK; +} + +int exec_cmd(command_list_t *clist, int rc) { + if (clist == NULL) return ERR_MEMORY; + + if (rc == ERR_TOO_MANY_COMMANDS) { + printf(CMD_ERR_PIPE_LIMIT, CMD_MAX); + return rc; + } + + char *cmd = clist->commands[0]->argv[0]; + if (!cmd) return ERR_MEMORY; + + if (clist->num == 1) { + if ((rc = exec_one_cmd(clist, rc)) != OK) { + if (rc == OK_EXIT) { + clear_cmd_list(clist); + return rc; + } else return ERR_EXEC_CMD; + } + } + + if (clist->num > 1) { + if ((rc = execute_pipeline(clist)) != OK) return ERR_EXEC_CMD; + } + + return OK; +} + +int exec_local_cmd_loop() +{ + char *cmd_line = (char *)malloc(ARG_MAX * sizeof(char)); + int rc = OK; + command_list_t *clist = (command_list_t *)malloc(sizeof(command_list_t)); + + if ((rc = alloc_cmd_list(clist, rc)) != OK) { + free(cmd_line); + return rc; + } + + while(1){ + printf("%s", SH_PROMPT); + if (fgets(cmd_line, ARG_MAX, stdin) == NULL){ + printf("\n"); + break; + } + //remove the trailing \n from cmd_buff + cmd_line[strcspn(cmd_line,"\n")] = '\0'; + + //IMPLEMENT THE REST OF THE REQUIREMENTS + if (strlen(cmd_line) == 0) { + printf(CMD_WARN_NO_CMD); + continue; + } + + if ((rc = build_cmd_list(cmd_line, clist, rc)) != OK) break; + + if ((rc = exec_cmd(clist, rc)) != OK) { + if (rc == OK_EXIT) rc = OK; + break; + } + + clear_cmd_list(clist); + } + + free(cmd_line); + free_cmd_list(clist); + free(clist); + return rc; +} diff --git a/6-RShell/dshlib.h b/6-RShell/dshlib.h new file mode 100644 index 0000000000000000000000000000000000000000..06ef5b931788100ee5f8dee8b3b369eb4bac3489 --- /dev/null +++ b/6-RShell/dshlib.h @@ -0,0 +1,100 @@ +#ifndef __DSHLIB_H__ + #define __DSHLIB_H__ + + +//Constants for command structure sizes +#define EXE_MAX 64 +#define ARG_MAX 256 +#define CMD_MAX 8 +#define CMD_ARGV_MAX (CMD_MAX + 1) +// Longest command that can be read from the shell +#define SH_CMD_MAX EXE_MAX + ARG_MAX + +typedef struct command +{ + char exe[EXE_MAX]; + char args[ARG_MAX]; +} command_t; + +#include <stdbool.h> + +typedef struct cmd_buff +{ + int argc; + char **argv; //[CMD_ARGV_MAX] + char *_cmd_buffer; + char *input_file; // extra credit, stores input redirection file (for `<`) + char *output_file; // extra credit, stores output redirection file (for `>`) + bool append_mode; // extra credit, sets append mode fomr output_file +} cmd_buff_t; + +typedef struct command_list{ + int num; + cmd_buff_t **commands; // [CMD_MAX] +}command_list_t; + +//Special character #defines +#define SPACE_CHAR ' ' +#define PIPE_CHAR '|' +#define PIPE_STRING "|" +#define DOUBLE_QUOTE_CHAR '"' + +#define SH_PROMPT "dsh4> " +#define EXIT_CMD "exit" +#define RC_SC 99 +#define EXIT_SC 100 + +//Standard Return Codes +#define OK 0 +#define WARN_NO_CMDS -1 +#define ERR_TOO_MANY_COMMANDS -2 +#define ERR_CMD_OR_ARGS_TOO_BIG -3 +#define ERR_CMD_ARGS_BAD -4 //for extra credit +#define ERR_MEMORY -5 +#define ERR_EXEC_CMD -6 +#define OK_EXIT -7 + + +//helpers +int numInstanceOf(char *str, const char c); +void print_dragon(); +char* trim_whitesapce(char *str); + +//prototypes +int alloc_cmd_buff(cmd_buff_t *cmd_buff); +void free_cmd_buff(cmd_buff_t *cmd_buff); +void clear_cmd_buff(cmd_buff_t *cmd_buff); +int build_cmd_buff(char *cmd_line, cmd_buff_t *cmd_buff); +//int close_cmd_buff(cmd_buff_t *cmd_buff); +int alloc_cmd_list(command_list_t *clist, int rc); +int build_cmd_list(char *cmd_line, command_list_t *clist, int rc); +void free_cmd_list(command_list_t *cmd_lst); +void clear_cmd_list(command_list_t *clist); + +//built in command stuff +typedef enum { + BI_CMD_EXIT, + BI_CMD_DRAGON, + BI_CMD_CD, + BI_CMD_RC, //extra credit command + BI_CMD_STOP_SVR, //new command "stop-server" + BI_NOT_BI, + BI_EXECUTED, +} Built_In_Cmds; +Built_In_Cmds match_command(const char *input); +Built_In_Cmds exec_built_in_cmd(cmd_buff_t *cmd); + +//main execution context +int exec_local_cmd_loop(); +int exec_one_cmd(command_list_t *clist, int rc); +int exec_cmd(command_list_t *clist, int rc); +int execute_pipeline(command_list_t *clist); + + +//output constants +#define CMD_OK_HEADER "PARSED COMMAND LINE - TOTAL COMMANDS %d\n" +#define CMD_WARN_NO_CMD "warning: no commands provided\n" +#define CMD_ERR_PIPE_LIMIT "error: piping limited to %d commands\n" +#define BI_NOT_IMPLEMENTED "not implemented" + +#endif diff --git a/6-RShell/makefile b/6-RShell/makefile new file mode 100644 index 0000000000000000000000000000000000000000..a079ef4946ce8578836606ceab3f553b305b1875 --- /dev/null +++ b/6-RShell/makefile @@ -0,0 +1,31 @@ +# Compiler settings +CC = gcc +CFLAGS = -Wall -Wextra -g + +# Target executable name +TARGET = dsh + +# Find all source and header files +SRCS = $(wildcard *.c) +HDRS = $(wildcard *.h) + +# Default target +all: $(TARGET) + +# Compile source to executable +$(TARGET): $(SRCS) $(HDRS) + $(CC) $(CFLAGS) -o $(TARGET) $(SRCS) + +# Clean up build files +clean: + rm -f $(TARGET) + +test: + bats $(wildcard ./bats/*.sh) + +valgrind: + echo "pwd\nexit" | valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1 ./$(TARGET) + echo "pwd\nexit" | valgrind --tool=helgrind --error-exitcode=1 ./$(TARGET) + +# Phony targets +.PHONY: all clean test diff --git a/6-RShell/rsh_cli.c b/6-RShell/rsh_cli.c new file mode 100644 index 0000000000000000000000000000000000000000..d1dea1326836cfc46c987d18c57e74792c589edc --- /dev/null +++ b/6-RShell/rsh_cli.c @@ -0,0 +1,238 @@ + +#include <sys/socket.h> +#include <arpa/inet.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/un.h> +#include <fcntl.h> + +#include "dshlib.h" +#include "rshlib.h" + + + + +/* + * exec_remote_cmd_loop(server_ip, port) + * server_ip: a string in ip address format, indicating the servers IP + * address. Note 127.0.0.1 is the default meaning the server + * is running on the same machine as the client + * + * port: The port the server will use. Note the constant + * RDSH_DEF_PORT which is 1234 in rshlib.h. If you are using + * tux you may need to change this to your own default, or even + * better use the command line override -c implemented in dsh_cli.c + * For example ./dsh -c 10.50.241.18:5678 where 5678 is the new port + * number and the server address is 10.50.241.18 + * + * This function basically implements the network version of + * exec_local_cmd_loop() from the last assignemnt. It will: + * + * 1. Allocate buffers for sending and receiving data over the + * network + * 2. Create a network connection to the server, getting an active + * socket by calling the start_client(server_ip, port) function. + * 2. Go into an infinite while(1) loop prompting the user for + * input commands. + * + * a. Accept a command from the user via fgets() + * b. Send that command to the server using send() - it should + * be a null terminated string + * c. Go into a loop and receive client requests. Note each + * receive might not be a C string so you need to print it + * out using: + * printf("%.*s", (int)bytes_received, rsp_buff); + * this version of printf() uses the "%.*s" flag that indicates + * that the rsp_buff might be a null terminated string, or + * it might not be, if its not, print exactly bytes_received + * bytes. + * d. In the recv() loop described above. Each time you receive + * data from the server, see if the last byte received is the + * EOF character. This indicates the server is done and you can + * send another command by going to the top of the loop. The + * best way to do this is as follows assuming you are receiving + * data into a buffer called recv_buff, and you received + * recv_bytes in the call to recv: + * + * recv_bytes = recv(sock, recv_buff, recv_buff_sz, 0) + * + * if recv_bytes: + * <negative_number>: communication error + * 0: Didn't receive anything, likely server down + * > 0: Got some data. Check if the last byte is EOF + * is_eof = (recv_buff[recv_bytes-1] == RDSH_EOF_CHAR) ? 1 : 0; + * if is_eof is true, this is the last part of the transmission + * from the server and you can break out of the recv() loop. + * + * returns: + * OK: The client executed all of its commands and is exiting + * either by the `exit` command that terminates the client + * or the `stop-server` command that terminates both the + * client and the server. + * ERR_MEMORY: If this function cannot allocate memory via + * malloc for the send and receive buffers + * ERR_RDSH_CLIENT: If the client cannot connect to the server. + * AKA the call to start_client() fails. + * ERR_RDSH_COMMUNICATION: If there is a communication error, AKA + * any failures from send() or recv(). + * + * NOTE: Since there are several exit points and each exit point must + * call free() on the buffers allocated, close the socket, and + * return an appropriate error code. Its suggested you use the + * helper function client_cleanup() for these purposes. For example: + * + * return client_cleanup(cli_socket, request_buff, resp_buff, ERR_RDSH_COMMUNICATION); + * return client_cleanup(cli_socket, request_buff, resp_buff, OK); + * + * The above will return ERR_RDSH_COMMUNICATION and OK respectively to the main() + * function after cleaning things up. See the documentation for client_cleanup() + * + */ +int exec_remote_cmd_loop(char *address, int port) +{ + char *cmd_buff; + char *rsp_buff; + int cli_socket; + ssize_t io_size; + int is_eof; + + // TODO set up cmd and response buffs + cmd_buff = (char *)malloc(RDSH_COMM_BUFF_SZ); + rsp_buff = (char *)malloc(RDSH_COMM_BUFF_SZ); + if (!cmd_buff || !rsp_buff) { + perror("malloc failed"); + return ERR_MEMORY; + } + + cli_socket = start_client(address,port); + if (cli_socket < 0){ + perror("start client"); + return client_cleanup(cli_socket, cmd_buff, rsp_buff, ERR_RDSH_CLIENT); + } + + while (1) + { + // TODO print prompt + printf("%s", SH_PROMPT); + // TODO fgets input + if (fgets(cmd_buff, ARG_MAX, stdin) == NULL) { + printf("\n"); + break; + } + cmd_buff[strcspn(cmd_buff,"\n")] = '\0'; + + // TODO send() over cli_socket + int send_len = strlen(cmd_buff) + 1; + int bytes_sent = send(cli_socket, cmd_buff, send_len, 0); + if (bytes_sent < 0) { + perror("send failed"); + return client_cleanup(cli_socket, cmd_buff, rsp_buff, ERR_RDSH_COMMUNICATION); + } + + // TODO recv all the results + char eof = RDSH_EOF_CHAR; + while ((io_size = recv(cli_socket, rsp_buff, RDSH_COMM_BUFF_SZ, 0)) > 0) { + if (io_size < 0) { + perror("recv failed"); + return client_cleanup(cli_socket, cmd_buff, rsp_buff, ERR_RDSH_COMMUNICATION); + } + if (io_size == 0) break; + is_eof = ((char)rsp_buff[io_size - 1] == eof) ? 1 : 0; + if (is_eof) rsp_buff[io_size - 1] = '\0'; + printf("%.*s", (int)io_size, rsp_buff); + + if (is_eof) break; + } + + // TODO break on exit command + if (strcmp(cmd_buff, EXIT_CMD) == 0 || strcmp(cmd_buff, "stop-server") == 0 ) break; + } + + return client_cleanup(cli_socket, cmd_buff, rsp_buff, OK); +} + +/* + * start_client(server_ip, port) + * server_ip: a string in ip address format, indicating the servers IP + * address. Note 127.0.0.1 is the default meaning the server + * is running on the same machine as the client + * + * port: The port the server will use. Note the constant + * RDSH_DEF_PORT which is 1234 in rshlib.h. If you are using + * tux you may need to change this to your own default, or even + * better use the command line override -c implemented in dsh_cli.c + * For example ./dsh -c 10.50.241.18:5678 where 5678 is the new port + * number and the server address is 10.50.241.18 + * + * This function basically runs the client by: + * 1. Creating the client socket via socket() + * 2. Calling connect() + * 3. Returning the client socket after connecting to the server + * + * returns: + * client_socket: The file descriptor fd of the client socket + * ERR_RDSH_CLIENT: If socket() or connect() fail + * + */ +int start_client(char *server_ip, int port){ + struct sockaddr_in addr; + int cli_socket = socket(AF_INET, SOCK_STREAM, 0); + int ret; + + // TODO set up cli_socket + if (cli_socket == -1) { + perror("socket"); + return ERR_RDSH_CLIENT; + } + + memset(&addr, 0, sizeof(struct sockaddr_in)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = inet_addr(server_ip); + addr.sin_port = htons(port); + + ret = connect(cli_socket, (const struct sockaddr *) &addr, sizeof(struct sockaddr_in)); + if (ret == -1) { + perror("connect"); + return ERR_RDSH_CLIENT; + } + + return cli_socket; +} + +/* + * client_cleanup(int cli_socket, char *cmd_buff, char *rsp_buff, int rc) + * cli_socket: The client socket + * cmd_buff: The buffer that will hold commands to send to server + * rsp_buff: The buffer that will hld server responses + * + * This function does the following: + * 1. If cli_socket > 0 it calls close(cli_socket) to close the socket + * 2. It calls free() on cmd_buff and rsp_buff + * 3. It returns the value passed as rc + * + * Note this function is intended to be helper to manage exit conditions + * from the exec_remote_cmd_loop() function given there are several + * cleanup steps. We provide it to you fully implemented as a helper. + * You do not have to use it if you want to develop an alternative + * strategy for cleaning things up in your exec_remote_cmd_loop() + * implementation. + * + * returns: + * rc: This function just returns the value passed as the + * rc parameter back to the caller. This way the caller + * can just write return client_cleanup(...) + * + */ +int client_cleanup(int cli_socket, char *cmd_buff, char *rsp_buff, int rc){ + //If a valid socket number close it. + if(cli_socket > 0) close(cli_socket); + + //Free up the buffers + free(cmd_buff); + free(rsp_buff); + + //Echo the return value that was passed as a parameter + return rc; +} diff --git a/6-RShell/rsh_server.c b/6-RShell/rsh_server.c new file mode 100644 index 0000000000000000000000000000000000000000..c49edcebc85f41b8cc725aa29dca1f69a68dabe4 --- /dev/null +++ b/6-RShell/rsh_server.c @@ -0,0 +1,620 @@ +#include <sys/socket.h> +#include <sys/wait.h> +#include <arpa/inet.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/un.h> +#include <fcntl.h> + +//INCLUDES for extra credit +//#include <signal.h> +//#include <pthread.h> +//------------------------- + +#include "dshlib.h" +#include "rshlib.h" + + +/* + * start_server(ifaces, port, is_threaded) + * ifaces: a string in ip address format, indicating the interface + * where the server will bind. In almost all cases it will + * be the default "0.0.0.0" which binds to all interfaces. + * note the constant RDSH_DEF_SVR_INTFACE in rshlib.h + * + * port: The port the server will use. Note the constant + * RDSH_DEF_PORT which is 1234 in rshlib.h. If you are using + * tux you may need to change this to your own default, or even + * better use the command line override -s implemented in dsh_cli.c + * For example ./dsh -s 0.0.0.0:5678 where 5678 is the new port + * + * is_threded: Used for extra credit to indicate the server should implement + * per thread connections for clients + * + * This function basically runs the server by: + * 1. Booting up the server + * 2. Processing client requests until the client requests the + * server to stop by running the `stop-server` command + * 3. Stopping the server. + * + * This function is fully implemented for you and should not require + * any changes for basic functionality. + * + * IF YOU IMPLEMENT THE MULTI-THREADED SERVER FOR EXTRA CREDIT YOU NEED + * TO DO SOMETHING WITH THE is_threaded ARGUMENT HOWEVER. + */ +int start_server(char *ifaces, int port, int is_threaded){ + int svr_socket; + int rc; + + // + //TODO: If you are implementing the extra credit, please add logic + // to keep track of is_threaded to handle this feature + // + + svr_socket = boot_server(ifaces, port); + if (svr_socket < 0){ + int err_code = svr_socket; //server socket will carry error code + return err_code; + } + + rc = process_cli_requests(svr_socket); + + stop_server(svr_socket); + + + return rc; +} + +/* + * stop_server(svr_socket) + * svr_socket: The socket that was created in the boot_server() + * function. + * + * This function simply returns the value of close() when closing + * the socket. + */ +int stop_server(int svr_socket){ + return close(svr_socket); +} + +/* + * boot_server(ifaces, port) + * ifaces & port: see start_server for description. They are passed + * as is to this function. + * + * This function "boots" the rsh server. It is responsible for all + * socket operations prior to accepting client connections. Specifically: + * + * 1. Create the server socket using the socket() function. + * 2. Calling bind to "bind" the server to the interface and port + * 3. Calling listen to get the server ready to listen for connections. + * + * after creating the socket and prior to calling bind you might want to + * include the following code: + * + * int enable=1; + * setsockopt(svr_socket, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)); + * + * when doing development you often run into issues where you hold onto + * the port and then need to wait for linux to detect this issue and free + * the port up. The code above tells linux to force allowing this process + * to use the specified port making your life a lot easier. + * + * Returns: + * + * server_socket: Sockets are just file descriptors, if this function is + * successful, it returns the server socket descriptor, + * which is just an integer. + * + * ERR_RDSH_COMMUNICATION: This error code is returned if the socket(), + * bind(), or listen() call fails. + * + */ +int boot_server(char *ifaces, int port){ + int svr_socket = socket(AF_INET, SOCK_STREAM, 0); + int ret; + + struct sockaddr_in addr; + + // TODO set up the socket - this is very similar to the demo code + + /* + * Prepare for accepting connections. The backlog size is set + * to 20. So while one request is being processed other requests + * can be waiting. + */ + if (svr_socket == -1) { + perror("socket"); + return ERR_RDSH_COMMUNICATION; + } + + int enable=1; + setsockopt(svr_socket, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)); + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = inet_addr(ifaces); + addr.sin_port = htons(port); + + ret = bind(svr_socket, (const struct sockaddr *) &addr, sizeof(struct sockaddr_in)); + + if (ret == -1) { + perror("bind"); + return ERR_RDSH_COMMUNICATION; + } + + ret = listen(svr_socket, 20); + if (ret == -1) { + perror("listen"); + return ERR_RDSH_COMMUNICATION; + } + + return svr_socket; +} + +/* + * process_cli_requests(svr_socket) + * svr_socket: The server socket that was obtained from boot_server() + * + * This function handles managing client connections. It does this using + * the following logic + * + * 1. Starts a while(1) loop: + * + * a. Calls accept() to wait for a client connection. Recall that + * the accept() function returns another socket specifically + * bound to a client connection. + * b. Calls exec_client_requests() to handle executing commands + * sent by the client. It will use the socket returned from + * accept(). + * c. Loops back to the top (step 2) to accept connecting another + * client. + * + * note that the exec_client_requests() return code should be + * negative if the client requested the server to stop by sending + * the `stop-server` command. If this is the case step 2b breaks + * out of the while(1) loop. + * + * 2. After we exit the loop, we need to cleanup. Dont forget to + * free the buffer you allocated in step #1. Then call stop_server() + * to close the server socket. + * + * Returns: + * + * OK_EXIT: When the client sends the `stop-server` command this function + * should return OK_EXIT. + * + * ERR_RDSH_COMMUNICATION: This error code terminates the loop and is + * returned from this function in the case of the accept() + * function failing. + * + * OTHERS: See exec_client_requests() for return codes. Note that positive + * values will keep the loop running to accept additional client + * connections, and negative values terminate the server. + * + */ +int process_cli_requests(int svr_socket){ + int cli_socket; + int rc = OK; + + while(1){ + // TODO use the accept syscall to create cli_socket + cli_socket = accept(svr_socket, NULL, NULL); + if (cli_socket == -1) { + perror("accept"); + return ERR_RDSH_COMMUNICATION; + } + + // and then exec_client_requests(cli_socket) + rc = exec_client_requests(cli_socket); + if (rc < 0) break; + + } + + stop_server(svr_socket); + return rc; +} + +/* + * exec_client_requests(cli_socket) + * cli_socket: The server-side socket that is connected to the client + * + * This function handles accepting remote client commands. The function will + * loop and continue to accept and execute client commands. There are 2 ways + * that this ongoing loop accepting client commands ends: + * + * 1. When the client executes the `exit` command, this function returns + * to process_cli_requests() so that we can accept another client + * connection. + * 2. When the client executes the `stop-server` command this function + * returns to process_cli_requests() with a return code of OK_EXIT + * indicating that the server should stop. + * + * Note that this function largely follows the implementation of the + * exec_local_cmd_loop() function that you implemented in the last + * shell program deliverable. The main difference is that the command will + * arrive over the recv() socket call rather than reading a string from the + * keyboard. + * + * This function also must send the EOF character after a command is + * successfully executed to let the client know that the output from the + * command it sent is finished. Use the send_message_eof() to accomplish + * this. + * + * Of final note, this function must allocate a buffer for storage to + * store the data received by the client. For example: + * io_buff = malloc(RDSH_COMM_BUFF_SZ); + * And since it is allocating storage, it must also properly clean it up + * prior to exiting. + * + * Returns: + * + * OK: The client sent the `exit` command. Get ready to connect + * another client. + * OK_EXIT: The client sent `stop-server` command to terminate the server + * + * ERR_RDSH_COMMUNICATION: A catch all for any socket() related send + * or receive errors. + */ +int exec_client_requests(int cli_socket) { + int io_size; + command_list_t* cmd_list = malloc(sizeof(command_list_t)); + int rc; + int cmd_rc; + //int last_rc; + char *io_buff; + + io_buff = malloc(RDSH_COMM_BUFF_SZ); + if (io_buff == NULL){ + return ERR_RDSH_SERVER; + } + + while(1) { + // TODO use recv() syscall to get input + io_size = recv(cli_socket, io_buff, RDSH_COMM_BUFF_SZ, 0); + if (io_size == -1) { + perror("read error"); + return ERR_RDSH_COMMUNICATION; + } + // TODO build up a cmd_list + cmd_list->num = 0; + + rc = alloc_cmd_list(cmd_list, rc); + + rc = build_cmd_list(io_buff, cmd_list, rc); + if (rc == OK) { + // TODO rsh_execute_pipeline to run your cmd_list + cmd_rc = rsh_execute_pipeline(cli_socket, cmd_list); + } + + if (strcmp(io_buff, EXIT_CMD) == 0) { + free(io_buff); + free_cmd_list(cmd_list); + rc = OK; + return OK; + } else if (strcmp(io_buff, "stop-server") == 0) { + free(io_buff); + free_cmd_list(cmd_list); + rc = OK; + return OK_EXIT; + } + + // TODO send appropriate respones with send_message_string + // - error constants for failures + // - buffer contents from execute commands + // - etc. + if (cmd_rc == 0) send_message_string(cli_socket, io_buff); + else send_message_string(cli_socket, CMD_ERR_RDSH_EXEC); + + // TODO send_message_eof when done + send_message_eof(cli_socket); + } + + return WARN_RDSH_NOT_IMPL; +} + +/* + * send_message_eof(cli_socket) + * cli_socket: The server-side socket that is connected to the client + + * Sends the EOF character to the client to indicate that the server is + * finished executing the command that it sent. + * + * Returns: + * + * OK: The EOF character was sent successfully. + * + * ERR_RDSH_COMMUNICATION: The send() socket call returned an error or if + * we were unable to send the EOF character. + */ +int send_message_eof(int cli_socket){ + int send_len = (int)sizeof(RDSH_EOF_CHAR); + int sent_len = send(cli_socket, &RDSH_EOF_CHAR, send_len, 0); + + if (sent_len != send_len) return ERR_RDSH_COMMUNICATION; + return OK; +} + + +/* + * send_message_string(cli_socket, char *buff) + * cli_socket: The server-side socket that is connected to the client + * buff: A C string (aka null terminated) of a message we want + * to send to the client. + * + * Sends a message to the client. Note this command executes both a send() + * to send the message and a send_message_eof() to send the EOF character to + * the client to indicate command execution terminated. + * + * Returns: + * + * OK: The message in buff followed by the EOF character was + * sent successfully. + * + * ERR_RDSH_COMMUNICATION: The send() socket call returned an error or if + * we were unable to send the message followed by the EOF character. + */ +int send_message_string(int cli_socket, char *buff){ + //TODO implement writing to cli_socket with send() + int send_len = strlen(buff) + 1; + int bytes_sent; + + bytes_sent = send(cli_socket, buff, send_len, 0); + if (bytes_sent == -1) { + perror("send failed"); + return ERR_RDSH_COMMUNICATION; + } + + int result = send_message_eof(cli_socket); + if (result == OK) return OK; + else { + perror("eof send failed"); + return ERR_RDSH_COMMUNICATION; + } + + return WARN_RDSH_NOT_IMPL; +} + + +/* + * rsh_execute_pipeline(int cli_sock, command_list_t *clist) + * cli_sock: The server-side socket that is connected to the client + * clist: The command_list_t structure that we implemented in + * the last shell. + * + * This function executes the command pipeline. It should basically be a + * replica of the execute_pipeline() function from the last deliverable. + * The only thing different is that you will be using the cli_sock as the + * main file descriptor on the first executable in the pipeline for STDIN, + * and the cli_sock for the file descriptor for STDOUT, and STDERR for the + * last executable in the pipeline. See picture below: + * + * + *┌───────────┐ ┌───────────┐ + *│ cli_sock │ │ cli_sock │ + *└─────┬─────┘ └────▲──▲───┘ + * │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ + * │ │ Process 1 │ │ Process 2 │ │ Process N │ │ │ + * │ │ │ │ │ │ │ │ │ + * └───▶stdin stdout├─┬──▶│stdin stdout├─┬──▶│stdin stdout├──┘ │ + * │ │ │ │ │ │ │ │ │ + * │ stderr├─┘ │ stderr├─┘ │ stderr├─────┘ + * └──────────────┘ └──────────────┘ └──────────────┘ + * WEXITSTATUS() + * of this last + * process to get + * the return code + * for this function + * + * Returns: + * + * EXIT_CODE: This function returns the exit code of the last command + * executed in the pipeline. If only one command is executed + * that value is returned. Remember, use the WEXITSTATUS() + * macro that we discussed during our fork/exec lecture to + * get this value. + */ +int rsh_execute_pipeline(int cli_sock, command_list_t *clist) { + int pipes[clist->num - 1][2]; // Array of pipes + pid_t pids[clist->num]; + int pids_st[clist->num]; // Array to store process IDs + //Built_In_Cmds bi_cmd; + int exit_code; + + // Create all necessary pipes + for (int i = 0; i < clist->num - 1; i++) { + if (pipe(pipes[i]) == -1) { + perror("pipe"); + exit(EXIT_FAILURE); + } + } + + for (int i = 0; i < clist->num; i++) { + // TODO this is basically the same as the piped fork/exec assignment, except for where you connect the begin and end of the pipeline (hint: cli_sock) + // TODO HINT you can dup2(cli_sock with STDIN_FILENO, STDOUT_FILENO, etc. + pids[i] = fork(); + if (pids[i] == -1) { + perror("fork"); + exit(EXIT_FAILURE); + } + + if (pids[i] == 0) { + if (pids[i] == 0) { //child process + cmd_buff_t *cmd = clist->commands[i]; + //set up input pipe for all except first process + if (i == 0) { + dup2(cli_sock, STDIN_FILENO); + } + if (i == clist->num - 1) { + dup2(cli_sock, STDOUT_FILENO); + dup2(cli_sock, STDERR_FILENO); + } + + if (i > 0) { + dup2(pipes[i-1][0], STDIN_FILENO); + } + //set up output pipes for all except last process + if (i < clist->num - 1) { + dup2(pipes[i][1], STDOUT_FILENO); + } + //close all pipe ends in child + for (int j = 0; j < clist->num - 1; j++) { + close(pipes[j][0]); + close(pipes[j][1]); + } + //execute command + execvp(cmd->argv[0], cmd->argv); + perror("execvp"); + exit(EXIT_FAILURE); + } cmd_buff_t *cmd = clist->commands[i]; + if (i == 0) dup2(cli_sock, STDIN_FILENO); + + if (i == clist->num - 1) { + dup2(cli_sock, STDOUT_FILENO); + dup2(cli_sock, STDERR_FILENO); + } + + if (i > 0) dup2(pipes[i-1][0], STDIN_FILENO); + + if (i < clist->num - 1) dup2(pipes[i][1], STDOUT_FILENO); + + for (int j = 0; j < clist->num - 1; j++) { + close(pipes[j][0]); + close(pipes[j][1]); + } + + execvp(cmd->argv[0], cmd->argv); + perror("execvp"); + exit(EXIT_FAILURE); + } + } + + // Parent process: close all pipe ends + for (int i = 0; i < clist->num - 1; i++) { + close(pipes[i][0]); + close(pipes[i][1]); + } + + // Wait for all children + for (int i = 0; i < clist->num; i++) { + waitpid(pids[i], &pids_st[i], 0); + } + + //by default get exit code of last process + //use this as the return value + exit_code = WEXITSTATUS(pids_st[clist->num - 1]); + for (int i = 0; i < clist->num; i++) { + //if any commands in the pipeline are EXIT_SC + //return that to enable the caller to react + if (WEXITSTATUS(pids_st[i]) == EXIT_SC) + exit_code = EXIT_SC; + } + return exit_code; +} + +/************** OPTIONAL STUFF ***************/ +/**** + **** NOTE THAT THE FUNCTIONS BELOW ALIGN TO HOW WE CRAFTED THE SOLUTION + **** TO SEE IF A COMMAND WAS BUILT IN OR NOT. YOU CAN USE A DIFFERENT + **** STRATEGY IF YOU WANT. IF YOU CHOOSE TO DO SO PLEASE REMOVE THESE + **** FUNCTIONS AND THE PROTOTYPES FROM rshlib.h + **** + */ + +/* + * rsh_match_command(const char *input) + * cli_socket: The string command for a built-in command, e.g., dragon, + * cd, exit-server + * + * This optional function accepts a command string as input and returns + * one of the enumerated values from the BuiltInCmds enum as output. For + * example: + * + * Input Output + * exit BI_CMD_EXIT + * dragon BI_CMD_DRAGON + * + * This function is entirely optional to implement if you want to handle + * processing built-in commands differently in your implementation. + * + * Returns: + * + * BI_CMD_*: If the command is built-in returns one of the enumeration + * options, for example "cd" returns BI_CMD_CD + * + * BI_NOT_BI: If the command is not "built-in" the BI_NOT_BI value is + * returned. + */ +Built_In_Cmds rsh_match_command(const char *input) +{ + if (strcmp(input, "exit") == 0) + return BI_CMD_EXIT; + if (strcmp(input, "dragon") == 0) + return BI_CMD_DRAGON; + if (strcmp(input, "cd") == 0) + return BI_CMD_CD; + if (strcmp(input, "stop-server") == 0) + return BI_CMD_STOP_SVR; + if (strcmp(input, "rc") == 0) + return BI_CMD_RC; + return BI_NOT_BI; +} + +/* + * rsh_built_in_cmd(cmd_buff_t *cmd) + * cmd: The cmd_buff_t of the command, remember, this is the + * parsed version fo the command + * + * This optional function accepts a parsed cmd and then checks to see if + * the cmd is built in or not. It calls rsh_match_command to see if the + * cmd is built in or not. Note that rsh_match_command returns BI_NOT_BI + * if the command is not built in. If the command is built in this function + * uses a switch statement to handle execution if appropriate. + * + * Again, using this function is entirely optional if you are using a different + * strategy to handle built-in commands. + * + * Returns: + * + * BI_NOT_BI: Indicates that the cmd provided as input is not built + * in so it should be sent to your fork/exec logic + * BI_EXECUTED: Indicates that this function handled the direct execution + * of the command and there is nothing else to do, consider + * it executed. For example the cmd of "cd" gets the value of + * BI_CMD_CD from rsh_match_command(). It then makes the libc + * call to chdir(cmd->argv[1]); and finally returns BI_EXECUTED + * BI_CMD_* Indicates that a built-in command was matched and the caller + * is responsible for executing it. For example if this function + * returns BI_CMD_STOP_SVR the caller of this function is + * responsible for stopping the server. If BI_CMD_EXIT is returned + * the caller is responsible for closing the client connection. + * + * AGAIN - THIS IS TOTALLY OPTIONAL IF YOU HAVE OR WANT TO HANDLE BUILT-IN + * COMMANDS DIFFERENTLY. + */ +Built_In_Cmds rsh_built_in_cmd(cmd_buff_t *cmd) +{ + Built_In_Cmds ctype = BI_NOT_BI; + ctype = rsh_match_command(cmd->argv[0]); + + switch (ctype) + { + // case BI_CMD_DRAGON: + // print_dragon(); + // return BI_EXECUTED; + case BI_CMD_EXIT: + return BI_CMD_EXIT; + case BI_CMD_STOP_SVR: + return BI_CMD_STOP_SVR; + case BI_CMD_RC: + return BI_CMD_RC; + case BI_CMD_CD: + chdir(cmd->argv[1]); + return BI_EXECUTED; + default: + return BI_NOT_BI; + } +} diff --git a/6-RShell/rshlib.h b/6-RShell/rshlib.h new file mode 100644 index 0000000000000000000000000000000000000000..a104cf14358dae73e2d328a1c79c0c6fd9c8ff2c --- /dev/null +++ b/6-RShell/rshlib.h @@ -0,0 +1,78 @@ +#ifndef __RSH_LIB_H__ + #define __RSH_LIB_H__ + +#include "dshlib.h" + +//common remote shell client and server constants and definitions + + +//Constants for communication +//Note that these should work fine in a local VM but you will likely have +//to change the port number if you are working on tux. +#define RDSH_DEF_PORT 1234 //Default port # +#define RDSH_DEF_SVR_INTFACE "0.0.0.0" //Default start all interfaces +#define RDSH_DEF_CLI_CONNECT "127.0.0.1" //Default server is running on + //localhost 127.0.0.1 + +//constants for buffer sizes +#define RDSH_COMM_BUFF_SZ (1024*64) //64K +#define STOP_SERVER_SC 200 //returned from pipeline excution + //if the command is to stop the + //server. See documentation for + //exec_client_requests() for more info + +//end of message delimiter. This is super important. TCP is a stream, therefore +//the protocol designer is responsible for managing where messages begin and end +//there are many common techniques for this, but one of the simplest ways is to +//use an end of stream marker. Since rsh is a "shell" program we will be using +//ascii code 0x04, which is commonly used as the end-of-file (EOF) character in +//linux based systems. +static const char RDSH_EOF_CHAR = 0x04; + +//rdsh specific error codes for functions +#define ERR_RDSH_COMMUNICATION -50 //Used for communication errors +#define ERR_RDSH_SERVER -51 //General server errors +#define ERR_RDSH_CLIENT -52 //General client errors +#define ERR_RDSH_CMD_EXEC -53 //RSH command execution errors +#define WARN_RDSH_NOT_IMPL -99 //Not Implemented yet warning + +//Output message constants for server +#define CMD_ERR_RDSH_COMM "rdsh-error: communications error\n" +#define CMD_ERR_RDSH_EXEC "rdsh-error: command execution error\n" +#define CMD_ERR_RDSH_ITRNL "rdsh-error: internal server error - %d\n" +#define CMD_ERR_RDSH_SEND "rdsh-error: partial send. Sent %d, expected to send %d\n" +#define RCMD_SERVER_EXITED "server appeared to terminate - exiting\n" + +//Output message constants for client +#define RCMD_MSG_CLIENT_EXITED "client exited: getting next connection...\n" +#define RCMD_MSG_SVR_STOP_REQ "client requested server to stop, stopping...\n" +#define RCMD_MSG_SVR_EXEC_REQ "rdsh-exec: %s\n" +#define RCMD_MSG_SVR_RC_CMD "rdsh-exec: rc = %d\n" + +//client prototypes for rsh_cli.c - - see documentation for each function to +//see what they do +int start_client(char *address, int port); +int client_cleanup(int cli_socket, char *cmd_buff, char *rsp_buff, int rc); +int exec_remote_cmd_loop(char *address, int port); + + +//server prototypes for rsh_server.c - see documentation for each function to +//see what they do +int start_server(char *ifaces, int port, int is_threaded); +int boot_server(char *ifaces, int port); +int stop_server(int svr_socket); +int send_message_eof(int cli_socket); +int send_message_string(int cli_socket, char *buff); +int process_cli_requests(int svr_socket); +int exec_client_requests(int cli_socket); +int rsh_execute_pipeline(int socket_fd, command_list_t *clist); + +Built_In_Cmds rsh_match_command(const char *input); +Built_In_Cmds rsh_built_in_cmd(cmd_buff_t *cmd); + +//eliminate from template, for extra credit +void set_threaded_server(int val); +int exec_client_thread(int main_socket, int cli_socket); +void *handle_client(void *arg); + +#endif