From 40b9213fb2f1323b42dddf2524aa59e6c40a9b2f Mon Sep 17 00:00:00 2001
From: jl4589 <jl4589@drexel.edu>
Date: Mon, 10 Mar 2025 10:41:51 -0400
Subject: [PATCH] Added Files for Assignment-06

---
 Assignment-06/questions.md                    |  19 +
 Assignment-06/readme.md                       | 278 ++++++++++
 Assignment-06/starter/.debug/launch.json      |  29 +
 Assignment-06/starter/.debug/tasks.json       |  20 +
 .../starter/bats/assignment_tests (2).sh      |  36 ++
 .../starter/bats/student_tests (2).sh         |  14 +
 Assignment-06/starter/dsh_cli.c               | 149 ++++++
 Assignment-06/starter/dshlib.c                |  81 +++
 Assignment-06/starter/dshlib.h                |  92 ++++
 Assignment-06/starter/gitignore               |   1 +
 Assignment-06/starter/makefile                |  31 ++
 Assignment-06/starter/rsh_cli.c               | 195 +++++++
 Assignment-06/starter/rsh_server.c            | 501 ++++++++++++++++++
 Assignment-06/starter/rshlib.h                |  78 +++
 14 files changed, 1524 insertions(+)
 create mode 100644 Assignment-06/questions.md
 create mode 100644 Assignment-06/readme.md
 create mode 100644 Assignment-06/starter/.debug/launch.json
 create mode 100644 Assignment-06/starter/.debug/tasks.json
 create mode 100644 Assignment-06/starter/bats/assignment_tests (2).sh
 create mode 100644 Assignment-06/starter/bats/student_tests (2).sh
 create mode 100644 Assignment-06/starter/dsh_cli.c
 create mode 100644 Assignment-06/starter/dshlib.c
 create mode 100644 Assignment-06/starter/dshlib.h
 create mode 100644 Assignment-06/starter/gitignore
 create mode 100644 Assignment-06/starter/makefile
 create mode 100644 Assignment-06/starter/rsh_cli.c
 create mode 100644 Assignment-06/starter/rsh_server.c
 create mode 100644 Assignment-06/starter/rshlib.h

