diff --git a/3-ShellP1/dsh b/3-ShellP1/dsh
index bf8a6f9f628431b104fe182d5354b5deba6d7493..cf84894e331d54e3344d4f81291f8a18e41419fd 100755
Binary files a/3-ShellP1/dsh and b/3-ShellP1/dsh differ
diff --git a/4-ShellP2/.gitignore b/4-ShellP2/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..eb47a8e82ad127f89b6fdbbf0f531cb8865b7bf8
--- /dev/null
+++ b/4-ShellP2/.gitignore
@@ -0,0 +1 @@
+dsh
\ No newline at end of file
diff --git a/4-ShellP2/bats/assignment_tests.sh b/4-ShellP2/bats/assignment_tests.sh
new file mode 100755
index 0000000000000000000000000000000000000000..f62bbeb0645a7d5cce0f5d3503c637a993c6db6b
--- /dev/null
+++ b/4-ShellP2/bats/assignment_tests.sh
@@ -0,0 +1,118 @@
+#!/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 "Change directory" {
+#     current=$(pwd)
+
+#     cd ~/tmp
+#     mkdir -p dsh-test
+
+#     run "${current}/dsh" <<EOF                
+# cd dsh-test
+# pwd
+# 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="/tmp/dsh-testdsh2>dsh2>dsh2>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 ]
+# }
+
+# @test "Change directory - no args" {
+#     current=$(pwd)
+
+#     cd /tmp
+#     mkdir -p dsh-test
+
+#     run "${current}/dsh" <<EOF                
+# cd
+# pwd
+# 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="/tmpdsh2>dsh2>dsh2>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 ]
+# }
+
+
+@test "Which which ... which?" {
+    run "./dsh" <<EOF                
+which which
+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="/usr/bin/whichdsh2>dsh2>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" ]
+}
+
+@test "It handles quoted spaces" {
+    run "./dsh" <<EOF                
+   echo " hello     world     " 
+EOF
+
+    # Strip all whitespace (spaces, tabs, newlines) from the output
+    stripped_output=$(echo "$output" | tr -d '\t\n\r\f\v')
+
+    # Expected output with all whitespace removed for easier matching
+    expected_output=" hello     world     dsh2> dsh2> cmd loop returned 0"
+
+    # 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" ]
+}
\ No newline at end of file
diff --git a/4-ShellP2/bats/student_tests.sh b/4-ShellP2/bats/student_tests.sh
new file mode 100755
index 0000000000000000000000000000000000000000..73b17557a4796a54428956bd693c15f828c1f9d9
--- /dev/null
+++ b/4-ShellP2/bats/student_tests.sh
@@ -0,0 +1,98 @@
+#!/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 ]
+}
+
+# Test: Check if the 'exit' built-in command exits the shell
+@test "Check exit command exits the shell" {
+    run ./dsh <<EOF
+exit
+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="dsh2>cmdloopreturned-7"
+
+    # 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" ]
+}
+
+# Test: Check handling of empty input (should warn and continue)
+@test "Check empty input handling" {
+    run ./dsh <<EOF
+
+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="dsh2>warning:nocommandsprovideddsh2>cmdloopreturned0"
+
+    # These echo commands will help with debugging and will only print
+    echo "Captured stdout:" 
+    #if the test fails
+    echo "Output: $output"
+    echo "Exit Status: $status"
+    echo "${stripped_output} -> ${expected_output}"
+
+    # Check exact match
+    [ "$stripped_output" = "$expected_output" ]
+}
+
+# Test: Check 'cd' command changes directory
+@test "Check cd command changes directory" {
+    run ./dsh <<EOF
+cd /
+pwd
+EOF
+    stripped_output=$(echo "$output" | tr -d '[:space:]')
+    expected_output="/dsh2>dsh2>dsh2>cmdloopreturned0"
+    echo "Captured stdout:"
+    echo "Output: $output"
+    echo "Exit Status: $status"
+    echo "${stripped_output} -> ${expected_output}"
+    [ "$stripped_output" = "$expected_output" ]
+}
+
+# Test: Check if 'rc' command returns last exit status
+@test "Check rc command returns last exit status" {
+    run ./dsh <<EOF
+cd
+rc
+EOF
+    [ "$status" -eq 0 ]
+    echo "Captured stdout: $output"
+    stripped_output=$(echo "$output" | tr -d '[:space:]')
+    expected_output="dsh2>dsh2>0dsh2>cmdloopreturned0"
+    [ "$stripped_output" = "$expected_output" ]
+}
+
+# Test: Check handling of non-existent command
+@test "Check handling of non-existent command" {
+    run ./dsh <<EOF
+nocommand
+rc
+EOF
+    echo "Captured stdout: $output"
+    stripped_output=$(echo "$output" | tr -d '[:space:]')
+    expected_output="Errorexecutingnocommand:Nosuchfileordirectorydsh2>dsh2>dsh2>2dsh2>cmdloopreturned0"
+    [ "$stripped_output" = "$expected_output" ]
+}
\ No newline at end of file
diff --git a/4-ShellP2/convert.py b/4-ShellP2/convert.py
new file mode 100644
index 0000000000000000000000000000000000000000..d9259b3b59ece4e449d1f38bac3bcad2bedd5126
--- /dev/null
+++ b/4-ShellP2/convert.py
@@ -0,0 +1,24 @@
+import sys
+
+# Input binary file and output header file
+input_file = "dragon.bin"
+output_file = "dragon_data.h"
+
+try:
+    with open(input_file, "rb") as f:
+        binary_content = f.read()
+except FileNotFoundError:
+    print(f"Error: {input_file} not found.", file=sys.stderr)
+    sys.exit(1)
+
+# Convert binary content into a C array
+hex_data = ', '.join(f'0x{byte:02x}' for byte in binary_content)
+
+# Write the header file
+with open(output_file, "w", encoding="utf-8") as f:
+    f.write(f'#ifndef DRAGON_DATA_H\n#define DRAGON_DATA_H\n\n')
+    f.write(f'const unsigned char DRAGON_BIN[] = {{ {hex_data} }};\n')
+    f.write(f'const unsigned int DRAGON_BIN_SIZE = {len(binary_content)};\n\n')
+    f.write(f'#endif // DRAGON_DATA_H\n')
+
+print(f"Generated {output_file}")
diff --git a/4-ShellP2/dragon.bin b/4-ShellP2/dragon.bin
new file mode 100644
index 0000000000000000000000000000000000000000..cce2d5afea01f25637612a3ea87396c38ff931a1
--- /dev/null
+++ b/4-ShellP2/dragon.bin
@@ -0,0 +1,3 @@
+x���A�� �=�`�{����J
+�L���Ek!�HDm�5��F��#�HB<���\ƌ�ÈXs=_ 6����[�w�-�`ز1�J��'Y~R�D����ڹW�@2AϺ���@�4X*����'rUP!bA	Q�c�gj�
+�{c�"d	�+�)#+{�>dN��`P4!�`?$\wH���$t�Pz>RA�
�J��O���IJ@D�3!e�Ʉ�n�ܧk�m0���|���W��U��W�@܃a��k��[r<�<|����Ʀr���,�� ��f_�=y��uE"e\\+'P��![>#Ф�(!�4�_{��}y��?����h&{
)�����q[!DZ&��}a"L+�_�3�v�G
\ No newline at end of file
diff --git a/4-ShellP2/dragon.c b/4-ShellP2/dragon.c
new file mode 100644
index 0000000000000000000000000000000000000000..255710fd60a16a0a5eb672723639f237d945537a
--- /dev/null
+++ b/4-ShellP2/dragon.c
@@ -0,0 +1,27 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <zlib.h>
+#include "dragon_data.h"  // Include the embedded binary data
+
+#define DRAGON_BUFFER_SIZE 100000
+
+// EXTRA CREDIT - print the Drexel dragon from embedded binary
+extern void print_dragon() {
+    unsigned char *decompressed_data = (unsigned char *)malloc(DRAGON_BUFFER_SIZE);
+    if (!decompressed_data) {
+        fprintf(stderr, "Memory allocation failed!\n");
+        return;
+    }
+
+    uLongf decompressed_size = DRAGON_BUFFER_SIZE;
+    
+    // Ensure correct decompression
+    if (uncompress(decompressed_data, &decompressed_size, DRAGON_BIN, DRAGON_BIN_SIZE) != Z_OK) {
+        fprintf(stderr, "Decompression fail\n");
+        free(decompressed_data);
+        return;
+    }
+
+    printf("%s", decompressed_data);
+    free(decompressed_data);
+}
\ No newline at end of file
diff --git a/4-ShellP2/dragon_data.h b/4-ShellP2/dragon_data.h
new file mode 100644
index 0000000000000000000000000000000000000000..5888b1c72210c61c030fb58e79954dfec1e8cfd8
--- /dev/null
+++ b/4-ShellP2/dragon_data.h
@@ -0,0 +1,7 @@
+#ifndef DRAGON_DATA_H
+#define DRAGON_DATA_H
+
+const unsigned char DRAGON_BIN[] = { 0x78, 0x9c, 0xb5, 0xd6, 0x41, 0xa2, 0x83, 0x20, 0x0c, 0x04, 0xd0, 0x3d, 0xa7, 0x60, 0x93, 0x7b, 0xe4, 0xfe, 0xa7, 0xea, 0xaf, 0x4a, 0x0a, 0xc9, 0x4c, 0x82, 0xad, 0x9f, 0x45, 0x6b, 0x21, 0xf8, 0x48, 0x44, 0x6d, 0xeb, 0x0f, 0x35, 0x95, 0xbf, 0x46, 0xc6, 0xda, 0x23, 0x82, 0x48, 0x42, 0x3c, 0x84, 0x14, 0xc6, 0x03, 0x88, 0x5c, 0xc6, 0x8c, 0xe8, 0xc3, 0x88, 0x58, 0x73, 0x3d, 0x5f, 0x20, 0x36, 0xf3, 0xfd, 0xa9, 0xbe, 0x5b, 0xac, 0x77, 0x84, 0x2d, 0xfb, 0x60, 0x03, 0x11, 0xd8, 0xb2, 0x31, 0x9f, 0x4a, 0x89, 0xa4, 0x27, 0x59, 0x7e, 0x52, 0xa3, 0x44, 0xb2, 0xa5, 0x86, 0xf3, 0xda, 0xb9, 0x57, 0xa3, 0x40, 0x32, 0x41, 0xcf, 0xba, 0xe3, 0xe5, 0xdf, 0x40, 0xd2, 0x34, 0x58, 0x2a, 0x12, 0xef, 0x98, 0xdf, 0x10, 0xae, 0xff, 0x27, 0x72, 0x55, 0x50, 0x21, 0x62, 0x41, 0x09, 0x51, 0xd3, 0x63, 0x1c, 0x96, 0x0b, 0x67, 0x6a, 0xeb, 0x0a, 0x15, 0x98, 0x7b, 0x63, 0x88, 0x22, 0x64, 0x09, 0xa2, 0x2b, 0xde, 0x29, 0x23, 0x2b, 0x7b, 0xeb, 0x3e, 0x64, 0x4e, 0x85, 0x9d, 0x60, 0x1e, 0x50, 0x16, 0x34, 0x21, 0x9f, 0x60, 0x3f, 0x24, 0x5c, 0x01, 0x77, 0x48, 0xc6, 0x18, 0x12, 0x8c, 0xe4, 0x9a, 0x24, 0x08, 0x74, 0x06, 0x12, 0x8d, 0x50, 0x7a, 0x3e, 0x52, 0x41, 0x8d, 0x0d, 0xa4, 0x4a, 0x89, 0xcc, 0x4f, 0xe9, 0xf4, 0x9a, 0xc4, 0xb2, 0x40, 0x44, 0xcf, 0x33, 0x21, 0x65, 0x1c, 0xde, 0xc9, 0x84, 0xec, 0x6e, 0x96, 0xdc, 0xa7, 0x6b, 0xca, 0x04, 0x6d, 0x30, 0xd7, 0x14, 0xf6, 0xa7, 0x15, 0x7c, 0x8f, 0xb4, 0xee, 0x82, 0x57, 0xc4, 0xcd, 0x55, 0xac, 0xa7, 0x57, 0xe9, 0x40, 0xdc, 0x83, 0x61, 0xce, 0x06, 0xad, 0x6b, 0xfb, 0xca, 0x5b, 0x1e, 0x72, 0x3c, 0xbb, 0x3c, 0x7c, 0xdc, 0xf5, 0xd7, 0x05, 0xc5, 0xc6, 0xa6, 0x72, 0x05, 0x82, 0x07, 0xe4, 0xa0, 0xd8, 0x2c, 0xbc, 0xf1, 0x18, 0x20, 0x86, 0xec, 0x66, 0x5f, 0xec, 0x3d, 0x10, 0x79, 0x1e, 0xb6, 0xbd, 0x75, 0x45, 0x22, 0x65, 0x5c, 0x5c, 0x2b, 0x27, 0x50, 0x82, 0xcc, 0x02, 0x7f, 0x21, 0x5b, 0x3e, 0x23, 0x15, 0xd0, 0xa4, 0xb9, 0x28, 0x10, 0x21, 0xce, 0x34, 0xac, 0x12, 0x5f, 0x7b, 0xb0, 0xd6, 0x7d, 0x79, 0xfd, 0x81, 0x3f, 0x12, 0xc4, 0xc8, 0xf3, 0x02, 0x93, 0x68, 0x26, 0x7b, 0x1b, 0x0d, 0x29, 0xd9, 0xeb, 0xfa, 0x06, 0xd2, 0xed, 0x71, 0x5b, 0x21, 0xc7, 0xb1, 0x26, 0x08, 0x93, 0xba, 0x7d, 0x61, 0x22, 0x4c, 0x2b, 0x91, 0x5f, 0x9a, 0x33, 0xda, 0x0b, 0x76, 0x93, 0x04, 0x47 };
+const unsigned int DRAGON_BIN_SIZE = 365;
+
+#endif // DRAGON_DATA_H
diff --git a/4-ShellP2/dsh_cli.c b/4-ShellP2/dsh_cli.c
new file mode 100644
index 0000000000000000000000000000000000000000..9262cf457e6b235b19999efa04153e1de0962255
--- /dev/null
+++ b/4-ShellP2/dsh_cli.c
@@ -0,0 +1,13 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "dshlib.h"
+
+/* DO NOT EDIT
+ * main() logic moved to exec_local_cmd_loop() in dshlib.c
+*/
+int main(){
+  int rc = exec_local_cmd_loop();
+  printf("cmd loop returned %d\n", rc);
+}
\ No newline at end of file
diff --git a/4-ShellP2/dshlib.c b/4-ShellP2/dshlib.c
new file mode 100644
index 0000000000000000000000000000000000000000..1e9f5f6b448992d0d49dcb7fc28cbbce82676d6b
--- /dev/null
+++ b/4-ShellP2/dshlib.c
@@ -0,0 +1,216 @@
+#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"
+
+/*
+ * 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()
+ */
+
+static int last_exit_status = 0; // Store last exit code
+
+int alloc_cmd_buff(cmd_buff_t *cmd_buff) {
+    cmd_buff->_cmd_buffer = (char *)malloc(SH_CMD_MAX * sizeof(char));
+    if (!cmd_buff->_cmd_buffer) {
+        return ERR_MEMORY;
+    }
+    memset(cmd_buff->_cmd_buffer, 0, SH_CMD_MAX);
+    return OK;
+}
+
+int free_cmd_buff(cmd_buff_t *cmd_buff) {
+    if (cmd_buff->_cmd_buffer) {
+        free(cmd_buff->_cmd_buffer);
+        cmd_buff->_cmd_buffer = NULL;
+    }
+    return OK;
+}
+
+int clear_cmd_buff(cmd_buff_t *cmd_buff) {
+    memset(cmd_buff, 0, sizeof(cmd_buff_t));
+    return OK;
+}
+
+int build_cmd_buff(char *cmd_line, cmd_buff_t *cmd_buff) {
+    clear_cmd_buff(cmd_buff);
+
+    int i = 0;
+    char *cmd_start = cmd_line;
+    char *token_start = NULL;
+    bool in_quotes = false;
+
+    while (*cmd_start) {
+        // Detect start of quoted argument
+        if (*cmd_start == '\"' && !in_quotes) {
+            in_quotes = true;
+            token_start = ++cmd_start;
+        }
+        // Detect end of quoted argument
+        else if (*cmd_start == '\"' && in_quotes) {
+            in_quotes = false;
+            *cmd_start = '\0';
+            cmd_buff->argv[i++] = token_start;
+        }
+        // Handle normal argument (non-space outside quotes)
+        else if (!in_quotes && (*cmd_start != ' ' && *cmd_start != '\t')) {
+            token_start = cmd_start;
+            while (*cmd_start != ' ' && *cmd_start != '\t' && *cmd_start != '\0') {
+                cmd_start++;  
+            }
+            if (*cmd_start != '\0') {
+                *cmd_start = '\0';
+                cmd_start++;
+            }
+            cmd_buff->argv[i++] = token_start;
+        } 
+        else {
+            cmd_start++;
+        }
+    }
+
+    cmd_buff->argv[i] = NULL;
+    cmd_buff->argc = i;
+
+    if (i > 0) {
+        return OK;
+    } else {
+        return WARN_NO_CMDS;
+}
+}
+
+Built_In_Cmds match_command(const char *input) {
+    if (strcmp(input, "exit") == 0) {
+        return BI_CMD_EXIT;
+    } else if (strcmp(input, "cd") == 0) {
+        return BI_CMD_CD;
+    } else if (strcmp(input, "dragon") == 0) {
+        return BI_CMD_DRAGON;
+    } else if (strcmp(input, "rc") == 0) {
+        return BI_RC;
+    }
+    return BI_NOT_BI;
+}
+
+Built_In_Cmds exec_built_in_cmd(cmd_buff_t *cmd) {
+    Built_In_Cmds cmd_type = match_command(cmd->argv[0]);
+    if (cmd_type == BI_CMD_EXIT) {
+        return BI_CMD_EXIT;
+    }
+    if (cmd_type == BI_CMD_CD) {
+        if (cmd->argc > 1) {
+            if (chdir(cmd->argv[1]) != 0) {
+                fprintf(stderr, "cd: %s: No such file or directory\n", cmd->argv[1]);
+                return ERR_EXEC_CMD;
+            }
+        }
+        return BI_EXECUTED;
+    }
+    if (cmd_type == BI_CMD_DRAGON) {
+        print_dragon();
+        return BI_EXECUTED;
+    }
+    if (cmd_type == BI_RC) {
+        printf("%d\n", last_exit_status);
+        return BI_EXECUTED;
+    }
+    return BI_NOT_BI;
+}
+
+int exec_cmd(cmd_buff_t *cmd) {
+    pid_t pid = fork();
+    if (pid == -1) {
+        perror("fork");
+        return ERR_EXEC_CMD;
+    } else if (pid == 0) {
+        execvp(cmd->argv[0], cmd->argv);
+        fprintf(stderr, "Error executing %s: %s\n", cmd->argv[0], strerror(errno));
+        exit(errno);
+    } else {
+        int status;
+        waitpid(pid, &status, 0);
+        if (WIFEXITED(status)) {
+            last_exit_status = WEXITSTATUS(status);
+        } else {
+            last_exit_status = -1;
+        }
+        return last_exit_status;
+    }
+}
+
+int exec_local_cmd_loop() {
+    char cmd_line[SH_CMD_MAX];
+    cmd_buff_t cmd;
+    Built_In_Cmds result;
+
+    while (1) {
+        printf("%s", SH_PROMPT);
+        if (fgets(cmd_line, sizeof(cmd_line), stdin) == NULL) {
+            printf("\n");
+            break;
+        }
+        cmd_line[strcspn(cmd_line, "\n")] = '\0';
+
+        if (build_cmd_buff(cmd_line, &cmd) == WARN_NO_CMDS) {
+            printf(CMD_WARN_NO_CMD);
+            continue;
+        }
+
+        result = exec_built_in_cmd(&cmd);
+        if (result == BI_CMD_EXIT) {
+            free_cmd_buff(&cmd);
+            return OK_EXIT;
+            break;
+        } else if (result == BI_NOT_BI) {
+            last_exit_status = exec_cmd(&cmd);
+        }
+    }
+
+    free_cmd_buff(&cmd);
+    return OK;
+}
\ No newline at end of file
diff --git a/4-ShellP2/dshlib.h b/4-ShellP2/dshlib.h
new file mode 100644
index 0000000000000000000000000000000000000000..e1d168fc87297d388aea97b687fd2819bcf5cad3
--- /dev/null
+++ b/4-ShellP2/dshlib.h
@@ -0,0 +1,80 @@
+#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 cmd_buff
+{
+    int  argc;
+    char *argv[CMD_ARGV_MAX];
+    char *_cmd_buffer;
+} cmd_buff_t;
+
+/* WIP - Move to next assignment 
+#define N_ARG_MAX    15     //MAX number of args for a command
+typedef struct command{
+    char exe [EXE_MAX];
+    char args[ARG_MAX];
+    int  argc;
+    char *argv[N_ARG_MAX + 1];  //last argv[LAST] must be \0
+}command_t;
+*/
+
+
+//Special character #defines
+#define SPACE_CHAR  ' '
+#define PIPE_CHAR   '|'
+#define PIPE_STRING "|"
+
+#define SH_PROMPT "dsh2> "
+#define EXIT_CMD "exit"
+
+//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);
+void print_dragon();
+
+//built in command stuff
+typedef enum {
+    BI_CMD_EXIT,
+    BI_CMD_DRAGON,
+    BI_CMD_CD,
+    BI_NOT_BI,
+    BI_EXECUTED,
+    BI_RC,
+} 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);
+
+
+
+
+//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"
+
+#endif
\ No newline at end of file
diff --git a/4-ShellP2/fork-exec-1.png b/4-ShellP2/fork-exec-1.png
new file mode 100644
index 0000000000000000000000000000000000000000..56186a921c27a47641e54efbb72397c544f93a32
Binary files /dev/null and b/4-ShellP2/fork-exec-1.png differ
diff --git a/4-ShellP2/fork-exec-2.png b/4-ShellP2/fork-exec-2.png
new file mode 100644
index 0000000000000000000000000000000000000000..30e5f4ff67c5baaaa99bf7fee75e4f2a7b802bde
Binary files /dev/null and b/4-ShellP2/fork-exec-2.png differ
diff --git a/4-ShellP2/makefile b/4-ShellP2/makefile
new file mode 100644
index 0000000000000000000000000000000000000000..0bb94789e9fa66834a72a42118d5664824a30a36
--- /dev/null
+++ b/4-ShellP2/makefile
@@ -0,0 +1,32 @@
+# Compiler settings
+CC = gcc
+CFLAGS = -Wall -Wextra -g
+LDFLAGS = -lz
+
+# 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) $(LDFLAGS)
+
+# 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/4-ShellP2/questions.md b/4-ShellP2/questions.md
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/4-ShellP2/readme.md b/4-ShellP2/readme.md
new file mode 100644
index 0000000000000000000000000000000000000000..a0a38162c5704952d2bb1be57e4872f648c42858
--- /dev/null
+++ b/4-ShellP2/readme.md
@@ -0,0 +1,192 @@
+# Assignment: Custom Shell Part 2 - Fork/Exec
+
+This week we will build on our `dsh` Drexel Shell by adding an implementation for the builtin command `cd`, and a `fork/exec` implementation to run "external commands".
+
+This content builds on the prior assignment; if you need a refresher on what a shell is or the difference between built-in and external commands, please review the readme from that assignment.
+
+# Reuse Prior Work!
+
+The `dsh` assignments are meant to be additive. Much of the parsing logic from the last assignement can be re-used in this assignement. The structures are a little different so you might have to refactor some of your code, but that's a great practical lesson in software engineering; the highest quality results come from frequent iteration.
+
+The next section highlights the differences (both conceptually and in file structure) from the prior assignement.
+
+# Differences from Part 1 Assignment
+
+- We've restructured the code slightly to move all implementation details into the lib file (`dshlib.c`) out of `dsh_cli.c`. **You do not need to write any code in `dsh_cli.c`!**
+- This week we'll implement a fork/exec pattern to execute external commands; these commands should execute and behave just as they would if you ran them from your default shell; last week we only printed command lines that we parsed
+- If you did the `dragon` extra credit, we moved the implementation into `dragon.c`
+- We will NOT implement pipe splitting or multiple commands in one command line input; last week we implemented parsing the CLI by pipe just to print commands, but actually implementing pipes to execute commands is beyond the scope of this week's assignement - we will get to it but not this week!
+  - This week we work with a single `cmd_buff` type at a time; we will not use the `command_list_t` from last week
+  - This an example of some refactoring from last week's code, you can adapt your parsing logic but omit the pipe logic until we get to that in a future assignement
+
+# Fork / Exec
+
+Let's introduce two new system calls: fork() and exec(). These calls are fundamental to process creation and execution in all Unix-like operating systems. 
+
+When a process calls fork(), the operating system creates a new child process that is an exact copy of the parent, inheriting its memory, file descriptors, and execution state. The child process receives a return value of 0 from fork(), while the parent receives the child's process ID. After forking, the child process often replaces its memory image with a new executable using one of the exec() family of functions (e.g., execl(), execv(), execvp()). 
+
+Unlike fork(), exec() does not create a new process but instead replaces the calling process’s address space with a new program, preserving file descriptors unless explicitly changed. This mechanism allows Unix shells to execute new programs by first forking a child process and then using exec() to run the desired binary while the parent process waits for the child to complete using wait().
+
+Recall the fork/exec pattern from lecture slides and demo - we are implementing this two-step process using system calls.
+
+![fork-exec](fork-exec-1.png)
+
+![fork-exec](fork-exec-2.png)
+
+Remember that the fork/exec pattern requires you to use conditional branching logic to implement the child path and the parent path in the code. We did a basic demo of this in class using this demo code https://github.com/drexel-systems/SysProg-Class/blob/main/demos/process-thread/2-fork-exec/fork-exec.c. In the demo we used `execv()`, which requires an absolute path to the binary. In this assignement you should use `execvp()`; `execvp()` will search the `PATH` variable locations for binaries. As with the demo, you can use `WEXITSTATUS` to extract the status code from the child process.
+
+# Assignment Details
+
+### Step 1 - Review [./starter/dshlib.h](./starter/dshlib.h)
+
+The file [./starter/dshlib.h](./starter/dshlib.h) contains some useful definitions and types. Review the available resources in this file before you start coding - these are intended to make your work easier and more robust!
+
+### Step 2 - Implement `cd` in [./starter/dshlib.c](./starter/dshlib.c)
+
+Building on your code from last week, implement the `cd` command.
+
+- when called with no arguments, `cd` does nothing (this is different than Linux shell behavior; shells implement `cd` as `cd ~1` or `cd $HOME`; we'll do that in a future assignement)
+- when called with one argument, `chdir()` the current dsh process into the directory provided by argument
+
+### Step 3 - Re-implement Your Main Loop and Parsing Code in exec_local_cmd_loop() [./starter/dshlib.c](./starter/dshlib.c)
+
+Implement `exec_local_cmd_loop()` by refactoring your code from last week to use 1 `cmd_buff` type in the main loop instead of using a command list. 
+
+On each line-of-input parsing, you should populate `cmd_buff` using these rules:
+
+- trim ALL leading and trailing spaces
+- eliminate duplicate spaces UNLESS they are in a quoted string
+- account for quoted strings in input; treat a quoted string with spaces as a single argument
+  - for example, given ` echo    " hello,    world"  ` you would parse this as: `["echo", " hello,    world"]`; note that spaces inside the double quotes were preserved
+
+`cmd_buff` is provided to get you started. You don't have to use this struct, but it is all that's required to parse a line of input into a `cmd_buff`.
+
+```c
+typedef struct cmd_buff
+{
+    int  argc;
+    char *argv[CMD_ARGV_MAX];
+    char *_cmd_buffer;
+} cmd_buff_t;
+```
+
+### Step 4 - Implement fork/exec pattern in [./starter/dshlib.c](./starter/dshlib.c)
+
+Implement fork/exec of external commands using `execvp()`. This is a pretty straight-forward task; once the command and it's arguments are parsed, you can pass them straight to `execvp()`.
+
+Don't forget to implement a wait of the return code, and extraction of the return code. We're not doing anything with the return code yet, unless you are doing extra credit.
+
+### Step 5 - Create BATS Tests
+
+So far we've provided pre-built a `test.sh` file with assigments. These files use the [bash-based BATS unit test framework](https://bats-core.readthedocs.io/en/stable/tutorial.html#your-first-test).
+
+Going forward, assignements will have a bats folder structure like this:
+
+- your-workspace-folder/
+  - bats/assignement_tests.sh
+  - bats/student_tests.sh
+
+**bats/assignment_tests.sh**
+
+- DO NOT EDIT THIS FILE
+- assignment_tests.sh contains tests that must pass to meet the requirements of the assignment
+- it is run as part of `make test`; remember to run this to verify your code
+
+**bats/student_tests.sh**
+
+- this file must contain YOUR test suite to help verify your code
+- for some assignments you will be graded on creation of the tests, and it is your responsibility to make sure the tests provide adequate coverage
+- this file is also run with `make test`
+
+**About BATS**
+
+Key points of BATS testing - 
+
+- file header is `#!/usr/bin/env bats` such that you can execute tests by simply running `./test_file.sh`
+- incorrect `\r\n` can cause execution to fail - easiest way to avoid is use the [drexel-cci](https://marketplace.visualstudio.com/items?itemName=bdlilley.drexel-cci) extension to download assignement code; if you do not use this, make sure you do not copy any windows line endings into the file during a copy/paste
+- assertions are in square braces
+  - example: check output `[ "$stripped_output" = "$expected_output" ]`
+  - example: check return code `$status` variable: `[ "$status" -eq 0 ]`
+
+Please review the BATS link above if you have questions on syntax or usage. You can also look at test files we provided with assignment for more examples. **You will be graded on the quality of breadth of your unit test suite.**
+
+What this means to you - follow these guidelines when writing tests:
+
+- cover every type of functionallity; for example, you need to cover built-in command and external commands
+- test for all use cases / edge cases - for example, for the built-in `cd` command you might want to verify that:
+  - when called without arguments, the working dir doesn't change (you could verify with `pwd`)
+  - when called with one argument, it changes directory to the given argument (again, you can verify with `pwd`)
+- be thorough - try to cover all the possible ways a user might break you program!
+- write tests first; this is called "Test Driven Development" - to learn more, check out [Martin Fowler on TDD](https://martinfowler.com/bliki/TestDrivenDevelopment.html)
+
+### Step 6 - Answer Questions
+
+Answer the questions located in [./questions.md](./questions.md).
+
+### Sample Run with Sample Output
+The below shows a sample run executing multiple commands and the expected program output:
+
+```bash
+./dsh 
+dsh2> uname -a
+Linux ubuntu 6.12.10-orbstack-00297-gf8f6e015b993 #42 SMP Sun Jan 19 03:00:07 UTC 2025 aarch64 aarch64 aarch64 GNU/Linux
+dsh2> uname
+Linux
+dsh2> echo "hello,      world"
+hello,      world
+dsh2> pwd
+/home/ben/SysProg-Class-Solutions/assignments/4-ShellP2/solution
+dsh2> ls 
+dir1  dragon.c  dragon.txt  dsh  dsh_cli.c  dshlib.c  dshlib.h  fancy_code_do_not_use  makefile  shell_roadmap.md  test.sh  wip
+dsh2> cd dir1
+dsh2> pwd
+/home/ben/SysProg-Class-Solutions/assignments/4-ShellP2/solution/dir1
+dsh2> 
+```
+
+### Extra Credit: +10
+
+This week we're being naive about return codes from external commands; if there is any kind of failure, we just print the `CMD_ERR_EXECUTE` message.
+
+Implement return code handling for extra credit. Hint - check out `man execvp` and review the `errno` and return value information.
+
+Errno and value definitions are in `#include <errno.h>`. 
+
+Tips:
+
+- in the child process, `errno` will contain the error value if there was an error; so return this from your child process
+- the `WEXITSTATUS` macro will extract `errno`
+
+Requirements:
+
+- Check for all file-related status codes from `errno.h` that you might expect when trying to invoke a binary from $PATH; for example - `ENOENT` is file not found, `EACCES` is permission denied
+- Print a suitable message for each error you detect
+- Implement a "rc" builtin command that prints the return code of the last operation; for example, if the child process returns `-1`, `rc` should output `-1`
+- **Don't forget to add unit tests in** `./bats/student_tests.sh`!
+
+Example run:
+
+```bash
+./dsh
+dsh2> not_exists
+Command not found in PATH
+dsh2> rc
+2
+dsh2>
+```
+
+This extra credit is a precursor to implementing variables; shells set the variable `$?` to the return code of the last executed command. A full variable implementation is beyond the scope of this assignement, so we opted to create the `rc` builtin to mimic the behavior of the `$?` variable in other shells.
+
+#### Grading Rubric
+
+This assignment will be weighted 50 points.
+
+- 25 points:  Correct implementation of required functionality
+- 5 points:  Code quality (how easy is your solution to follow)
+- 15 points: Answering the written questions: [questions.md](./questions.md)
+- 15 points: Quality and breadth of BATS unit tests
+- 10 points:  [EXTRA CREDIT] handle return codes for execvp
+
+Total points achievable is 70/60. 
+
+