From d256c28b5444d97991e9f7b3a9a3e4280e1144dc Mon Sep 17 00:00:00 2001 From: vyvyvyThao <wn73@drexel.edu> Date: Thu, 13 Mar 2025 22:13:57 -0400 Subject: [PATCH] upload --- 6-RShell/bats/assignment_tests.sh | 36 ++ 6-RShell/bats/student_tests.sh | 148 ++++++ 6-RShell/dsh | Bin 0 -> 59104 bytes 6-RShell/dsh_cli.c | 149 ++++++ 6-RShell/dshlib.c | 365 +++++++++++++++ 6-RShell/dshlib.h | 92 ++++ 6-RShell/makefile | 31 ++ 6-RShell/questions.md | 19 + 6-RShell/rsh_cli.c | 258 +++++++++++ 6-RShell/rsh_server.c | 745 ++++++++++++++++++++++++++++++ 6-RShell/rshlib.h | 78 ++++ 11 files changed, 1921 insertions(+) create mode 100644 6-RShell/bats/assignment_tests.sh create mode 100644 6-RShell/bats/student_tests.sh create mode 100755 6-RShell/dsh create mode 100644 6-RShell/dsh_cli.c create mode 100644 6-RShell/dshlib.c create mode 100644 6-RShell/dshlib.h create mode 100644 6-RShell/makefile create mode 100644 6-RShell/questions.md create mode 100644 6-RShell/rsh_cli.c create mode 100644 6-RShell/rsh_server.c create mode 100644 6-RShell/rshlib.h diff --git a/6-RShell/bats/assignment_tests.sh b/6-RShell/bats/assignment_tests.sh new file mode 100644 index 0000000..7a8a30c --- /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 ] +} \ No newline at end of file diff --git a/6-RShell/bats/student_tests.sh b/6-RShell/bats/student_tests.sh new file mode 100644 index 0000000..48122f5 --- /dev/null +++ b/6-RShell/bats/student_tests.sh @@ -0,0 +1,148 @@ +#!/usr/bin/env bats + +# File: student_tests.sh +# Testing the remote shell server implementation + +# helper function to start server in background +setup() { + # create a test directory and files + mkdir -p rshell_test + cd rshell_test + echo "test content" > testfile.txt + + # start server in background with test port + ../dsh -s -p 5555 & + SERVER_PID=$! + sleep 1 +} + +# helper function to cleanup after each test +teardown() { + # kill server if it's running + if [ -n "$SERVER_PID" ]; then + kill $SERVER_PID 2>/dev/null || true + wait $SERVER_PID 2>/dev/null || true + fi + + # cleanup test directory + cd .. + rm -rf rshell_test +} + +@test "test_server_startup" { + # check if server process is running + ps -p $SERVER_PID + [ "$?" -eq 0 ] +} + +# test server file creation +@test "test_server_file_operations" { + # create a file through client command + echo "touch serverfile.txt" | ../dsh -c -p 5555 + + # verify file exists on server side + [ -f "serverfile.txt" ] +} + +# test server directory operations +@test "test_server_directory_operations" { + # create directory through client + echo "mkdir testdir" | ../dsh -c -p 5555 + + # verify directory exists on server side + [ -d "testdir" ] + + # change into directory and create file + { + echo "cd testdir" + echo "touch inside.txt" + } | ../dsh -c -p 5555 + + # verify file exists in correct location on server + [ -f "testdir/inside.txt" ] +} + +# test server file content modification +@test "test_server_file_modification" { + # create and write to file through client + { + echo "echo 'server content' > server_write.txt" + echo "exit" + } | ../dsh -c -p 5555 + + # verify file content on server side + [ -f "server_write.txt" ] + grep -q "server content" "server_write.txt" +} + +# test server pipe operations +@test "test_server_pipe_operations" { + # create test files on server side + echo "line1\nline2\nline3" > input.txt + + # use pipe operation through client + echo "cat input.txt | grep line2 > output.txt" | ../dsh -c -p 5555 + + # verify result on server side + [ -f "output.txt" ] + grep -q "line2" "output.txt" +} + +# test ls command +@test "test_server_ls" { + echo "ls > ls.txt" | ../dsh -c -p 5555 + [ -f "ls.txt" ] + grep -q "testfile.txt" "ls.txt" +} + +# test server stop command +@test "test_server_stop" { + # tend stop-server command + echo "stop-server" | ../dsh -c -p 5555 + + # wait + sleep 1 + + # verify server process is no longer running + ! ps -p $SERVER_PID +} + +# test server concurrent file access (if extra credit implemented) +# @test "test_server_concurrent_access" { +# # create a test file +# echo "initial" > /tmp/rshell_test/concurrent.txt + +# # start two clients modifying the same file +# { +# echo "echo 'client1' >> concurrent.txt" +# echo "exit" +# } | ../dsh -c -p 5555 & + +# { +# echo "echo 'client2' >> concurrent.txt" +# echo "exit" +# } | ../dsh -c -p 5555 & + +# # wait for both to complete +# wait + +# # verify both writes occurred on server side +# grep -q "client1" "/tmp/rshell_test/concurrent.txt" +# grep -q "client2" "/tmp/rshell_test/concurrent.txt" +# } + +# test server working directory persistence +@test "test_server_working_directory" { + # create nested directories + mkdir -p dir1/dir2 + + # change directory and verify through file creation + { + echo "cd dir1/dir2" + echo "pwd > pwd.txt" + } | ../dsh -c -p 5555 + + # verify pwd.txt exists in correct directory on server + [ -f "dir1/dir2/pwd.txt" ] + grep -q "dir2" "dir1/dir2/pwd.txt" +} diff --git a/6-RShell/dsh b/6-RShell/dsh new file mode 100755 index 0000000000000000000000000000000000000000..f0986ffee5e69623bdb243354d3231acdf40615c GIT binary patch literal 59104 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 literal 0 HcmV?d00001 diff --git a/6-RShell/dsh_cli.c b/6-RShell/dsh_cli.c new file mode 100644 index 0000000..68fab16 --- /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); +} \ No newline at end of file diff --git a/6-RShell/dshlib.c b/6-RShell/dshlib.c new file mode 100644 index 0000000..c242131 --- /dev/null +++ b/6-RShell/dshlib.c @@ -0,0 +1,365 @@ +#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 <errno.h> + +#include "dshlib.h" + +void printDragon(){ + const char dragon[39][103] = { +" @%%%% \n", +" %%%%%% \n", +" %%%%%% \n", +" % %%%%%%% @ \n", +" %%%%%%%%%% %%%%%%% \n", +" %%%%%%% %%%%@ %%%%%%%%%%%%@ %%%%%% @%%%% \n", +" %%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%% \n", +" %%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%% %%%%%%%%%%%%%%% \n", +" %%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%% %%% \n", +" %%%%%%%%%%%%%%%%%%%%%%%%%%%%@ @%%%%%%%%%%%%%%%%%% %% \n", +" %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%% \n", +" %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \n", +" %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%@%%%%%%@ \n", +" %%%%%%%%@ %%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%% %% \n", +" %%%%%%%%%%%%% %%@%%%%%%%%%%%% %%%%%%%%%%% %%%%%%%%%%%% @% \n", +" %%%%%%%%%% %%% %%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%% \n", +" %%%%%%%%% % %%%%%%%%%%%%% %%%%%%%%%%%%@%%%%%%%%%%% \n", +"%%%%%%%%%@ % %%%%%%%%%%%%% @%%%%%%%%%%%%%%%%%%%%%%%%% \n", +"%%%%%%%%@ %%@%%%%%%%%%%%% @%%%%%%%%%%%%%%%%%%%%%%%%%%%% \n", +"%%%%%%%@ %%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \n", +"%%%%%%%%%% %%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%% \n", +"%%%%%%%%%@ @%%%%%%%%%%%%%% %%%%%%%%%%%%@ %%%% %%%%%%%%%%%%%%%%% %%%%%%%%\n", +"%%%%%%%%%% %%%%%%%%%%%%%%%%% %%%%%%%%%%%%% %%%%%%%%%%%%%%%%%% %%%%%%%%%\n", +"%%%%%%%%%@%%@ %%%%%%%%%%%%%%%%@ %%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%% %%\n", +" %%%%%%%%%% % %%%%%%%%%%%%%%@ %%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%% %%\n", +" %%%%%%%%%%%% @ %%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% \n", +" %%%%%%%%%%%%% %% % %@ %%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% \n", +" %%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%% @%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%% \n", +" %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% \n", +" @%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%% \n", +" %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%% %%%%%%% \n", +" %%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%% @%%%%%%%%% \n", +" %%%%%%%%%%%%%%%%%%%% @%@% @%%%%%%%%%%%%%%%%%% %%% \n", +" %%%%%%%%%%%%%%% %%%%%%%%%% %%%%%%%%%%%%%%% % \n", +" %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%% \n", +" %%%%%%%%%%%%%%%%%%%%%%%%%% %%%% %%% %%%%%%%%%% %%%@ \n", +" %%%%%%%%%%%%%%%%%%% %%%%%% %% %%%%%%%%%%%%%@ \n", +" %%%%%%%@ \n" + }; + + + for (int i = 0; i < sizeof(dragon) / sizeof(dragon[0]); i++) { + for (int j = 0; j < sizeof(dragon[0]) / sizeof(dragon[0][0]); j++) { + if (dragon[i][j] == 0) break; + putchar((char)dragon[i][j]); + } + } +} + +/* + * 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 build_cmd_list(char *cmd_line, command_list_t *clist) +{ + if (strlen(cmd_line) == 0) { + printf(CMD_WARN_NO_CMD); + return OK; + } + + // Initialize command list + clist->num = 0; + memset(clist->commands, 0, sizeof(clist->commands)); + + // Split by pipe + char *saveptr1; // For the pipe tokenization + char *saveptr2; // For the argument tokenization + char *cmd_copy = strdup(cmd_line); + if (!cmd_copy) return ERR_MEMORY; + + char *cmd = strtok_r(cmd_copy, "|", &saveptr1); + while (cmd != NULL) { + // Trim leading/trailing spaces + while (*cmd == ' ') cmd++; + char *end = cmd + strlen(cmd) - 1; + while (end > cmd && *end == ' ') { + *end = '\0'; + end--; + } + + if (strlen(cmd) == 0) { + cmd = strtok_r(NULL, "|", &saveptr1); + continue; + } + + // Check command limit + if (clist->num >= CMD_MAX) { + free(cmd_copy); + return ERR_TOO_MANY_COMMANDS; + } + + // Parse command and arguments + cmd_buff_t *curr_cmd = &clist->commands[clist->num]; + curr_cmd->argc = 0; + + // Split command into arguments + char *arg = strtok_r(cmd, " ", &saveptr2); + while (arg != NULL) { + if (curr_cmd->argc >= CMD_ARGV_MAX) { + free(cmd_copy); + return ERR_CMD_OR_ARGS_TOO_BIG; + } + curr_cmd->argv[curr_cmd->argc++] = strdup(arg); + arg = strtok_r(NULL, " ", &saveptr2); + } + curr_cmd->argv[curr_cmd->argc] = NULL; + + clist->num++; + cmd = strtok_r(NULL, "|", &saveptr1); + } + + free(cmd_copy); + return OK; +} + +int exec_local_cmd_loop() +{ + char cmd_buff[SH_CMD_MAX]; + int rc = 0; + command_list_t clist; + memset(&clist, 0, sizeof(command_list_t)); + + while(1) { + printf("%s", SH_PROMPT); + + if (fgets(cmd_buff, SH_CMD_MAX, stdin) == NULL) { + printf("\n"); + break; + } + + // Remove trailing newline + cmd_buff[strcspn(cmd_buff, "\n")] = '\0'; + + // Skip empty commands + if (strlen(cmd_buff) == 0) { + printf(CMD_WARN_NO_CMD); + continue; + } + + // Parse commands by pipe + rc = build_cmd_list(cmd_buff, &clist); + if (rc != OK) { + if (rc == ERR_TOO_MANY_COMMANDS) { + printf(CMD_ERR_PIPE_LIMIT, CMD_MAX); + } + continue; + } + + // Handle built-in commands + if (clist.num == 1) { + if (strcmp(clist.commands[0].argv[0], EXIT_CMD) == 0) { + break; + } else if (strcmp(clist.commands[0].argv[0], "cd") == 0) { + if (clist.commands[0].argv[1]) { + if (chdir(clist.commands[0].argv[1]) != 0) { + perror("cd"); + rc = ERR_CMD_ARGS_BAD; + } + } + continue; + } else if (strcmp(clist.commands[0].argv[0], "dragon") == 0) { + printDragon(); + continue; + } else if (strcmp(clist.commands[0].argv[0], "rc") == 0) { + printf("%d\n", rc); + continue; + } + } + + // Handle external commands with pipes + pid_t pids[CMD_MAX]; + int pipes[CMD_MAX - 1][2]; + + // Create pipes + for (int i = 0; i < clist.num - 1; i++) { + if (pipe(pipes[i]) == -1) { + perror("pipe"); + rc = ERR_MEMORY; + continue; + } + } + + // Execute each command + for (int i = 0; i < clist.num; i++) { + pids[i] = fork(); + + if (pids[i] < 0) { + perror("fork"); + rc = ERR_MEMORY; + break; + } + + if (pids[i] == 0) { // Child process + // Set up pipe connections + if (i > 0) { + // Connect to previous pipe's read end + dup2(pipes[i-1][0], STDIN_FILENO); + } + + if (i < clist.num - 1) { + // Connect to next pipe's write end + dup2(pipes[i][1], STDOUT_FILENO); + } + + // Handle redirections by checking arguments + char *new_argv[CMD_ARGV_MAX]; + int new_argc = 0; + + for (int j = 0; j < clist.commands[i].argc; j++) { + char *arg = clist.commands[i].argv[j]; + + if (strcmp(arg, "<") == 0 && j + 1 < clist.commands[i].argc) { + // Input redirection + int fd = open(clist.commands[i].argv[j + 1], O_RDONLY); + if (fd == -1) { + perror("open input file"); + exit(1); + } + dup2(fd, STDIN_FILENO); + close(fd); + j++; // Skip the filename + continue; + } + + if (strcmp(arg, ">") == 0 && j + 1 < clist.commands[i].argc) { + // Output redirection (overwrite) + int fd = open(clist.commands[i].argv[j + 1], O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd == -1) { + perror("open output file"); + exit(1); + } + dup2(fd, STDOUT_FILENO); + close(fd); + j++; // Skip the filename + continue; + } + + if (strcmp(arg, ">>") == 0 && j + 1 < clist.commands[i].argc) { + // Output redirection (append) + int fd = open(clist.commands[i].argv[j + 1], O_WRONLY | O_CREAT | O_APPEND, 0644); + if (fd == -1) { + perror("open output file"); + exit(1); + } + dup2(fd, STDOUT_FILENO); + close(fd); + j++; // Skip the filename + continue; + } + + // If not a redirection operator, add to new argv + new_argv[new_argc++] = arg; + } + new_argv[new_argc] = NULL; + + // Close all pipe ends in child + for (int j = 0; j < clist.num - 1; j++) { + close(pipes[j][0]); + close(pipes[j][1]); + } + + // Execute the command with cleaned up arguments + execvp(new_argv[0], new_argv); + perror("execvp"); + exit(errno); + } + } + + // 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 to complete and get the last command's status + for (int i = 0; i < clist.num; i++) { + int status; + waitpid(pids[i], &status, 0); + if (i == clist.num - 1) { // Only use the last command's status + if (WIFEXITED(status)) { + rc = WEXITSTATUS(status); + } else { + rc = ERR_EXEC_CMD; + } + } + } + + } + + return OK; +} + +int free_cmd_list(command_list_t *cmd_lst) { + if (cmd_lst == NULL) { + return OK; + } + + // Free each command's arguments + for (int i = 0; i < cmd_lst->num; i++) { + cmd_buff_t *curr_cmd = &cmd_lst->commands[i]; + for (int j = 0; j < curr_cmd->argc; j++) { + free(curr_cmd->argv[j]); + } + curr_cmd->argv[curr_cmd->argc] = NULL; + curr_cmd->argc = 0; + } + + // Reset the number of commands + cmd_lst->num = 0; + return OK; +} diff --git a/6-RShell/dshlib.h b/6-RShell/dshlib.h new file mode 100644 index 0000000..b4cc21b --- /dev/null +++ b/6-RShell/dshlib.h @@ -0,0 +1,92 @@ +#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 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 + + + +//prototypes +int alloc_cmd_buff(cmd_buff_t *cmd_buff); +int free_cmd_buff(cmd_buff_t *cmd_buff); +int 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 build_cmd_list(char *cmd_line, command_list_t *clist); +int free_cmd_list(command_list_t *cmd_lst); + +//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_cmd(cmd_buff_t *cmd); +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 \ No newline at end of file diff --git a/6-RShell/makefile b/6-RShell/makefile new file mode 100644 index 0000000..b14b072 --- /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 \ No newline at end of file diff --git a/6-RShell/questions.md b/6-RShell/questions.md new file mode 100644 index 0000000..43ffc10 --- /dev/null +++ b/6-RShell/questions.md @@ -0,0 +1,19 @@ +1. How does the remote client determine when a command's output is fully received from the server, and what techniques can be used to handle partial reads or ensure complete message transmission? + +_answer here_ + +2. This week's lecture on TCP explains that it is a reliable stream protocol rather than a message-oriented one. Since TCP does not preserve message boundaries, how should a networked shell protocol define and detect the beginning and end of a command sent over a TCP connection? What challenges arise if this is not handled correctly? + +_answer here_ + +3. Describe the general differences between stateful and stateless protocols. + +_answer here_ + +4. Our lecture this week stated that UDP is "unreliable". If that is the case, why would we ever use it? + +_answer here_ + +5. What interface/abstraction is provided by the operating system to enable applications to use network communications? + +_answer here_ \ No newline at end of file diff --git a/6-RShell/rsh_cli.c b/6-RShell/rsh_cli.c new file mode 100644 index 0000000..35acdcf --- /dev/null +++ b/6-RShell/rsh_cli.c @@ -0,0 +1,258 @@ +#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; + int rc = OK; + + // Allocate buffers + cmd_buff = malloc(RDSH_COMM_BUFF_SZ); + rsp_buff = malloc(RDSH_COMM_BUFF_SZ); + if (cmd_buff == NULL || rsp_buff == NULL) { + return client_cleanup(-1, cmd_buff, rsp_buff, ERR_MEMORY); + } + + // Connect to server + 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) + { + printf(SH_PROMPT); + + // get command input + if (fgets(cmd_buff, RDSH_COMM_BUFF_SZ, stdin) == NULL) { + break; + } + + // remove trailing newline + cmd_buff[strcspn(cmd_buff, "\n")] = 0; + + // send command to server (including null terminator) + if (send(cli_socket, cmd_buff, strlen(cmd_buff) + 1, 0) < 0) { + perror("send"); + return client_cleanup(cli_socket, cmd_buff, rsp_buff, ERR_RDSH_COMMUNICATION); + } + + // receive response from server + while ((io_size = recv(cli_socket, rsp_buff, RDSH_COMM_BUFF_SZ, 0)) > 0) { + // check if this is the last chunk + is_eof = (rsp_buff[io_size-1] == RDSH_EOF_CHAR) ? 1 : 0; + + // print the received data + if (is_eof) { + printf("%.*s", (int)(io_size-1), rsp_buff); + } else { + printf("%.*s", (int)io_size, rsp_buff); + } + + // if EOF, receiving done + if (is_eof) { + break; + } + } + + if (io_size < 0) { + perror("recv"); + return client_cleanup(cli_socket, cmd_buff, rsp_buff, ERR_RDSH_COMMUNICATION); + } + + if (io_size == 0) { + // server closed connection + return client_cleanup(cli_socket, cmd_buff, rsp_buff, ERR_RDSH_COMMUNICATION); + } + + // exit command + if (strcmp(cmd_buff, EXIT_CMD) == 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; + int ret; + + // Create socket + cli_socket = socket(AF_INET, SOCK_STREAM, 0); + if (cli_socket == -1) { + perror("socket"); + return ERR_RDSH_CLIENT; + } + + // Set up address structure + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = inet_addr(server_ip); + addr.sin_port = htons(port); + + // Connect to server + ret = connect(cli_socket, (struct sockaddr *)&addr, sizeof(addr)); + if (ret == -1) { + perror("connect"); + close(cli_socket); + 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; +} \ No newline at end of file diff --git a/6-RShell/rsh_server.c b/6-RShell/rsh_server.c new file mode 100644 index 0000000..b0eb66c --- /dev/null +++ b/6-RShell/rsh_server.c @@ -0,0 +1,745 @@ +#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> +#include <limits.h> +#include <errno.h> // For errno + +//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; + int ret; + + struct sockaddr_in addr; + + // Create socket + svr_socket = socket(AF_INET, SOCK_STREAM, 0); + if (svr_socket == -1) { + perror("socket"); + return ERR_RDSH_COMMUNICATION; + } + + // Enable address reuse + int enable = 1; + if (setsockopt(svr_socket, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0) { + perror("setsockopt"); + close(svr_socket); + return ERR_RDSH_COMMUNICATION; + } + + // Set up address structure + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = inet_addr(ifaces); + addr.sin_port = htons(port); + + // Bind socket + if (bind(svr_socket, (struct sockaddr *)&addr, sizeof(addr)) == -1) { + perror("bind"); + close(svr_socket); + return ERR_RDSH_COMMUNICATION; + } + + /* + * Prepare for accepting connections. The backlog size is set + * to 20. So while one request is being processed other requests + * can be waiting. + */ + ret = listen(svr_socket, 20); + if (ret == -1) { + perror("listen"); + close(svr_socket); + 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){ + // Accept client connection + cli_socket = accept(svr_socket, NULL, NULL); + if (cli_socket == -1) { + perror("accept"); + return ERR_RDSH_COMMUNICATION; + } + + // Handle client requests + rc = exec_client_requests(cli_socket); + close(cli_socket); + + // If client requested server stop, break the loop + if (rc == OK_EXIT) { + break; + } + } + + 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; + char *io_buff; + int rc = OK; + command_list_t cmd_list; + Built_In_Cmds cmd_type; + char response[256]; + + io_buff = malloc(RDSH_COMM_BUFF_SZ); + if (io_buff == NULL){ + return ERR_RDSH_SERVER; + } + + while(1) { + // Receive message from client + io_size = recv(cli_socket, io_buff, RDSH_COMM_BUFF_SZ, 0); + if (io_size <= 0) { + if (io_size == 0) { + // Client closed connection + break; + } + perror("recv"); + free(io_buff); + return ERR_RDSH_COMMUNICATION; + } + + // Parse the command + if (build_cmd_list(io_buff, &cmd_list) != OK) { + // Send error message to client + send_message_string(cli_socket, CMD_WARN_NO_CMD); + send_message_eof(cli_socket); + continue; + } + + // Check if it's a built-in command + cmd_type = rsh_match_command(cmd_list.commands[0].argv[0]); + + switch (cmd_type) { + case BI_CMD_EXIT: + // Send goodbye message and close client connection + send_message_string(cli_socket, RCMD_MSG_CLIENT_EXITED); + send_message_eof(cli_socket); + free_cmd_list(&cmd_list); + free(io_buff); + close(cli_socket); // Close client connection + return OK; // Return OK to allow server to accept new connections + + case BI_CMD_STOP_SVR: + // Send goodbye message + send_message_string(cli_socket, RCMD_MSG_SVR_STOP_REQ); + send_message_eof(cli_socket); + free_cmd_list(&cmd_list); + free(io_buff); + return OK_EXIT; + + case BI_CMD_CD: + if (cmd_list.commands[0].argc < 2) { + // No directory specified, just print current directory + char cwd[4096]; + if (getcwd(cwd, sizeof(cwd)) != NULL) { + send_message_string(cli_socket, cwd); + } else { + send_message_string(cli_socket, "Error getting current directory"); + } + } else { + // Change to specified directory + if (chdir(cmd_list.commands[0].argv[1]) != 0) { + snprintf(response, sizeof(response), "cd: %s: %s\n", + cmd_list.commands[0].argv[1], strerror(errno)); + send_message_string(cli_socket, response); + } else { + char cwd[4096]; + if (getcwd(cwd, sizeof(cwd)) != NULL) { + send_message_string(cli_socket, cwd); + } + } + } + send_message_eof(cli_socket); + break; + + case BI_CMD_DRAGON: + // Create a temporary file to store dragon output + FILE *temp = tmpfile(); + if (temp != NULL) { + // Save stdout + int saved_stdout = dup(STDOUT_FILENO); + // Redirect stdout to temp file + dup2(fileno(temp), STDOUT_FILENO); + // Print dragon + printDragon(); + // Restore stdout + dup2(saved_stdout, STDOUT_FILENO); + close(saved_stdout); + // Reset file pointer + rewind(temp); + // Read and send dragon output + char dragon_buff[1024]; + size_t bytes_read; + while ((bytes_read = fread(dragon_buff, 1, sizeof(dragon_buff), temp)) > 0) { + if (send(cli_socket, dragon_buff, bytes_read, 0) != bytes_read) { + perror("send"); + free_cmd_list(&cmd_list); + free(io_buff); + return ERR_RDSH_COMMUNICATION; + } + } + } + send_message_eof(cli_socket); + break; + + case BI_CMD_RC: + snprintf(response, sizeof(response), RCMD_MSG_SVR_RC_CMD, rc); + send_message_string(cli_socket, response); + send_message_eof(cli_socket); + break; + + default: + // Execute non-built-in commands using pipeline + rc = rsh_execute_pipeline(cli_socket, &cmd_list); + if (rc < 0) { + free_cmd_list(&cmd_list); + free(io_buff); + return rc; + } + send_message_eof(cli_socket); + break; + } + + free_cmd_list(&cmd_list); + } + + free(io_buff); + return rc; +} + +/* + * 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; + 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){ + int send_len = strlen(buff); + int sent_len; + + // Send the message + sent_len = send(cli_socket, buff, send_len, 0); + if (sent_len != send_len) { + return ERR_RDSH_COMMUNICATION; + } + + // Send EOF character + return send_message_eof(cli_socket); +} + + +/* + * 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 + int exit_code; + + // Create all necessary pipes + for (int i = 0; i < clist->num - 1; i++) { + if (pipe(pipes[i]) == -1) { + perror("pipe"); + return ERR_RDSH_COMMUNICATION; + } + } + + for (int i = 0; i < clist->num; i++) { + pids[i] = fork(); + + if (pids[i] < 0) { + perror("fork"); + return ERR_RDSH_COMMUNICATION; + } + + if (pids[i] == 0) { // Child process + // First process: redirect stdin from client socket + if (i == 0) { + dup2(cli_sock, STDIN_FILENO); + close(cli_sock); + } + + // Last process: redirect stdout and stderr to client socket + if (i == clist->num - 1) { + dup2(cli_sock, STDOUT_FILENO); + dup2(cli_sock, STDERR_FILENO); + close(cli_sock); + } + + // Set up pipes between processes + if (i > 0) { + dup2(pipes[i-1][0], STDIN_FILENO); + close(pipes[i-1][0]); + close(pipes[i-1][1]); + } + if (i < clist->num - 1) { + dup2(pipes[i][1], STDOUT_FILENO); + close(pipes[i][0]); + close(pipes[i][1]); + } + + // Close all other pipe ends + for (int j = 0; j < clist->num - 1; j++) { + close(pipes[j][0]); + close(pipes[j][1]); + } + + // Handle redirections by checking arguments + char *new_argv[CMD_ARGV_MAX]; + int new_argc = 0; + + for (int j = 0; j < clist->commands[i].argc; j++) { + char *arg = clist->commands[i].argv[j]; + + if (strcmp(arg, "<") == 0 && j + 1 < clist->commands[i].argc) { + // Input redirection + int fd = open(clist->commands[i].argv[j + 1], O_RDONLY); + if (fd == -1) { + perror("open input file"); + exit(1); + } + dup2(fd, STDIN_FILENO); + close(fd); + j++; // Skip the filename + continue; + } + + if (strcmp(arg, ">") == 0 && j + 1 < clist->commands[i].argc) { + // Output redirection (overwrite) + int fd = open(clist->commands[i].argv[j + 1], O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd == -1) { + perror("open output file"); + exit(1); + } + dup2(fd, STDOUT_FILENO); + close(fd); + j++; // Skip the filename + continue; + } + + if (strcmp(arg, ">>") == 0 && j + 1 < clist->commands[i].argc) { + // Output redirection (append) + int fd = open(clist->commands[i].argv[j + 1], O_WRONLY | O_CREAT | O_APPEND, 0644); + if (fd == -1) { + perror("open output file"); + exit(1); + } + dup2(fd, STDOUT_FILENO); + close(fd); + j++; // Skip the filename + continue; + } + + // If not a redirection operator, add to new argv + new_argv[new_argc++] = arg; + } + new_argv[new_argc] = NULL; + + // Execute the command with cleaned up arguments + execvp(new_argv[0], new_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); + } + + // Get exit code of last process + exit_code = WEXITSTATUS(pids_st[clist->num - 1]); + + // Check for EXIT_SC in any process + for (int i = 0; i < clist->num; i++) { + if (WEXITSTATUS(pids_st[i]) == EXIT_SC) { + exit_code = EXIT_SC; + break; + } + } + + 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 0000000..6ae2ca9 --- /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 \ No newline at end of file -- GitLab