diff --git a/Assignment-06/questions.md b/Assignment-06/questions.md
new file mode 100644
index 0000000..43ffc10
--- /dev/null
+++ b/Assignment-06/questions.md
@@ -0,0 +1,19 @@
+1. How does the remote client determine when a command's output is fully received from the server, and what techniques can be used to handle partial reads or ensure complete message transmission?
+
+_answer here_
+
+2. This week's lecture on TCP explains that it is a reliable stream protocol rather than a message-oriented one. Since TCP does not preserve message boundaries, how should a networked shell protocol define and detect the beginning and end of a command sent over a TCP connection? What challenges arise if this is not handled correctly?
+
+_answer here_
+
+3. Describe the general differences between stateful and stateless protocols.
+
+_answer here_
+
+4. Our lecture this week stated that UDP is "unreliable". If that is the case, why would we ever use it?
+
+_answer here_
+
+5. What interface/abstraction is provided by the operating system to enable applications to use network communications?
+
+_answer here_
\ No newline at end of file
diff --git a/Assignment-06/readme.md b/Assignment-06/readme.md
new file mode 100644
index 0000000..70880aa
--- /dev/null
+++ b/Assignment-06/readme.md
@@ -0,0 +1,278 @@
+## Remote Drexel Shell
+
+This work builds on your previous shell version that executed commands and supported pipes.  Except this time, we will enable the shell program to be executed over a network.  To do this we will enhance our `dsh` executable to accept some parameters.  Specifically, `dsh` should mirror the behavior of the piped version of your shell, performing local `fork/exec` to run commands, including built-in commands.  However, `dsh_cli.c` has been enhanced to support additional command line arguments:
+
+```bash
+#start dsh in server mode
+./dsh -s 
+#note you can override:
+# -i:  Overrides default interface, e.g., -i 127.0.0.1
+# -p:  Overrides default port, e.g., -p 5678
+```
+
+The starter code provides information and defaults for both the `interfaces` argument as well as a default `port`.  Note that server interfaces are specified in terms of IP addresses, and almost always we use the value of `0.0.0.0` for the server to indicate that the server should bind on all available network interfaces. 
+
+```bash
+# start dsh in client mode
+./dsh -c 
+#note like server-mode you can override:
+# -i:  Overrides default ip address of the server, e.g., -i 129.25.203.107
+# -p:  Overrides default port, e.g., -p 5678
+```
+In client mode, the `dsh` shell will connect to the server and send it commands.  The IP address of the server and port can be provided if the default options require changing.  Note that the defaults will work well - for example `dsh -s` to start the server and `dsh -c` to start the client if you are running in a local VM; however, the defaults might not work if you are running on tux as others might be using the default port number for this program of `1234`.  If you are running on tux an example configuration might look like:
+
+```bash
+#start the server on tux, use port 7890
+./dsh -s -i 0.0.0.0 -p 7890
+
+#start the client to connect to tux, assuming tux's IP
+#address is 129.25.203.107 and the server is running
+#on port number 7890
+./dsh -c -i 129.25.203.107 -p 7890
+```
+
+#### A cautionary tale about network I/O
+We will be using the TCP/IP protocol, which is a **stream** protocol.  This means that TCP/IP sends bytes in streams.  There is no corresponding indication of where a logical stream begins and a logical stream ends.  Consider you send the command `ls` in a single send to the server.  Its possible that the server will receive `ls\0`, `ls`, or `l` on a receive.  In other words, the network might break up sends into multiple receives.  You must handle this yourself when receiving data over the network.
+
+There are many techniques to handle managing a logical stream.  We will consider the easiest technique that involves using a special character to mark the end of a logical stream, and then loop `recv()` calls until we receive the character that we expect.  
+
+For the remote dsh program we will use the null byte `\0` as our end of stream delimiters for requests sent by the dsh client to the server.  On the way back we will use the ASCII code 0x04 or EOF character to indicate the end of the stream. We defined `RDSH_EOF_CHAR` in `rshlib.h` as follows:  
+
+```c
+static const char RDSH_EOF_CHAR = 0x04;  
+```
+
+**THIS MEANS THAT YOU WILL BE RESPONSIBLE FOR CORRECTLY NOT ONLY SENDING DATA BUT ENSURING THAT THE DATA YOU SEND ENDS WITH THE APPROPRIATE END OF STREAM CHARACTER**
+
+Some pseudo-code is below for how to handle this:
+
+```c
+//Sending a stream that is null terminated
+char *cmd = "ls -l"
+int  send_len = strlen(cmd) + 1;    //the +1 includes the NULL byte
+int  bytes_sent;
+
+//send the command including the null byte
+bytes_sent = send(sock, cmd, send_len, 0);
+```
+
+```c
+//Receiving a stream that is null terminated
+char *buff;
+int  recv_size;         //the +1 includes the NULL byte
+int  is_last_chunk;     //boolean to see if this is the last chunk
+char eof_char = '\0';   //using the null character in this demo, however
+                        //you can set this to RDSH_EOF_CHAR, which is
+                        //0x04, or the linux EOF character.  We define
+                        //RDSH_EOF_CHAR for you in rshlib.h.  For example,
+                        //if all we would need to do is to change:
+                        //
+                        // char eof_char = '\0'; to
+                        // char eof_char = RDSH_EOF_CHAR;
+                        //
+                        // to handle the stream of data that the server will
+                        // send back to the client.
+
+//note that RDSH_COMM_BUFF_SZ is a constant that we provide in rshlib.h
+buff = malloc(RDSH_COMM_BUFF_SZ);
+
+//send the command including the null byte
+while ((recv_size= recv(socket, buff, RDSH_COMM_BUFF_SZ,0)) > 0){
+    //we got recv_size bytes
+    if (recv_size < 0){
+        //we got an error, handle it and break out of loop or return
+        //from function
+    }
+    if (recv_size == 0){
+        //we received zero bytes, this often happens when we are waiting for
+        //the other side of the connection to send, but they close the socket
+        //for now lets just assume the other side went away and break out of
+        //the loop or return from the function
+    }
+
+    //At this point we have some data, lets see if this is the last chunk
+    is_last_chunk = ((char)buff[recv_size-1] == eof_char) ? 1 : 0;
+
+    if (is_last_chunk){
+        buff[recv_size-1] = '\0'; //remove the marker and replace with a null
+                                  //this makes string processing easier
+    }
+
+    //Now the data in buff is guaranteed to be null-terminated.  Handle in,
+    //in our shell client we will just be printing it out. Note that we are
+    //using a special printf marker "%.*s" that will print out the characters
+    //until it encounters a null byte or prints out a max of recv_size
+    //characters, whatever happens first. 
+    printf("%.*s", (int)recv_size, buff);
+
+    //If we are not at the last chunk, loop back and receive some more, if it
+    //is the last chunk break out of the loop
+    if (is_last_chunk)
+        break;
+}
+
+//NORMAL PROCESSING CONTINUES HERE
+```
+
+In our final example, we will demonstrate how to just sent the `EOF` character. You will need this in your server.  Your server will do the traditional `fork/exec` pattern and you will `dup` `stdin`, `stdout`, and `stderr` to the socket you are using for communications.  This means that everything you `exec` will go back over the socket to the client until the command finishes executing.  However, the client will not know that the server is done until the server does one final send, sending the `EOF` character.  Remember this character is defined in `rshlib.h` as RDSH_EOF_CHAR.
+
+```c
+//Demo function to just send the eof
+int send_eof(int socket){
+    int bytes_sent;
+
+    //send one character, the EOF character.
+    bytes_sent = send(socket, &RDSH_EOF_CHAR, 1, 0);
+    if (bytes_sent == 1){
+        //this is what is expected, we sent a single character,
+        //the EOF character, so we can return a good error code.
+        //we use OK for this as defined in dshlib.h
+        return OK;
+    }
+    
+
+    //handle error and send back an appropriate error code
+    //if bytes_sent < 0 that would indicate a network error
+    //if it equals zero it indicates the character could not
+    //be sent, which is also an error.  I could not imagine a
+    //situation where bytes_sent > 1 since we told send to 
+    //send exactly one byte, but if this happens it would also
+    //be an error.
+
+    //Ill just return a generic COMMUNICATION error we defined
+    //for you in rshlib.h, but you can return different error
+    //codes for different conditions if you want. 
+    return ERR_RDSH_COMMUNICATION;
+}
+```
+
+#### Directions
+
+Now that we have provided an overview of what we are looking for we can turn to specific directions for this assignment including an overview of the starter code that we provide.
+
+Files provided in the starter:
+
+| File | Description |
+| --- | --- |
+| dsh_cli.c | You should not need to modify this file but you should understand it.  It accepts command line args to start `dsh` in stand-alone mode, client-mode, or server-mode. |
+| dshlib.h | This file is largely unchanged from the last assignment.  The shell prompt has been bumped to `dsh4>` |
+| dshlib.c | We provide an empty starter for this file.  This file should be replaced with the code you wrote in `5-ShellP3`. It assumes that local shell execution runs via the `exec_local_cmd_loop()` command. No other changes are required - reuse the submission of your last assignment as-is!|
+| rshlib.h | This header file shows key constants and prototypes needed for the remote shell implementation.  There are comments in this file to highlight what they are used for |
+| rsh_cli.c | The implementation of the remote shell client.  The client should start executing via the `exec_remote_cmd_loop(server_ip_address, port)` function. There is significant documentation in the starter to get you going. |
+| rsh_server.c | The implementation of the remote shell server.  The server should start executing via the `start_server(server_interfaces, port)` function. There is significant documentation in the starter to get you going. |
+
+This version of `dsh` has the following options, which can be viewed by executing `dsh -h`
+
+```bash
+Usage: ./dsh [-c | -s] [-i IP] [-p PORT] [-x] [-h]
+  Default is to run ./dsh in local mode
+  -c            Run as client
+  -s            Run as server
+  -i IP         Set IP/Interface address (only valid with -c or -s)
+  -p PORT       Set port number (only valid with -c or -s)
+  -x            Enable threaded mode (only valid with -s)
+  -h            Show this help message
+  ```
+  The defaults for the interfaces to bind to on the server, the server IP address and the port number are specified in the `rshlib.h` file.  Note these might require adjustments as there is only a single port 1234 and only one student can use this port at a time.  As shown above you can adjust the port numbers and other defaults using the `-i` and `-p` command line options.  
+
+```c
+#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
+```
+
+For this assignment we are providing a lot of leadway for you to modify the design of the starter if it makes your life easier. Below we provide the high level flow of the functions provided by the starter, but **you are free to change any of these things if you desire**.
+
+**Main Start Code Flow**
+From `dsh_cli.c`:
+```
+┌─────────────────────────────┐
+│ENTRY-POINT:                 │
+│main()                       │
+│  mode = initParams()        │
+│  switch mode:               │
+│    case LCLI                │
+│      exec_local_cmd_loop()  │
+│    case SCLI                │
+│      exec_remote_cmd_loop() │
+│    case SSVR                │
+│       start_server()        │
+└─────────────────────────────┘
+```
+**Client Starter Code Flow**
+From `rsh_cli.c`:
+```
+┌────────────────────────┐                        
+│ENTRY-POINT:            │      ┌────────────────┐
+│exec_remote_cmd_loop(){ │      │start_client(){ │
+│                        │      │  socket()      │
+│  start_client()        ├─────▶│  connect()     │
+│                        │      │}               │
+│  loop:                 │      └────────────────┘
+│    print_prompt        │                        
+│    send-request        │                        
+│    recv-response       │     ┌─────────────────┐
+│                        │     │client_cleanup(){│
+│  client-cleanup()      ├────▶│   free buffers  │
+│}                       │     │   close socket  │
+│                        │     │}                │
+└────────────────────────┘     └─────────────────┘
+```
+
+**Server Starter Code Flow**
+From `rsh_server.c`:
+```
+                                                             ┌───────────────────────────┐   
+                              ┌────────────────────────┐     │exec_cli_requests(){       │   
+                              │boot_server(){          │     │ allocate-recv-buffer      │   
+                              │  sock = socket()       │     │ loop:                     │   
+                              │    bind()              │     │   recv()                  │   
+             ┌───────────────▶│    listen()            │     │   build_cmd_list()        │   
+             │                │  return sock;          │     │   rsh_execute_pipeline()  │   
+             │                │}                       │     │   send_msg_eof()          │   
+             │                └────────────────────────┘  ┌─▶│                           │──┐
+             │                ┌────────────────────────┐  │  │   cmd==exit break         │  │
+┌────────────────────────┐    │process_cli_requests(){ │  │  │   cmd==stop-server break  │  │
+│ENTRY-POINT:            │    │ loop:                  │  │  │                           │  │
+│start-server(){         │    │  accept()              │  │  │ free-recv-buffer          │  │
+│  boot_server()         │    │  rc=exec_cli_requests()│  │  │ close socket              │  │
+│  process_cli_requests()│───▶│                        │──┘  │}                          │  │
+│  stop_server()         │    │  if rc==OK_EXIT        │     └───────────────────────────┘  │
+│}                       │    │    break loop          │                                    │
+└────────────────────────┘    │}                       │     ┌───────────────────────────┐  │
+             │                └────────────────────────┘     │rsh-execute-pipeline(){    │  │
+             │                 ┌───────────────────────┐     │ modified execute pipeline │  │
+             │                 │stop_server(){         │     │ dup stdin, stdout, stderr │  │
+             └────────────────▶│  close-server-sock    │     │ file descriptors to the   │◀─┘
+                               │}                      │     │ client connection socket  │   
+                               └───────────────────────┘     │}                          │   
+                                                             └───────────────────────────┘   
+```
+#### Approaching this assignment
+
+You are free to approach this assignment anyway you choose.  The advice here is how we did it in the solution, and represents good engineering practice of iterative software development. 
+
+1.  Start by implementing the server.  Shell out a minimal implementation in `rsh_server.c` using the provided arguements from `dsh_cli.c`.  The goal at this point is to be able to start a socket server, accept client connections and properly send and receive messages.  Do not attempt to process any messages at this point.  Build the initial version to accept commands from the client (that are expected to be null `\0` terminated), and then simply reply by _echoing_ the this input back to the client in its expected format (messages from the server back to the client are streams of characters and terminated with the `EOF` character). Remember we provide `RDSH_EOF_CHAR` in `rshlib.h` as a `static constant char` which means you can treat it like a `#define` constant but can also use it as a variable.  In other words you can `send(socket, &RDSH_EOF_CHAR, 1, 0);
+
+2. Next implement the basic client.  Build the logic to connect to the server with the provided arguments collected from `dsh_cli.c`. From there copy the basic command loop from your previous shell that accepts user input from the command line.  However, instead of calling any fork/exec logic, properly format the input collected from the user and send it to the server.  Remember you must send a null byte at the end as per the protocol.  Because C strings are null terminated you can easily do this by `send(socket, my_string, strlen(my_string)+1,0);`.  After sending, build a simple receive loop given that the data sent back from the server might not come all at one time.  Keep looping until the payload that you see ends with the `RDSH_EOF_CHAR` character.  We describe how to do this above.
+
+3. Expand the rdsh_server implementation to start handling some key built-in commands.  These commands do not require any fork/exec logic.  I would recommend starting with implementing the `exit` command that simply closes the socket connection with the client and loops the server back to the next `accept()` socket API call. Correct operation would now enable a client to connect to the server, run `exit`, then allow another client to connect to the server. After that, implement the `stop-server` command that not only closes the client connection, but also gracefully shuts down the server.  This would involve freeing all allocated memory, and ultimately closing the main server socket. 
+
+4. Implement the other required built in commands such as `cd`.  There are also other built-in commands that were extra credit earlier that you can integrate such as `dragon`.
+
+5. Finally implement the fork/exec logic to implement remote commands.  This will largly be the same as in your previous assignment except you will be `dup2()` the `stdin` descriptor in the first process and the `stdout` and `stderr` in the last pipeline process using the socket representing the client connection.  
+
+6. Build your tests to not only show us that your code is working, but to help you debug your code as well.  In this final assignment testing will be 100% your responsibility to implement.
+
+
+#### What to hand in and Grading Rubric
+
+Grading Rubric
+- 70 points: Correct implementation of required functionality points: 
+- 10 points: Code quality (how easy is your solution to follow)
+- 10 points: Answering the written questions: questions.md
+- 10 points: Quality and breadth of BATS unit tests
+- 10 points: [EXTRA CREDIT] Make your server multi-threaded to handle concurrent client connections
+
+Total points achievable is 110/100.
\ No newline at end of file
diff --git a/Assignment-06/starter/.debug/launch.json b/Assignment-06/starter/.debug/launch.json
new file mode 100644
index 0000000..fdb6409
--- /dev/null
+++ b/Assignment-06/starter/.debug/launch.json
@@ -0,0 +1,29 @@
+{
+    "configurations": [
+        {
+            "name": "(gdb) 6-RShell",
+            "type": "cppdbg",
+            "request": "launch",
+            "program": "${workspaceFolder}/6-RShell/starter/dsh",
+            "args": [""],
+            "stopAtEntry": false,
+            "cwd": "${workspaceFolder}/6-RShell/starter",
+            "environment": [],
+            "externalConsole": false,
+            "MIMode": "gdb",
+            "setupCommands": [
+                {
+                    "description": "Enable pretty-printing for gdb",
+                    "text": "-enable-pretty-printing",
+                    "ignoreFailures": true
+                },
+                {
+                    "description": "Set Disassembly Flavor to Intel",
+                    "text": "-gdb-set disassembly-flavor intel",
+                    "ignoreFailures": true
+                }
+            ],
+            "preLaunchTask": "Build 6-RShell"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/Assignment-06/starter/.debug/tasks.json b/Assignment-06/starter/.debug/tasks.json
new file mode 100644
index 0000000..d9a3754
--- /dev/null
+++ b/Assignment-06/starter/.debug/tasks.json
@@ -0,0 +1,20 @@
+{
+    "version": "2.0.0",
+    "tasks": [
+      {
+        "label": "Build 6-RShell",
+        "type": "shell",
+        "command": "make",
+        "group": {
+          "kind": "build",
+          "isDefault": true
+        },
+        "options": {
+          "cwd": "${workspaceFolder}/6-RShell/starter"
+      },
+        "problemMatcher": ["$gcc"],
+        "detail": "Runs the 'make' command to build the project."
+      }
+    ]
+  }
+  
\ No newline at end of file
diff --git a/Assignment-06/starter/bats/assignment_tests (2).sh b/Assignment-06/starter/bats/assignment_tests (2).sh
new file mode 100644
index 0000000..5246ee4
--- /dev/null
+++ b/Assignment-06/starter/bats/assignment_tests (2).sh	
@@ -0,0 +1,36 @@
+#!/usr/bin/env bats
+
+############################ DO NOT EDIT THIS FILE #####################################
+# File: assignement_tests.sh
+# 
+# DO NOT EDIT THIS FILE
+#
+# Add/Edit Student tests in student_tests.sh
+# 
+# All tests in this file must pass - it is used as part of grading!
+########################################################################################
+
+@test "Pipes" {
+    run "./dsh" <<EOF                
+ls | grep dshlib.c
+EOF
+
+    # Strip all whitespace (spaces, tabs, newlines) from the output
+    stripped_output=$(echo "$output" | tr -d '[:space:]')
+
+    # Expected output with all whitespace removed for easier matching
+    expected_output="dshlib.clocalmodedsh4>dsh4>cmdloopreturned0"
+
+    # These echo commands will help with debugging and will only print
+    #if the test fails
+    echo "Captured stdout:" 
+    echo "Output: $output"
+    echo "Exit Status: $status"
+    echo "${stripped_output} -> ${expected_output}"
+
+    # Check exact match
+    [ "$stripped_output" = "$expected_output" ]
+
+    # Assertions
+    [ "$status" -eq 0 ]
+}
diff --git a/Assignment-06/starter/bats/student_tests (2).sh b/Assignment-06/starter/bats/student_tests (2).sh
new file mode 100644
index 0000000..638bc34
--- /dev/null
+++ b/Assignment-06/starter/bats/student_tests (2).sh	
@@ -0,0 +1,14 @@
+#!/usr/bin/env bats
+
+# File: student_tests.sh
+# 
+# Create your unit tests suit in this file
+
+@test "Example: check ls runs without errors" {
+    run ./dsh <<EOF                
+ls
+EOF
+
+    # Assertions
+    [ "$status" -eq 0 ]
+}
diff --git a/Assignment-06/starter/dsh_cli.c b/Assignment-06/starter/dsh_cli.c
new file mode 100644
index 0000000..68fab16
--- /dev/null
+++ b/Assignment-06/starter/dsh_cli.c
@@ -0,0 +1,149 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <argp.h>
+#include <getopt.h>
+
+#include "dshlib.h"
+#include "rshlib.h"
+
+
+/*
+ * Used to pass startup parameters back to main
+ */
+#define MODE_LCLI   0       //Local client
+#define MODE_SCLI   1       //Socket client
+#define MODE_SSVR   2       //Socket server
+
+typedef struct cmd_args{
+  int   mode;
+  char  ip[16];   //e.g., 192.168.100.101\0
+  int   port;
+  int   threaded_server;
+}cmd_args_t;
+
+
+
+//You dont really need to understand this but the C runtime library provides
+//an getopt() service to simplify handling command line arguments.  This
+//code will help setup dsh to handle triggering client or server mode along
+//with passing optional connection parameters. 
+
+void print_usage(const char *progname) {
+  printf("Usage: %s [-c | -s] [-i IP] [-p PORT] [-x] [-h]\n", progname);
+  printf("  Default is to run %s in local mode\n", progname);
+  printf("  -c            Run as client\n");
+  printf("  -s            Run as server\n");
+  printf("  -i IP         Set IP/Interface address (only valid with -c or -s)\n");
+  printf("  -p PORT       Set port number (only valid with -c or -s)\n");
+  printf("  -x            Enable threaded mode (only valid with -s)\n");
+  printf("  -h            Show this help message\n");
+  exit(0);
+}
+
+void parse_args(int argc, char *argv[], cmd_args_t *cargs) {
+  int opt;
+  memset(cargs, 0, sizeof(cmd_args_t));
+
+  //defaults
+  cargs->mode = MODE_LCLI;
+  cargs->port = RDSH_DEF_PORT;
+
+  while ((opt = getopt(argc, argv, "csi:p:xh")) != -1) {
+      switch (opt) {
+          case 'c':
+              if (cargs->mode != MODE_LCLI) {
+                  fprintf(stderr, "Error: Cannot use both -c and -s\n");
+                  exit(EXIT_FAILURE);
+              }
+              cargs->mode = MODE_SCLI;
+              strncpy(cargs->ip, RDSH_DEF_CLI_CONNECT, sizeof(cargs->ip) - 1);
+              break;
+          case 's':
+              if (cargs->mode != MODE_LCLI) {
+                  fprintf(stderr, "Error: Cannot use both -c and -s\n");
+                  exit(EXIT_FAILURE);
+              }
+              cargs->mode = MODE_SSVR;
+              strncpy(cargs->ip, RDSH_DEF_SVR_INTFACE, sizeof(cargs->ip) - 1);
+              break;
+          case 'i':
+              if (cargs->mode == MODE_LCLI) {
+                  fprintf(stderr, "Error: -i can only be used with -c or -s\n");
+                  exit(EXIT_FAILURE);
+              }
+              strncpy(cargs->ip, optarg, sizeof(cargs->ip) - 1);
+              cargs->ip[sizeof(cargs->ip) - 1] = '\0';  // Ensure null termination
+              break;
+          case 'p':
+              if (cargs->mode == MODE_LCLI) {
+                  fprintf(stderr, "Error: -p can only be used with -c or -s\n");
+                  exit(EXIT_FAILURE);
+              }
+              cargs->port = atoi(optarg);
+              if (cargs->port <= 0) {
+                  fprintf(stderr, "Error: Invalid port number\n");
+                  exit(EXIT_FAILURE);
+              }
+              break;
+          case 'x':
+              if (cargs->mode != MODE_SSVR) {
+                  fprintf(stderr, "Error: -x can only be used with -s\n");
+                  exit(EXIT_FAILURE);
+              }
+              cargs->threaded_server = 1;
+              break;
+          case 'h':
+              print_usage(argv[0]);
+              break;
+          default:
+              print_usage(argv[0]);
+      }
+  }
+
+  if (cargs->threaded_server && cargs->mode != MODE_SSVR) {
+      fprintf(stderr, "Error: -x can only be used with -s\n");
+      exit(EXIT_FAILURE);
+  }
+}
+
+
+
+/* DO NOT EDIT
+ * main() logic fully implemented to:
+ *    1. run locally (no parameters)
+ *    2. start the server with the -s option
+ *    3. start the client with the -c option
+*/
+int main(int argc, char *argv[]){
+  cmd_args_t cargs;
+  int rc;
+
+  memset(&cargs, 0, sizeof(cmd_args_t));
+  parse_args(argc, argv, &cargs);
+
+  switch(cargs.mode){
+    case MODE_LCLI:
+      printf("local mode\n");
+      rc = exec_local_cmd_loop();
+      break;
+    case MODE_SCLI:
+      printf("socket client mode:  addr:%s:%d\n", cargs.ip, cargs.port);
+      rc = exec_remote_cmd_loop(cargs.ip, cargs.port);
+      break;
+    case MODE_SSVR:
+      printf("socket server mode:  addr:%s:%d\n", cargs.ip, cargs.port);
+      if (cargs.threaded_server){
+        printf("-> Multi-Threaded Mode\n");
+      } else {
+        printf("-> Single-Threaded Mode\n");
+      }
+      rc = start_server(cargs.ip, cargs.port, cargs.threaded_server);
+      break;
+    default:
+      printf("error unknown mode\n");
+      exit(EXIT_FAILURE);
+  }
+
+  printf("cmd loop returned %d\n", rc);
+}
\ No newline at end of file
diff --git a/Assignment-06/starter/dshlib.c b/Assignment-06/starter/dshlib.c
new file mode 100644
index 0000000..8ba1f4e
--- /dev/null
+++ b/Assignment-06/starter/dshlib.c
@@ -0,0 +1,81 @@
+#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"
+
+
+
+#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 exec_local_cmd_loop()
+{
+
+    // THIS CODE SHOULD BE THE SAME AS PRIOR ASSIGNMENTS
+   
+    return OK;
+}
diff --git a/Assignment-06/starter/dshlib.h b/Assignment-06/starter/dshlib.h
new file mode 100644
index 0000000..b4cc21b
--- /dev/null
+++ b/Assignment-06/starter/dshlib.h
@@ -0,0 +1,92 @@
+#ifndef __DSHLIB_H__
+    #define __DSHLIB_H__
+
+
+//Constants for command structure sizes
+#define EXE_MAX 64
+#define ARG_MAX 256
+#define CMD_MAX 8
+#define CMD_ARGV_MAX (CMD_MAX + 1)
+// Longest command that can be read from the shell
+#define SH_CMD_MAX EXE_MAX + ARG_MAX
+
+typedef struct command
+{
+    char exe[EXE_MAX];
+    char args[ARG_MAX];
+} command_t;
+
+#include <stdbool.h>
+
+typedef struct cmd_buff
+{
+    int  argc;
+    char *argv[CMD_ARGV_MAX];
+    char *_cmd_buffer;
+    char *input_file;  // extra credit, stores input redirection file (for `<`)
+    char *output_file; // extra credit, stores output redirection file (for `>`)
+    bool append_mode; // extra credit, sets append mode fomr output_file
+} cmd_buff_t;
+
+typedef struct command_list{
+    int num;
+    cmd_buff_t commands[CMD_MAX];
+}command_list_t;
+
+//Special character #defines
+#define SPACE_CHAR  ' '
+#define PIPE_CHAR   '|'
+#define PIPE_STRING "|"
+
+#define SH_PROMPT       "dsh4> "
+#define EXIT_CMD        "exit"
+#define RC_SC           99
+#define EXIT_SC         100
+
+//Standard Return Codes
+#define OK                       0
+#define WARN_NO_CMDS            -1
+#define ERR_TOO_MANY_COMMANDS   -2
+#define ERR_CMD_OR_ARGS_TOO_BIG -3
+#define ERR_CMD_ARGS_BAD        -4      //for extra credit
+#define ERR_MEMORY              -5
+#define ERR_EXEC_CMD            -6
+#define OK_EXIT                 -7
+
+
+
+//prototypes
+int alloc_cmd_buff(cmd_buff_t *cmd_buff);
+int free_cmd_buff(cmd_buff_t *cmd_buff);
+int clear_cmd_buff(cmd_buff_t *cmd_buff);
+int build_cmd_buff(char *cmd_line, cmd_buff_t *cmd_buff);
+int close_cmd_buff(cmd_buff_t *cmd_buff);
+int build_cmd_list(char *cmd_line, command_list_t *clist);
+int free_cmd_list(command_list_t *cmd_lst);
+
+//built in command stuff
+typedef enum {
+    BI_CMD_EXIT,
+    BI_CMD_DRAGON,
+    BI_CMD_CD,
+    BI_CMD_RC,              //extra credit command
+    BI_CMD_STOP_SVR,        //new command "stop-server"
+    BI_NOT_BI,
+    BI_EXECUTED,
+} Built_In_Cmds;
+Built_In_Cmds match_command(const char *input); 
+Built_In_Cmds exec_built_in_cmd(cmd_buff_t *cmd);
+
+//main execution context
+int exec_local_cmd_loop();
+int exec_cmd(cmd_buff_t *cmd);
+int execute_pipeline(command_list_t *clist);
+
+
+//output constants
+#define CMD_OK_HEADER       "PARSED COMMAND LINE - TOTAL COMMANDS %d\n"
+#define CMD_WARN_NO_CMD     "warning: no commands provided\n"
+#define CMD_ERR_PIPE_LIMIT  "error: piping limited to %d commands\n"
+#define BI_NOT_IMPLEMENTED "not implemented"
+
+#endif
\ No newline at end of file
diff --git a/Assignment-06/starter/gitignore b/Assignment-06/starter/gitignore
new file mode 100644
index 0000000..eb47a8e
--- /dev/null
+++ b/Assignment-06/starter/gitignore
@@ -0,0 +1 @@
+dsh
\ No newline at end of file
diff --git a/Assignment-06/starter/makefile b/Assignment-06/starter/makefile
new file mode 100644
index 0000000..b14b072
--- /dev/null
+++ b/Assignment-06/starter/makefile
@@ -0,0 +1,31 @@
+# Compiler settings
+CC = gcc
+CFLAGS = -Wall -Wextra -g
+
+# Target executable name
+TARGET = dsh
+
+# Find all source and header files
+SRCS = $(wildcard *.c)
+HDRS = $(wildcard *.h)
+
+# Default target
+all: $(TARGET)
+
+# Compile source to executable
+$(TARGET): $(SRCS) $(HDRS)
+	$(CC) $(CFLAGS) -o $(TARGET) $(SRCS)
+
+# Clean up build files
+clean:
+	rm -f $(TARGET)
+
+test:
+	bats $(wildcard ./bats/*.sh)
+
+valgrind:
+	echo "pwd\nexit" | valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1 ./$(TARGET) 
+	echo "pwd\nexit" | valgrind --tool=helgrind --error-exitcode=1 ./$(TARGET) 
+
+# Phony targets
+.PHONY: all clean test
\ No newline at end of file
diff --git a/Assignment-06/starter/rsh_cli.c b/Assignment-06/starter/rsh_cli.c
new file mode 100644
index 0000000..70a1bdb
--- /dev/null
+++ b/Assignment-06/starter/rsh_cli.c
@@ -0,0 +1,195 @@
+
+#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
+
+    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
+
+        // TODO fgets input
+
+        // TODO send() over cli_socket
+
+        // TODO recv all the results
+
+        // TODO break on exit command
+    }
+
+    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;
+
+    // TODO set up cli_socket
+
+
+    return cli_socket;
+}
+
+/*
+ * client_cleanup(int cli_socket, char *cmd_buff, char *rsp_buff, int rc)
+ *      cli_socket:   The client socket
+ *      cmd_buff:     The buffer that will hold commands to send to server
+ *      rsp_buff:     The buffer that will hld server responses
+ * 
+ *   This function does the following: 
+ *      1. If cli_socket > 0 it calls close(cli_socket) to close the socket
+ *      2. It calls free() on cmd_buff and rsp_buff
+ *      3. It returns the value passed as rc
+ *  
+ *   Note this function is intended to be helper to manage exit conditions
+ *   from the exec_remote_cmd_loop() function given there are several
+ *   cleanup steps.  We provide it to you fully implemented as a helper.
+ *   You do not have to use it if you want to develop an alternative
+ *   strategy for cleaning things up in your exec_remote_cmd_loop()
+ *   implementation. 
+ * 
+ *   returns:
+ *          rc:   This function just returns the value passed as the 
+ *                rc parameter back to the caller.  This way the caller
+ *                can just write return client_cleanup(...)
+ *      
+ */
+int client_cleanup(int cli_socket, char *cmd_buff, char *rsp_buff, int rc){
+    //If a valid socket number close it.
+    if(cli_socket > 0){
+        close(cli_socket);
+    }
+
+    //Free up the buffers 
+    free(cmd_buff);
+    free(rsp_buff);
+
+    //Echo the return value that was passed as a parameter
+    return rc;
+}
\ No newline at end of file
diff --git a/Assignment-06/starter/rsh_server.c b/Assignment-06/starter/rsh_server.c
new file mode 100644
index 0000000..7a5fb74
--- /dev/null
+++ b/Assignment-06/starter/rsh_server.c
@@ -0,0 +1,501 @@
+
+#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;
+    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.
+     */
+    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 
+        // and then exec_client_requests(cli_socket)
+    }
+
+    stop_server(cli_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;
+    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
+
+        // TODO build up a cmd_list
+
+        // TODO rsh_execute_pipeline to run your cmd_list
+
+        // TODO send appropriate respones with send_message_string
+        // - error constants for failures
+        // - buffer contents from execute commands
+        //  - etc.
+
+        // TODO send_message_eof when done
+    }
+
+    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;
+    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()
+    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.
+
+    }
+
+
+    // Parent process: close all pipe ends
+    for (int i = 0; i < clist->num - 1; i++) {
+        close(pipes[i][0]);
+        close(pipes[i][1]);
+    }
+
+    // Wait for all children
+    for (int i = 0; i < clist->num; i++) {
+        waitpid(pids[i], &pids_st[i], 0);
+    }
+
+    //by default get exit code of last process
+    //use this as the return value
+    exit_code = WEXITSTATUS(pids_st[clist->num - 1]);
+    for (int i = 0; i < clist->num; i++) {
+        //if any commands in the pipeline are EXIT_SC
+        //return that to enable the caller to react
+        if (WEXITSTATUS(pids_st[i]) == EXIT_SC)
+            exit_code = EXIT_SC;
+    }
+    return exit_code;
+}
+
+/**************   OPTIONAL STUFF  ***************/
+/****
+ **** NOTE THAT THE FUNCTIONS BELOW ALIGN TO HOW WE CRAFTED THE SOLUTION
+ **** TO SEE IF A COMMAND WAS BUILT IN OR NOT.  YOU CAN USE A DIFFERENT
+ **** STRATEGY IF YOU WANT.  IF YOU CHOOSE TO DO SO PLEASE REMOVE THESE
+ **** FUNCTIONS AND THE PROTOTYPES FROM rshlib.h
+ **** 
+ */
+
+/*
+ * rsh_match_command(const char *input)
+ *      cli_socket:  The string command for a built-in command, e.g., dragon,
+ *                   cd, exit-server
+ *   
+ *  This optional function accepts a command string as input and returns
+ *  one of the enumerated values from the BuiltInCmds enum as output. For
+ *  example:
+ * 
+ *      Input             Output
+ *      exit              BI_CMD_EXIT
+ *      dragon            BI_CMD_DRAGON
+ * 
+ *  This function is entirely optional to implement if you want to handle
+ *  processing built-in commands differently in your implementation. 
+ * 
+ *  Returns:
+ * 
+ *      BI_CMD_*:   If the command is built-in returns one of the enumeration
+ *                  options, for example "cd" returns BI_CMD_CD
+ * 
+ *      BI_NOT_BI:  If the command is not "built-in" the BI_NOT_BI value is
+ *                  returned. 
+ */
+Built_In_Cmds rsh_match_command(const char *input)
+{
+    if (strcmp(input, "exit") == 0)
+        return BI_CMD_EXIT;
+    if (strcmp(input, "dragon") == 0)
+        return BI_CMD_DRAGON;
+    if (strcmp(input, "cd") == 0)
+        return BI_CMD_CD;
+    if (strcmp(input, "stop-server") == 0)
+        return BI_CMD_STOP_SVR;
+    if (strcmp(input, "rc") == 0)
+        return BI_CMD_RC;
+    return BI_NOT_BI;
+}
+
+/*
+ * rsh_built_in_cmd(cmd_buff_t *cmd)
+ *      cmd:  The cmd_buff_t of the command, remember, this is the 
+ *            parsed version fo the command
+ *   
+ *  This optional function accepts a parsed cmd and then checks to see if
+ *  the cmd is built in or not.  It calls rsh_match_command to see if the 
+ *  cmd is built in or not.  Note that rsh_match_command returns BI_NOT_BI
+ *  if the command is not built in. If the command is built in this function
+ *  uses a switch statement to handle execution if appropriate.   
+ * 
+ *  Again, using this function is entirely optional if you are using a different
+ *  strategy to handle built-in commands.  
+ * 
+ *  Returns:
+ * 
+ *      BI_NOT_BI:   Indicates that the cmd provided as input is not built
+ *                   in so it should be sent to your fork/exec logic
+ *      BI_EXECUTED: Indicates that this function handled the direct execution
+ *                   of the command and there is nothing else to do, consider
+ *                   it executed.  For example the cmd of "cd" gets the value of
+ *                   BI_CMD_CD from rsh_match_command().  It then makes the libc
+ *                   call to chdir(cmd->argv[1]); and finally returns BI_EXECUTED
+ *      BI_CMD_*     Indicates that a built-in command was matched and the caller
+ *                   is responsible for executing it.  For example if this function
+ *                   returns BI_CMD_STOP_SVR the caller of this function is
+ *                   responsible for stopping the server.  If BI_CMD_EXIT is returned
+ *                   the caller is responsible for closing the client connection.
+ * 
+ *   AGAIN - THIS IS TOTALLY OPTIONAL IF YOU HAVE OR WANT TO HANDLE BUILT-IN
+ *   COMMANDS DIFFERENTLY. 
+ */
+Built_In_Cmds rsh_built_in_cmd(cmd_buff_t *cmd)
+{
+    Built_In_Cmds ctype = BI_NOT_BI;
+    ctype = rsh_match_command(cmd->argv[0]);
+
+    switch (ctype)
+    {
+    // case BI_CMD_DRAGON:
+    //     print_dragon();
+    //     return BI_EXECUTED;
+    case BI_CMD_EXIT:
+        return BI_CMD_EXIT;
+    case BI_CMD_STOP_SVR:
+        return BI_CMD_STOP_SVR;
+    case BI_CMD_RC:
+        return BI_CMD_RC;
+    case BI_CMD_CD:
+        chdir(cmd->argv[1]);
+        return BI_EXECUTED;
+    default:
+        return BI_NOT_BI;
+    }
+}
diff --git a/Assignment-06/starter/rshlib.h b/Assignment-06/starter/rshlib.h
new file mode 100644
index 0000000..6ae2ca9
--- /dev/null
+++ b/Assignment-06/starter/rshlib.h
@@ -0,0 +1,78 @@
+#ifndef __RSH_LIB_H__
+    #define __RSH_LIB_H__
+
+#include "dshlib.h"
+
+//common remote shell client and server constants and definitions
+
+
+//Constants for communication
+//Note that these should work fine in a local VM but you will likely have
+//to change the port number if you are working on tux.
+#define RDSH_DEF_PORT           1234        //Default port #
+#define RDSH_DEF_SVR_INTFACE    "0.0.0.0"   //Default start all interfaces
+#define RDSH_DEF_CLI_CONNECT    "127.0.0.1" //Default server is running on
+                                            //localhost 127.0.0.1
+
+//constants for buffer sizes
+#define RDSH_COMM_BUFF_SZ       (1024*64)   //64K
+#define STOP_SERVER_SC          200         //returned from pipeline excution
+                                            //if the command is to stop the
+                                            //server.  See documentation for 
+                                            //exec_client_requests() for more info
+
+//end of message delimiter.  This is super important.  TCP is a stream, therefore
+//the protocol designer is responsible for managing where messages begin and end
+//there are many common techniques for this, but one of the simplest ways is to
+//use an end of stream marker.  Since rsh is a "shell" program we will be using
+//ascii code 0x04, which is commonly used as the end-of-file (EOF) character in
+//linux based systems. 
+static const char RDSH_EOF_CHAR = 0x04;    
+
+//rdsh specific error codes for functions
+#define ERR_RDSH_COMMUNICATION  -50     //Used for communication errors
+#define ERR_RDSH_SERVER         -51     //General server errors
+#define ERR_RDSH_CLIENT         -52     //General client errors
+#define ERR_RDSH_CMD_EXEC       -53     //RSH command execution errors
+#define WARN_RDSH_NOT_IMPL      -99     //Not Implemented yet warning
+
+//Output message constants for server
+#define CMD_ERR_RDSH_COMM   "rdsh-error: communications error\n"
+#define CMD_ERR_RDSH_EXEC   "rdsh-error: command execution error\n"
+#define CMD_ERR_RDSH_ITRNL  "rdsh-error: internal server error - %d\n"
+#define CMD_ERR_RDSH_SEND   "rdsh-error: partial send.  Sent %d, expected to send %d\n"
+#define RCMD_SERVER_EXITED  "server appeared to terminate - exiting\n"
+
+//Output message constants for client
+#define RCMD_MSG_CLIENT_EXITED  "client exited: getting next connection...\n"
+#define RCMD_MSG_SVR_STOP_REQ   "client requested server to stop, stopping...\n"
+#define RCMD_MSG_SVR_EXEC_REQ   "rdsh-exec:  %s\n"
+#define RCMD_MSG_SVR_RC_CMD     "rdsh-exec:  rc = %d\n"
+
+//client prototypes for rsh_cli.c - - see documentation for each function to
+//see what they do
+int start_client(char *address, int port);
+int client_cleanup(int cli_socket, char *cmd_buff, char *rsp_buff, int rc);
+int exec_remote_cmd_loop(char *address, int port);
+    
+
+//server prototypes for rsh_server.c - see documentation for each function to
+//see what they do
+int start_server(char *ifaces, int port, int is_threaded);
+int boot_server(char *ifaces, int port);
+int stop_server(int svr_socket);
+int send_message_eof(int cli_socket);
+int send_message_string(int cli_socket, char *buff);
+int process_cli_requests(int svr_socket);
+int exec_client_requests(int cli_socket);
+int rsh_execute_pipeline(int socket_fd, command_list_t *clist);
+
+Built_In_Cmds rsh_match_command(const char *input);
+Built_In_Cmds rsh_built_in_cmd(cmd_buff_t *cmd);
+
+//eliminate from template, for extra credit
+void set_threaded_server(int val);
+int exec_client_thread(int main_socket, int cli_socket);
+void *handle_client(void *arg);
+
+#endif
\ No newline at end of file
-- 
GitLab