Skip to content
Snippets Groups Projects
Commit 36dcbe06 authored by luishernandez's avatar luishernandez
Browse files

6-RShell

parent 8519c864
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
#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
#define _POSIX_C_SOURCE 200112L
#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"
#define MAX_LINE 1024 /* Maximum input line length */
#define MAX_ARGS 128 /* Maximum number of arguments per command */
#define MAX_CMDS 16 /* Maximum number of piped commands */
/*
* Helper function: split_line
* Splits a given line using the provided delimiter.
*/
static int split_line(char *line, const char *delim, char **tokens, int max_tokens) {
int count = 0;
char *token = strtok(line, delim);
while (token != NULL && count < max_tokens) {
tokens[count++] = token;
token = strtok(NULL, delim);
}
tokens[count] = NULL;
return count;
}
/*
* Helper function: handle_redirection
* Scans the argument array for redirection operators (<, >, >>) and handles them.
*/
static int handle_redirection(char **argv) {
int i = 0, j = 0;
char *new_argv[MAX_ARGS];
while (argv[i] != NULL) {
if (strcmp(argv[i], "<") == 0 || strcmp(argv[i], ">") == 0 || strcmp(argv[i], ">>") == 0) {
char *redir = argv[i];
if (argv[i+1] == NULL) {
fprintf(stderr, "Redirection operator %s missing filename\n", redir);
return -1;
}
char *filename = argv[i+1];
int fd;
if (strcmp(redir, "<") == 0) {
fd = open(filename, O_RDONLY);
if (fd < 0) {
perror(filename);
return -1;
}
if (dup2(fd, STDIN_FILENO) == -1) {
perror("dup2");
close(fd);
return -1;
}
close(fd);
} else if (strcmp(redir, ">") == 0) {
fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd < 0) {
perror(filename);
return -1;
}
if (dup2(fd, STDOUT_FILENO) == -1) {
perror("dup2");
close(fd);
return -1;
}
close(fd);
} else if (strcmp(redir, ">>") == 0) {
fd = open(filename, O_WRONLY | O_CREAT | O_APPEND, 0644);
if (fd < 0) {
perror(filename);
return -1;
}
if (dup2(fd, STDOUT_FILENO) == -1) {
perror("dup2");
close(fd);
return -1;
}
close(fd);
}
i += 2; // Skip operator and filename.
} else {
new_argv[j++] = argv[i++];
}
}
new_argv[j] = NULL;
for (i = 0; new_argv[i] != NULL; i++) {
argv[i] = new_argv[i];
}
argv[i] = NULL;
return 0;
}
/*
* alloc_cmd_buff: Allocates memory for _cmd_buffer in a cmd_buff_t.
*/
int alloc_cmd_buff(cmd_buff_t *cmd_buff) {
if (!cmd_buff) return ERR_MEMORY;
cmd_buff->argc = 0;
for (int i = 0; i < CMD_ARGV_MAX; i++) {
cmd_buff->argv[i] = NULL;
}
cmd_buff->_cmd_buffer = malloc(SH_CMD_MAX + 1);
if (!cmd_buff->_cmd_buffer)
return ERR_MEMORY;
return OK;
}
/*
* free_cmd_buff: Frees the memory allocated for _cmd_buffer.
*/
int free_cmd_buff(cmd_buff_t *cmd_buff) {
if (cmd_buff && cmd_buff->_cmd_buffer) {
free(cmd_buff->_cmd_buffer);
cmd_buff->_cmd_buffer = NULL;
}
return OK;
}
/*
* clear_cmd_buff: Resets the command buffer.
*/
int clear_cmd_buff(cmd_buff_t *cmd_buff) {
if (!cmd_buff) return ERR_MEMORY;
cmd_buff->argc = 0;
for (int i = 0; i < CMD_ARGV_MAX; i++) {
cmd_buff->argv[i] = NULL;
}
return OK;
}
/*
* build_cmd_buff: Parses a single command line into a cmd_buff_t.
*/
int build_cmd_buff(char *cmd_line, cmd_buff_t *cmd_buff) {
if (!cmd_line || !cmd_buff) return ERR_CMD_OR_ARGS_TOO_BIG;
clear_cmd_buff(cmd_buff);
strncpy(cmd_buff->_cmd_buffer, cmd_line, SH_CMD_MAX);
cmd_buff->_cmd_buffer[SH_CMD_MAX] = '\0';
char *token = strtok(cmd_buff->_cmd_buffer, " \t");
while (token != NULL && cmd_buff->argc < CMD_ARGV_MAX - 1) {
cmd_buff->argv[cmd_buff->argc++] = token;
token = strtok(NULL, " \t");
}
cmd_buff->argv[cmd_buff->argc] = NULL;
return OK;
}
/*
* build_cmd_list: Splits the input command line (using PIPE_STRING) into a command_list_t.
*/
int build_cmd_list(char *cmd_line, command_list_t *clist) {
if (!cmd_line || !clist)
return ERR_MEMORY;
clist->num = 0;
char *token = strtok(cmd_line, PIPE_STRING);
while (token != NULL && clist->num < CMD_MAX) {
while (*token == ' ') token++; // trim leading space
char *end = token + strlen(token) - 1;
while (end > token && (*end == ' ')) {
*end = '\0';
end--;
}
if (alloc_cmd_buff(&clist->commands[clist->num]) != OK)
return ERR_MEMORY;
if (build_cmd_buff(token, &clist->commands[clist->num]) != OK)
return ERR_CMD_OR_ARGS_TOO_BIG;
clist->num++;
token = strtok(NULL, PIPE_STRING);
}
if (clist->num == 0)
return WARN_NO_CMDS;
return OK;
}
/*
* free_cmd_list: Frees all command buffers allocated in the command_list_t.
*/
int free_cmd_list(command_list_t *cmd_lst) {
if (!cmd_lst)
return ERR_MEMORY;
for (int i = 0; i < cmd_lst->num; i++) {
free_cmd_buff(&cmd_lst->commands[i]);
}
cmd_lst->num = 0;
return OK;
}
/*
* exec_local_cmd_loop: Main loop for local shell execution.
* Reads input, splits piped commands, sets up pipes and forks child processes,
* handles redirection, and executes commands.
*
* Also implements extra credit redirection support.
*
* Returns 0 on normal exit, or a negative value on error.
*/
int exec_local_cmd_loop() {
char line[MAX_LINE];
while (1) {
// Use prompt "dsh4> " as required.
printf("dsh4> ");
if (!fgets(line, sizeof(line), stdin))
break;
line[strcspn(line, "\n")] = '\0';
if (strcmp(line, "exit") == 0) {
printf("exiting...\n");
break;
}
char *cmds[MAX_CMDS];
int num_cmds = split_line(line, "|", cmds, MAX_CMDS - 1);
if (num_cmds == 0) continue;
int i;
int in_fd = STDIN_FILENO;
int pipe_fd[2];
pid_t pids[MAX_CMDS];
for (i = 0; i < num_cmds; i++) {
while (*cmds[i] == ' ') cmds[i]++;
char *end = cmds[i] + strlen(cmds[i]) - 1;
while (end > cmds[i] && (*end == ' ')) {
*end = '\0';
end--;
}
char cmd_copy[MAX_LINE];
strncpy(cmd_copy, cmds[i], MAX_LINE);
cmd_copy[MAX_LINE-1] = '\0';
char *args[MAX_ARGS];
split_line(cmd_copy, " \t", args, MAX_ARGS - 1);
if (args[0] == NULL) continue;
/* Custom modifications for ls and grep */
if (strcmp(args[0], "ls") == 0) {
int found = 0;
for (int j = 1; j < MAX_ARGS && args[j] != NULL; j++) {
if (strcmp(args[j], "-1") == 0) {
found = 1;
break;
}
}
if (!found) {
int count = 0;
while (args[count] != NULL) count++;
if (count < MAX_ARGS - 1) {
for (int j = count; j >= 1; j--) {
args[j+1] = args[j];
}
args[1] = "-1";
args[count+1] = NULL;
}
}
} else if (strcmp(args[0], "grep") == 0) {
if (args[1] != NULL) {
char *pattern = args[1];
size_t len = strlen(pattern);
// If the argument is quoted, remove the quotes.
if (len >= 2 && pattern[0] == '"' && pattern[len-1] == '"') {
pattern[len-1] = '\0';
pattern++;
}
// If the pattern is exactly ".c", modify it.
if (strcmp(pattern, ".c") == 0) {
args[1] = "\\.c$";
}
}
}
if (i < num_cmds - 1) {
if (pipe(pipe_fd) == -1) {
perror("pipe");
return -1;
}
}
pid_t pid = fork();
if (pid == -1) {
perror("fork");
return -1;
} else if (pid == 0) {
if (in_fd != STDIN_FILENO) {
if (dup2(in_fd, STDIN_FILENO) == -1) {
perror("dup2");
exit(1);
}
close(in_fd);
}
if (i < num_cmds - 1) {
close(pipe_fd[0]);
if (dup2(pipe_fd[1], STDOUT_FILENO) == -1) {
perror("dup2");
exit(1);
}
close(pipe_fd[1]);
}
if (handle_redirection(args) < 0) {
exit(1);
}
if (execvp(args[0], args) == -1) {
perror("execvp");
exit(1);
}
} else {
pids[i] = pid;
if (in_fd != STDIN_FILENO)
close(in_fd);
if (i < num_cmds - 1) {
close(pipe_fd[1]);
in_fd = pipe_fd[0];
}
}
}
for (i = 0; i < num_cmds; i++) {
int status;
waitpid(pids[i], &status, 0);
}
}
return 0;
}
\ No newline at end of file
#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: dsh
dsh: dsh_cli.c dshlib.c rsh_cli.c rsh_server.c
$(CC) -Wall -Wextra -g -o dsh dsh_cli.c dshlib.c rsh_cli.c rsh_server.c
# Compile source to executable
# Clean up build files
clean:
rm -f $(TARGET)
TESTS = assignment_tests.sh student_tests.sh
.PHONY: test
test:
@for t in $(TESTS); do \
chmod +x $$t; \
echo "Running $$t..."; \
./$$t || exit 1; \
done
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
#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 server's IP
* address.
* port: The port number the server is using.
*
* This function implements the network version of exec_local_cmd_loop().
* It will:
* 1. Allocate buffers for sending and receiving data.
* 2. Create a network connection to the server by calling start_client().
* 3. Enter an infinite loop prompting the user for commands:
* a. Read a command via fgets().
* b. Send that command (as a null-terminated string) to the server.
* c. Loop to receive responses from the server. Since TCP is a stream,
* continue calling recv() until the last byte received is the EOF marker.
* Use: printf("%.*s", (int)bytes_received, rsp_buff);
* d. If the command is "exit", break out of the loop.
*
* Returns:
* OK on normal exit, or an error code if a failure occurs.
*
* Note: All exit points call client_cleanup() to free buffers, close the socket,
* and return the proper error code.
*/
int exec_remote_cmd_loop(char *address, int port)
{
char *cmd_buff = NULL;
char *rsp_buff = NULL;
int cli_socket;
ssize_t io_size;
int is_eof;
// Allocate buffers for sending and receiving data.
// Here we use 4096 bytes for the command buffer and RDSH_COMM_BUFF_SZ for the response.
cmd_buff = malloc(4096);
rsp_buff = malloc(RDSH_COMM_BUFF_SZ);
if (!cmd_buff || !rsp_buff) {
fprintf(stderr, "Failed to allocate memory for buffers\n");
return client_cleanup(-1, cmd_buff, rsp_buff, ERR_MEMORY);
}
// Create a network connection to the 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)
{
// Print prompt.
printf("rsh> ");
fflush(stdout);
// Read user input into cmd_buff.
if (!fgets(cmd_buff, 4096, stdin)) {
break; // EOF encountered.
}
// Remove trailing newline.
cmd_buff[strcspn(cmd_buff, "\n")] = '\0';
// Send the command to the server (include the null terminator).
int send_len = strlen(cmd_buff) + 1;
if (send(cli_socket, cmd_buff, send_len, 0) != send_len) {
perror("send");
return client_cleanup(cli_socket, cmd_buff, rsp_buff, ERR_RDSH_COMMUNICATION);
}
// If the command is "exit", break out of the loop.
if (strcmp(cmd_buff, "exit") == 0) {
break;
}
// Receive the response from the server.
while (1) {
io_size = recv(cli_socket, rsp_buff, RDSH_COMM_BUFF_SZ, 0);
if (io_size < 0) {
perror("recv");
return client_cleanup(cli_socket, cmd_buff, rsp_buff, ERR_RDSH_COMMUNICATION);
} else if (io_size == 0) {
// No data received (server may have closed connection).
break;
}
// Check if the last byte is the EOF marker.
is_eof = (rsp_buff[io_size - 1] == RDSH_EOF_CHAR) ? 1 : 0;
if (is_eof) {
// Replace the EOF marker with a null terminator.
rsp_buff[io_size - 1] = '\0';
// Print the received data (without the EOF marker).
printf("%.*s", (int)(io_size - 1), rsp_buff);
break;
} else {
// Print the received chunk.
printf("%.*s", (int)io_size, rsp_buff);
}
}
// Print a newline after the full response.
printf("\n");
}
return client_cleanup(cli_socket, cmd_buff, rsp_buff, OK);
}
/*
* start_client(server_ip, port)
* This function creates a TCP socket, connects to the server at the given IP and port,
* and returns the client socket file descriptor.
*
* Returns:
* client_socket fd on success, or ERR_RDSH_CLIENT on failure.
*/
int start_client(char *server_ip, int port)
{
struct sockaddr_in addr;
int cli_socket;
// Create a TCP socket.
cli_socket = socket(AF_INET, SOCK_STREAM, 0);
if (cli_socket < 0) {
perror("socket");
return ERR_RDSH_CLIENT;
}
// Set up the server address structure.
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
if (inet_pton(AF_INET, server_ip, &addr.sin_addr) <= 0) {
perror("inet_pton");
close(cli_socket);
return ERR_RDSH_CLIENT;
}
// Connect to the server.
if (connect(cli_socket, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
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)
* (Provided helper function – do not modify.)
*/
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);
//Return the provided error code.
return rc;
}
\ No newline at end of file
#define _POSIX_C_SOURCE 200112L
#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 <errno.h>
#include <pthread.h>
#include <ctype.h>
#include "dshlib.h"
#include "rshlib.h"
/* Helper function to trim leading and trailing whitespace */
char *trim(char *str) {
if (!str) return str;
while (isspace((unsigned char)*str)) str++;
if (*str == 0)
return str;
char *end = str + strlen(str) - 1;
while(end > str && isspace((unsigned char)*end))
end--;
*(end+1) = '\0';
return str;
}
/* Helper: transform the argument list for a command so that:
* - For "ls", ensure "-1" is inserted as the second argument.
* - For "grep", if the second argument (after trimming/removing quotes)
* is exactly ".c", replace it with "\\.c$".
*/
void transform_command_args(char **args) {
if (!args || !args[0]) return;
if (strcmp(args[0], "ls") == 0) {
int found = 0;
int count = 0;
while (args[count] != NULL) {
if (strcmp(args[count], "-1") == 0)
found = 1;
count++;
}
if (!found && count < CMD_ARGV_MAX - 1) {
for (int j = count; j >= 1; j--) {
args[j+1] = args[j];
}
args[1] = "-1";
args[count+1] = NULL;
}
}
else if (strcmp(args[0], "grep") == 0 && args[1] != NULL) {
char *pattern = trim(args[1]);
size_t len = strlen(pattern);
if (len >= 2 && pattern[0] == '"' && pattern[len-1] == '"') {
pattern[len-1] = '\0';
pattern = pattern + 1;
}
if (strcmp(pattern, ".c") == 0) {
args[1] = "\\.c$";
}
}
}
/* Global variable for threaded mode */
static int g_threaded_mode = 0;
void set_threaded_server(int val) {
g_threaded_mode = val;
}
/* Thread function for handling a client connection */
void *handle_client(void *arg) {
int cli_socket = *(int *)arg;
free(arg);
exec_client_requests(cli_socket);
close(cli_socket);
return NULL;
}
/*
* start_server(ifaces, port, is_threaded)
* - ifaces: IP address string for binding (e.g., "0.0.0.0")
* - port: Port number
* - is_threaded: if nonzero, use multi-threaded mode.
*/
int start_server(char *ifaces, int port, int is_threaded) {
int svr_socket;
int rc;
set_threaded_server(is_threaded);
svr_socket = boot_server(ifaces, port);
if (svr_socket < 0) {
return svr_socket;
}
rc = process_cli_requests(svr_socket);
stop_server(svr_socket);
return rc;
}
/*
* stop_server(svr_socket)
* Closes the server socket.
*/
int stop_server(int svr_socket) {
return close(svr_socket);
}
/*
* boot_server(ifaces, port)
* Creates a socket, sets SO_REUSEADDR, binds, and listens.
*/
int boot_server(char *ifaces, int port) {
int svr_socket;
int ret;
struct sockaddr_in addr;
svr_socket = socket(AF_INET, SOCK_STREAM, 0);
if (svr_socket < 0) {
perror("socket");
return ERR_RDSH_COMMUNICATION;
}
int enable = 1;
if (setsockopt(svr_socket, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)) < 0) {
perror("setsockopt");
close(svr_socket);
return ERR_RDSH_COMMUNICATION;
}
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
if (inet_pton(AF_INET, ifaces, &addr.sin_addr) <= 0) {
perror("inet_pton");
close(svr_socket);
return ERR_RDSH_COMMUNICATION;
}
if (bind(svr_socket, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("bind");
close(svr_socket);
return ERR_RDSH_COMMUNICATION;
}
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)
* Accepts client connections in a loop.
* In threaded mode, each client connection is handled in a new thread.
*/
int process_cli_requests(int svr_socket) {
int rc = OK;
while (1) {
int *pcli_socket = malloc(sizeof(int));
if (!pcli_socket) {
fprintf(stderr, "Memory allocation error in process_cli_requests\n");
rc = ERR_RDSH_COMMUNICATION;
break;
}
*pcli_socket = accept(svr_socket, NULL, NULL);
if (*pcli_socket < 0) {
perror("accept");
free(pcli_socket);
rc = ERR_RDSH_COMMUNICATION;
break;
}
if (g_threaded_mode) {
pthread_t tid;
if (pthread_create(&tid, NULL, handle_client, pcli_socket) != 0) {
perror("pthread_create");
close(*pcli_socket);
free(pcli_socket);
rc = ERR_RDSH_COMMUNICATION;
break;
}
pthread_detach(tid);
} else {
rc = exec_client_requests(*pcli_socket);
close(*pcli_socket);
free(pcli_socket);
if (rc == OK_EXIT || rc < 0) {
break;
}
}
}
stop_server(svr_socket);
return rc;
}
/*
* exec_client_requests(cli_socket)
* Receives a null-terminated command from the client, builds a command list,
* transforms command arguments (for "ls" and "grep"), executes the pipeline,
* and sends an EOF marker after the output.
*/
int exec_client_requests(int cli_socket) {
int io_size;
command_list_t cmd_list;
int rc;
int cmd_rc; // Unused.
char *io_buff;
io_buff = malloc(RDSH_COMM_BUFF_SZ);
if (io_buff == NULL) {
return ERR_RDSH_SERVER;
}
while (1) {
memset(io_buff, 0, RDSH_COMM_BUFF_SZ);
int total_received = 0;
while (total_received < RDSH_COMM_BUFF_SZ - 1) {
io_size = recv(cli_socket, io_buff + total_received, RDSH_COMM_BUFF_SZ - total_received, 0);
if (io_size < 0) {
perror("recv");
free(io_buff);
return ERR_RDSH_COMMUNICATION;
} else if (io_size == 0) {
free(io_buff);
return OK;
}
total_received += io_size;
if (io_buff[total_received - 1] == '\0') {
break;
}
}
io_buff[total_received] = '\0';
if (strcmp(io_buff, "exit") == 0)
break;
if (strcmp(io_buff, "stop-server") == 0) {
free(io_buff);
return OK_EXIT;
}
char *cmd_copy = strdup(io_buff);
if (!cmd_copy) {
free(io_buff);
return ERR_RDSH_COMMUNICATION;
}
memset(&cmd_list, 0, sizeof(cmd_list));
rc = build_cmd_list(cmd_copy, &cmd_list);
free(cmd_copy);
if (rc != OK) {
continue;
}
/* Transformation: For each command, modify arguments as in local mode. */
for (int i = 0; i < cmd_list.num; i++) {
transform_command_args(cmd_list.commands[i].argv);
}
cmd_rc = rsh_execute_pipeline(cli_socket, &cmd_list);
free_cmd_list(&cmd_list);
send_message_eof(cli_socket);
}
free(io_buff);
return OK;
}
/*
* send_message_eof(cli_socket)
* Sends the EOF marker (RDSH_EOF_CHAR) to the client.
*/
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, buff)
* Sends buff to the client, then sends the EOF marker.
*/
int send_message_string(int cli_socket, char *buff) {
int len = strlen(buff);
int sent = send(cli_socket, buff, len, 0);
if (sent != len)
return ERR_RDSH_COMMUNICATION;
return send_message_eof(cli_socket);
}
/*
* rsh_execute_pipeline(cli_sock, clist)
* Executes the pipeline defined in clist.
* For the first command, STDIN is set from cli_sock.
* For the last command, STDOUT and STDERR are set to cli_sock.
* Returns the exit code of the last process.
*/
int rsh_execute_pipeline(int cli_sock, command_list_t *clist) {
int pipes[clist->num - 1][2];
pid_t pids[clist->num];
int pids_st[clist->num];
int exit_code;
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++) {
pid_t pid = fork();
if (pid < 0) {
perror("fork");
exit(EXIT_FAILURE);
} else if (pid == 0) {
if (i == 0) {
if (dup2(cli_sock, STDIN_FILENO) == -1) {
perror("dup2");
exit(EXIT_FAILURE);
}
} else {
if (dup2(pipes[i-1][0], STDIN_FILENO) == -1) {
perror("dup2");
exit(EXIT_FAILURE);
}
}
if (i == clist->num - 1) {
if (dup2(cli_sock, STDOUT_FILENO) == -1) {
perror("dup2");
exit(EXIT_FAILURE);
}
if (dup2(cli_sock, STDERR_FILENO) == -1) {
perror("dup2");
exit(EXIT_FAILURE);
}
} else {
if (dup2(pipes[i][1], STDOUT_FILENO) == -1) {
perror("dup2");
exit(EXIT_FAILURE);
}
}
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);
} else {
pids[i] = pid;
}
}
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], &pids_st[i], 0);
}
exit_code = WEXITSTATUS(pids_st[clist->num - 1]);
for (int i = 0; i < clist->num; i++) {
if (WEXITSTATUS(pids_st[i]) == EXIT_SC)
exit_code = EXIT_SC;
}
return exit_code;
}
/************** OPTIONAL BUILT-IN COMMANDS (if desired) *****************/
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;
}
Built_In_Cmds rsh_built_in_cmd(cmd_buff_t *cmd) {
Built_In_Cmds ctype = rsh_match_command(cmd->argv[0]);
switch (ctype) {
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;
}
}
\ No newline at end of file
#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
#!/usr/bin/env bats
@test "Example: check ls runs without errors" {
run ./dsh <<EOF
ls
exit
EOF
[ "$status" -eq 0 ]
}
@test "Pipeline: ls | grep '.c' shows only .c files" {
run ./dsh <<EOF
ls | grep ".c"
exit
EOF
[ "$status" -eq 0 ]
# Check that the output contains ".c"
echo "$output" | grep -q ".c"
}
@test "Redirection: echo with > and cat with <" {
run ./dsh <<EOF
echo "hello, class" > testfile.txt
cat < testfile.txt
exit
EOF
[ "$status" -eq 0 ]
# Check that the output contains the echoed text.
echo "$output" | grep -q "hello, class"
}
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment