Skip to content
Snippets Groups Projects
Commit 5202f4ff authored by cxb23's avatar cxb23
Browse files

I completed the assignmeent as much as I could.

parent 475cbb53
Branches
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 ]
}
#!/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 ]
}
@%%%%
%%%%%%
%%%%%%
% %%%%%%% @
%%%%%%%%%% %%%%%%%
%%%%%%% %%%%@ %%%%%%%%%%%%@ %%%%%% @%%%%
%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%% %%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%% %%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%@ @%%%%%%%%%%%%%%%%%% %%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%@%%%%%%@
%%%%%%%%@ %%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%% %%
%%%%%%%%%%%%% %%@%%%%%%%%%%%% %%%%%%%%%%% %%%%%%%%%%%% @%
%%%%%%%%%% %%% %%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%% % %%%%%%%%%%%%% %%%%%%%%%%%%@%%%%%%%%%%%
%%%%%%%%%@ % %%%%%%%%%%%%% @%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%@ %%@%%%%%%%%%%%% @%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%@ %%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%% %%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%
%%%%%%%%%@ @%%%%%%%%%%%%%% %%%%%%%%%%%%@ %%%% %%%%%%%%%%%%%%%%% %%%%%%%%
%%%%%%%%%% %%%%%%%%%%%%%%%%% %%%%%%%%%%%%% %%%%%%%%%%%%%%%%%% %%%%%%%%%
%%%%%%%%%@%%@ %%%%%%%%%%%%%%%%@ %%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%% %%
%%%%%%%%%% % %%%%%%%%%%%%%%@ %%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%% %%
%%%%%%%%%%%% @ %%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%
%%%%%%%%%%%%% %% % %@ %%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%
%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%% @%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%
@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%% %%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%% @%%%%%%%%%
%%%%%%%%%%%%%%%%%%%% @%@% @%%%%%%%%%%%%%%%%%% %%%
%%%%%%%%%%%%%%% %%%%%%%%%% %%%%%%%%%%%%%%% %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%% %%%% %%% %%%%%%%%%% %%%@
%%%%%%%%%%%%%%%%%%% %%%%%% %% %%%%%%%%%%%%%@
%%%%%%%@
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);
}
#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;
}
#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
# 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
#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;
}
#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;
}
}
#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
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment