Skip to content
Snippets Groups Projects
Commit d256c28b authored by Wendy Nguyen's avatar Wendy Nguyen
Browse files

upload

parent c9c9521d
No related branches found
No related tags found
No related merge requests found
#!/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
#!/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"
}
File added
#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
#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;
}
#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
# 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
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
#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
This diff is collapsed.
#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
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment