diff --git a/hw4/fork-exec-1.png b/hw4/fork-exec-1.png new file mode 100644 index 0000000000000000000000000000000000000000..56186a921c27a47641e54efbb72397c544f93a32 Binary files /dev/null and b/hw4/fork-exec-1.png differ diff --git a/hw4/fork-exec-2.png b/hw4/fork-exec-2.png new file mode 100644 index 0000000000000000000000000000000000000000..30e5f4ff67c5baaaa99bf7fee75e4f2a7b802bde Binary files /dev/null and b/hw4/fork-exec-2.png differ diff --git a/hw4/questions.md b/hw4/questions.md new file mode 100644 index 0000000000000000000000000000000000000000..f3e6ead3c9128d3910fec0fdb3ddef551fd2f55c --- /dev/null +++ b/hw4/questions.md @@ -0,0 +1,41 @@ +1. Can you think of why we use `fork/execvp` instead of just calling `execvp` directly? What value do you think the `fork` provides? + + > **Answer**: _start here_ + +2. What happens if the fork() system call fails? How does your implementation handle this scenario? + + > **Answer**: _start here_ + +3. How does execvp() find the command to execute? What system environment variable plays a role in this process? + + > **Answer**: _start here_ + +4. What is the purpose of calling wait() in the parent process after forking? What would happen if we didn’t call it? + + > **Answer**: _start here_ + +5. In the referenced demo code we used WEXITSTATUS(). What information does this provide, and why is it important? + + > **Answer**: _start here_ + +6. Describe how your implementation of build_cmd_buff() handles quoted arguments. Why is this necessary? + + > **Answer**: _start here_ + +7. What changes did you make to your parsing logic compared to the previous assignment? Were there any unexpected challenges in refactoring your old code? + + > **Answer**: _start here_ + +8. For this quesiton, you need to do some research on Linux signals. You can use [this google search](https://www.google.com/search?q=Linux+signals+overview+site%3Aman7.org+OR+site%3Alinux.die.net+OR+site%3Atldp.org&oq=Linux+signals+overview+site%3Aman7.org+OR+site%3Alinux.die.net+OR+site%3Atldp.org&gs_lcrp=EgZjaHJvbWUyBggAEEUYOdIBBzc2MGowajeoAgCwAgA&sourceid=chrome&ie=UTF-8) to get started. + +- What is the purpose of signals in a Linux system, and how do they differ from other forms of interprocess communication (IPC)? + + > **Answer**: _start here_ + +- Find and describe three commonly used signals (e.g., SIGKILL, SIGTERM, SIGINT). What are their typical use cases? + + > **Answer**: _start here_ + +- What happens when a process receives SIGSTOP? Can it be caught or ignored like SIGINT? Why or why not? + + > **Answer**: _start here_ diff --git a/hw4/readme.md b/hw4/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..a0a38162c5704952d2bb1be57e4872f648c42858 --- /dev/null +++ b/hw4/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. + + + + + +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. + + diff --git a/hw4/starter/.debug/launch.json b/hw4/starter/.debug/launch.json new file mode 100644 index 0000000000000000000000000000000000000000..aedc1efa4984aaf3265f70a3581d4956d569cf26 --- /dev/null +++ b/hw4/starter/.debug/launch.json @@ -0,0 +1,29 @@ +{ + "configurations": [ + { + "name": "(gdb) 4-ShellP2", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/4-ShellP2/starter/dsh", + "args": [""], + "stopAtEntry": false, + "cwd": "${workspaceFolder}/4-ShellP2/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 4-ShellP2" + } + ] +} \ No newline at end of file diff --git a/hw4/starter/.debug/tasks.json b/hw4/starter/.debug/tasks.json new file mode 100644 index 0000000000000000000000000000000000000000..71013e37fe8ec4999fb301dae2b55b1fc9b72543 --- /dev/null +++ b/hw4/starter/.debug/tasks.json @@ -0,0 +1,20 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Build 4-ShellP2", + "type": "shell", + "command": "make", + "group": { + "kind": "build", + "isDefault": true + }, + "options": { + "cwd": "${workspaceFolder}/4-ShellP2/starter" + }, + "problemMatcher": ["$gcc"], + "detail": "Runs the 'make' command to build the project." + } + ] + } + \ No newline at end of file diff --git a/hw4/starter/.gitignore b/hw4/starter/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..eb47a8e82ad127f89b6fdbbf0f531cb8865b7bf8 --- /dev/null +++ b/hw4/starter/.gitignore @@ -0,0 +1 @@ +dsh \ No newline at end of file diff --git a/hw4/starter/bats/assignment_tests.sh b/hw4/starter/bats/assignment_tests.sh new file mode 100755 index 0000000000000000000000000000000000000000..794b5d8d307cc8fffeb3eb23ade710823c61c9b0 --- /dev/null +++ b/hw4/starter/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/hw4/starter/bats/student_tests.sh b/hw4/starter/bats/student_tests.sh new file mode 100755 index 0000000000000000000000000000000000000000..638bc341446f7580a80c2aff52971b8023407ea8 --- /dev/null +++ b/hw4/starter/bats/student_tests.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/hw4/starter/dragon.c b/hw4/starter/dragon.c new file mode 100644 index 0000000000000000000000000000000000000000..83d01f243de7c7c98767c7dfd328588ec1f4b292 --- /dev/null +++ b/hw4/starter/dragon.c @@ -0,0 +1,6 @@ +#include <stdio.h> + +// EXTRA CREDIT - print the drexel dragon from the readme.md +extern void print_dragon(){ + // TODO implement +} diff --git a/hw4/starter/dsh.dSYM/Contents/Info.plist b/hw4/starter/dsh.dSYM/Contents/Info.plist new file mode 100644 index 0000000000000000000000000000000000000000..ec2ee631eae4517146ef8292a4ae925d46718d5c --- /dev/null +++ b/hw4/starter/dsh.dSYM/Contents/Info.plist @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> + <dict> + <key>CFBundleDevelopmentRegion</key> + <string>English</string> + <key>CFBundleIdentifier</key> + <string>com.apple.xcode.dsym.dsh</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundlePackageType</key> + <string>dSYM</string> + <key>CFBundleSignature</key> + <string>????</string> + <key>CFBundleShortVersionString</key> + <string>1.0</string> + <key>CFBundleVersion</key> + <string>1</string> + </dict> +</plist> diff --git a/hw4/starter/dsh_cli.c b/hw4/starter/dsh_cli.c new file mode 100644 index 0000000000000000000000000000000000000000..9262cf457e6b235b19999efa04153e1de0962255 --- /dev/null +++ b/hw4/starter/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/hw4/starter/dshlib.c b/hw4/starter/dshlib.c new file mode 100644 index 0000000000000000000000000000000000000000..4b002a070c1966f1d015f430b978d39dba57b874 --- /dev/null +++ b/hw4/starter/dshlib.c @@ -0,0 +1,185 @@ +#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" + +/* + * 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() + */ + +cmd_buff_t parse_command(char *cmd_buff, cmd_buff_t cmd){ + char *pointer = cmd_buff; + while (*pointer == ' '){ + pointer++; + } + char *space; + cmd.argc = 0; + int count = 0; + char *org = pointer; + + while (pointer!=NULL){ + if (*pointer=='\"'){ + pointer++; + org = pointer; + while(*pointer!='\"'){ + pointer++; + } + space = pointer; + } else{ + space = strchr(pointer, SPACE_CHAR); + if (space==NULL){ + size_t exe_length = strlen(pointer); + cmd.argv[count] = malloc(exe_length + 1); + strncpy(cmd.argv[count], org, exe_length); + cmd.argv[count][exe_length] = '\0'; + if (strcmp(cmd.argv[count],"") != 0){ + count++; + cmd.argc++; + } + pointer = space; + continue; + } + } + + size_t exe_length = space - org; + cmd.argv[count] = malloc(exe_length + 1); + strncpy(cmd.argv[count], org, exe_length); + cmd.argv[count][exe_length] = '\0'; + count++; + cmd.argc++; + pointer = space+1; + org = pointer; + } + + + // printf("\"%s\" ", cmd.argv[0]); + // for (int i=1; i<count; i++){ + // printf(", \"%s\" ", cmd.argv[i]); + + // } + // printf("\n"); + cmd.argv[cmd.argc] = NULL; + return cmd; +} + +int exec_local_cmd_loop() +{ + char *cmd_buff = malloc(SH_CMD_MAX); + int rc = 0; + cmd_buff_t cmd; + + // TODO IMPLEMENT MAIN LOOP + 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'; + + if (cmd_buff[0] == '\0'){ + printf(CMD_WARN_NO_CMD); + continue; + } + + // TODO IMPLEMENT parsing input to cmd_buff_t *cmd_buff + cmd = parse_command(cmd_buff, cmd); + + // TODO IMPLEMENT if built-in command, execute builtin logic for exit, cd (extra credit: dragon) + // the cd command should chdir to the provided directory; if no directory is provided, do nothing + if (strcmp(cmd_buff, EXIT_CMD) == 0){ + break; + } + if (strcmp(cmd.argv[0], "cd") == 0){ + + if (cmd.argc < 2){ + + continue; + } else { + // if(chdir(cmd.argv[1]) == 0){ + // printf("Success"); + // } else{ + // printf("%s\n", cmd.argv[1]); + // } + // printf("ok\n"); + chdir(cmd.argv[1]); + // printf() + } + } else { + + pid_t pid = fork(); + + if (pid==0){ + int i = execvp(cmd.argv[0], cmd.argv); + if (i==-1){ + printf("fail\n"); + exit(1); + } + exit(0); + } else { + int wait_code; + waitpid(pid, &wait_code, 0); + } + } + + for (int i=1; i<cmd.argc; i++){ + free(cmd.argv[i]); + } + + + } + + + // TODO IMPLEMENT if not built-in command, fork/exec as an external command + // for example, if the user input is "ls -l", you would fork/exec the command "ls" with the arg "-l" + + free(cmd_buff); + return OK; +} + diff --git a/hw4/starter/dshlib.h b/hw4/starter/dshlib.h new file mode 100644 index 0000000000000000000000000000000000000000..32059ccede7dec47a785b55185b1f5dda105145d --- /dev/null +++ b/hw4/starter/dshlib.h @@ -0,0 +1,79 @@ +#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); + +//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/hw4/starter/makefile b/hw4/starter/makefile new file mode 100644 index 0000000000000000000000000000000000000000..b14b0723e23ea2036f0220406b40a93e6a0cd283 --- /dev/null +++ b/hw4/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/hw4/starter/ok.dSYM/Contents/Info.plist b/hw4/starter/ok.dSYM/Contents/Info.plist new file mode 100644 index 0000000000000000000000000000000000000000..01a73dbe2c82ef0bcd4c0e1ed9942751d19ecc62 --- /dev/null +++ b/hw4/starter/ok.dSYM/Contents/Info.plist @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> + <dict> + <key>CFBundleDevelopmentRegion</key> + <string>English</string> + <key>CFBundleIdentifier</key> + <string>com.apple.xcode.dsym.ok</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundlePackageType</key> + <string>dSYM</string> + <key>CFBundleSignature</key> + <string>????</string> + <key>CFBundleShortVersionString</key> + <string>1.0</string> + <key>CFBundleVersion</key> + <string>1</string> + </dict> +</plist> diff --git a/hw4/starter/ok.dSYM/Contents/Resources/DWARF/ok b/hw4/starter/ok.dSYM/Contents/Resources/DWARF/ok new file mode 100644 index 0000000000000000000000000000000000000000..f5692c73526cca8881535b752238d63bb593d139 Binary files /dev/null and b/hw4/starter/ok.dSYM/Contents/Resources/DWARF/ok differ diff --git a/hw4/starter/shell_roadmap.md b/hw4/starter/shell_roadmap.md new file mode 100644 index 0000000000000000000000000000000000000000..9af14341f8cd435e39cfa8c1bc6666428be203dd --- /dev/null +++ b/hw4/starter/shell_roadmap.md @@ -0,0 +1,13 @@ +## Proposed Shell Roadmap + +Week 5: Shell CLI Parser Due + +Week 6: Execute Basic Commands + +Week 7: Add support for pipes (extra-credit for file redirection) + +Week 8: Basic socket assignment + +Week 9: Remote Shell Extension + +Week 11: Add support for something like `nsenter` to enter a namespace \ No newline at end of file