Skip to content
Snippets Groups Projects
Commit 2599b1ca authored by sjy36's avatar sjy36
Browse files

5-ShellP3, no extra credit

parent 19d20cc6
Branches
No related tags found
No related merge requests found
Showing
with 1035 additions and 0 deletions
# Code Grading Instructions
Use the requirements and possible grade below. If the requirements are not meant, deduct points from the possible grade.
# REQUIREMENTS
- distinquish internal vs. external commands
- internal commands are implemented in C code
- external commands must be run with fork/exec
- MUST IMPLEMENT INTERNAL COMMANDS:
- implement "cd" internal command
- if cd is provided with arguments, attmpt to change the current process working directory to the provided argument
- if no argument is provided to cd, it should do nothing
- the code must implement "exit" as an internal command, exiting with a return code of 0
- MUST IMPLEMENT EXTERNAL COMMANDS:
- all commands that are not internal are considered external
- if more than one command is provided separated by the pipe char |, those commands must be executed with fork/exec AND be connected with pipes
# OPTIONAL EXTRA CREDIT REQUIREMENTS
- extra credit 1 - implement < and > for redirection bewteen commands
- extra credit 2 - implement >> append redirection
# Possible Grade
- 50 points: Correct implementation of required functionality
- 5 points: Code quality (how easy is your solution to follow)
- 10 points: Quality and breadth of BATS unit tests
- 10 points: [EXTRA CREDIT 1] handle < and > redirection
- 5 points: [EXTRA CREDIT 2] handle >> append redirection
\ No newline at end of file
rubric:
possibleScore:
- requirement: correct implementation of required functionality
points: 50
- requirement: code quality (how easy is your solution to follow)
points: 10
possibleExtraCredotScore:
- requirement: implement < and > redirection
points: 10
- requirement: implement append >> redirection
points: 5
requirements:
- description: implement the cd internal command
criteria:
- cd command is implemented directly in code
- with no arguments does nothing
- with 1 argument changes directory of the current process to the argument
- description: implement the exit internal command
criteria:
- exit command is implemented directly in code
- exits the process with return code 0
- description: implement other commands with fork/exec and pipe support
criteria:
- other than exit and cd, other commands should be implemented with fork/exec
- multiple commands with pipes must be supported
- description: optional extra credit to implement < and > file redirction
criteria:
- must support file redirection with < and >
- description: optional extra credit to implement append >> redirection
criteria:
- must support append redirection with >>
1. Your shell forks multiple child processes when executing piped commands. How does your implementation ensure that all child processes complete before the shell continues accepting user input? What would happen if you forgot to call waitpid() on all child processes?
The implementation makes sure all child provesses are complete by waitpid() system calls b/c the parent waits for a specific child to terminate before returning something. If the waitpid() is not called, it can cause a zombie b/c it's the process is basically 'dead' but exist as a placeholder. Another possible outcome is wrong shell output.
2. The dup2() function is used to redirect input and output file descriptors. Explain why it is necessary to close unused pipe ends after calling dup2(). What could go wrong if you leave pipes open?
It's necessary to avoid resource leaks and make sure everything is properly sinaled. If a process fails to close its unused write end of a pipe, the process will not receive an EOF signal. If you leave pipes open, it'll have hanging processes.
3. Your shell recognizes built-in commands (cd, exit, dragon). Unlike external commands, built-in commands do not require execvp(). Why is cd implemented as a built-in rather than an external command? What challenges would arise if cd were implemented as an external process?
cd is implemented as a built-in command b/c it directly changes the shell's directory. If I made cd a external command, it would be totally separate process and it won't impact or change the parent shell.
4. Currently, your shell supports a fixed number of piped commands (CMD_MAX). How would you modify your implementation to allow an arbitrary number of piped commands while still handling memory allocation efficiently? What trade-offs would you need to consider?
In order to support an arbitrary number, I would have to stop usong a fixed-size array to a dynamuc allocation. So insted of CMD_MAX it proably be a linked list. The trade-offs are very good error handling b/c one little mistep will mess up everything.
# Assignment: Custom Shell Part 3 - Pipes
This week we will build on our `dsh` Drexel Shell by implementing pipes. In the first shell assignment we approached this subject by splitting lines of input by the pipe `|` and printing out each separate command parsed between the pipes. In the second assignment we set the pipe splitting logic aside and implemented _execution_ of single commands.
This week we'll bring these concepts together by implementing pipelined execution of multiple commands!
# Reuse Prior Work!
The `dsh` assignments are meant to be additive. This week you'll need to use code you wrote in the first two shell assignments. Be sure to re-use your existing code, and refactor it to meet the requirements of this assignment.
# Pipes
This week you'll need to use a couple new important syscalls - `pipe()` and `dup2()`.
The `pipe()` command creates a 1-way communication path that is implemented with two file descriptions. Conceptually it looks like this:
```txt
process-1-output --write--> pipe[fd1]:pipe[fd0] --read--> process-2-input
```
How do the pipes get "connected" between the processes? That's where `dup2()` comes in.
> Tip: pipes should be created and connected inside a `fork()`; you've already used `fork/execvp` to implement a single command. This time you'll need to do that in a loop, because each command should get it's own `fork/execvp`. So, you'll need to handle `pipe()` and `dup2()` _inside_ each child's fork.
Think of `dup2()` as sort of patching or redirection. The function signature is:
`dup2(source_fd, target_fd)`
Remember that each forked child has it's own process space and set of file descriptors, to include `STDIN_FILENO` and `STDOUT_FILENO`. So, inside each child process, you could use dup2 to _replace_ the child's STDIN and STDOUT with pipe file descriptors instead.
**Preventing Descriptor Leaks**
When you use `dup2()`, it _copies_ the source FD onto the target, effectively replacing the target. The source is no longer used, and can be closed. If you don't close the original pipes after copying with `dup2()`, you have introduced a **descriptor leak**; this could cause your program to run out of available file descriptors.
# Multiple Children / Waitpid
Since you must create multiple forks to handle piped commands, be sure to wait on all of them with `waitpid()`, which can be used to wait for a specific process id. _(Hint: you'll need to save the pid of each process you fork)._
# 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 - Re-implement Your Main Loop and Parsing Code in exec_local_cmd_loop() to parse multiple commands by pipe [./starter/dshlib.c](./starter/dshlib.c)
This should mostly be a refactoring, copy/paste exercise to merge your solutions from the first two shell assignments. You need to combine your pipe tokenizing solution with loading up `cmd_buff_t` ultimately into a `command_list_t`.
### Step 3 - Implement pipelined execution of the parsed commands [./starter/dshlib.c](./starter/dshlib.c)
The first part of this is a refactoring problem - instead of just one `fork/exec`, you'll need to do that in a loop and keep track of the child pids and child exit statuses.
The second part is implementing pipe logic. The section above named "Pipes" contains everything you really need to know! Also, check out demo code from this week's lecture [5-pipe-handling](https://github.com/drexel-systems/SysProg-Class/tree/main/demos/process-thread/5-pipe-handling).
### Step 4 - Create BATS Tests
You should now be familiar with authoring your own [bash-based BATS unit tests](https://bats-core.readthedocs.io/en/stable/tutorial.html#your-first-test) from the prior assignment.
Same as last week, you have an "assignment-tests.sh" file that we provide and you cannot change, and a "student_tests.sh" file that **you need to add your own tests to**:
- your-workspace-folder/
- bats/assignement_tests.sh
- bats/student_tests.sh
**This week we expect YOU to come up with the majority of the test cases! If you have trouble, look at the tests we provided from prior weeks**
**Be sure to run `make test` and verify your tests pass before submitting assignments.**
### Step 5 - 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
dsh3> ls | grep ".c"
dragon.c
dsh_cli.c
dshlib.c
dsh3> exit
exiting...
cmd loop returned 0
```
### Extra Credit: +10
Last week you should have learned about redirection (it was part of the assignment's research question). Once you've implemented pipes for this assignment, redirection is a natural next step.
**Requirements**
- parse `<` and `>` during command buffer creation; you'll need to parse and track these on `cmd_buff_t`
- modify command execution to use `dup2()` in a similar way as you did for pipes; you can copy the in and out fd's specified by the user on to STDIN or STDOUT of the child's forked process
Example run:
```bash
./dsh
dsh3> echo "hello, class" > out.txt
dsh3> cat out.txt
hello, class
```
### Extra Credit++: +5
Extend the first extra credit to implement `>>`. This is the same as `>`, except the target fd is in append mode.
Example run:
```bash
./dsh
dsh3> echo "hello, class" > out.txt
dsh3> cat out.txt
hello, class
dsh3> echo "this is line 2" >> out.txt
dsh3> cat out.txt
hello, class
this is line 2
dsh3>
```
#### Grading Rubric
- 50 points: Correct implementation of required functionality
- 5 points: Code quality (how easy is your solution to follow)
- 10 points: Answering the written questions: [questions.md](./questions.md)
- 10 points: Quality and breadth of BATS unit tests
- 10 points: [EXTRA CREDIT] handle < and > redirection
- 5 points: [EXTRA CREDIT++] handle >> append redirection
Total points achievable is 90/75.
{
"configurations": [
{
"name": "(gdb) 5-ShellP3",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/5-ShellP3/starter/dsh",
"args": [""],
"stopAtEntry": false,
"cwd": "${workspaceFolder}/5-ShellP3/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 5-ShellP3"
}
]
}
\ No newline at end of file
{
"version": "2.0.0",
"tasks": [
{
"label": "Build 5-ShellP3",
"type": "shell",
"command": "make",
"group": {
"kind": "build",
"isDefault": true
},
"options": {
"cwd": "${workspaceFolder}/5-ShellP3/starter"
},
"problemMatcher": ["$gcc"],
"detail": "Runs the 'make' command to build the project."
}
]
}
\ No newline at end of file
#!/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.cdsh3>dsh3>cmdloopreturned0"
# These echo commands will help with debugging and will only print
#if the test fails
echo "Captured stdout:"
echo "Output: $output"
echo "Exit Status: $status"
echo "${stripped_output} -> ${expected_output}"
# Check exact match
[ "$stripped_output" = "$expected_output" ]
# Assertions
[ "$status" -eq 0 ]
}
#!/usr/bin/env bats
# File: student_tests.sh
#
# Create your unit tests suit in this file
@test "Change directory" {
run ./dsh <<EOF
cd /tmp
pwd
exit
EOF
echo "Output: $output"
[[ "$output" == *"/tmp"* ]]
}
@test "Change directory - no args (should not change)" {
run ./dsh <<EOF
pwd
cd
pwd
exit
EOF
#get only the first and last pwd outputs
first_pwd=$(echo "$output" | grep -Eo '/.*' | head -n 1)
second_pwd=$(echo "$output" | grep -Eo '/.*' | tail -n 1)
echo "First PWD: $first_pwd"
echo "Second PWD: $second_pwd"
[[ "$first_pwd" == "$second_pwd" ]]
}
@test "External command execution - ls" {
run ./dsh <<EOF
ls
exit
EOF
#expect ls to return something
echo "Output: $output"
[[ -n "$output" ]]
}
@test "External command execution - echo" {
run ./dsh <<EOF
echo "Hello, World!"
exit
EOF
[[ "$output" == *"Hello, World!"* ]]
}
@test "Invalid command handling" {
run ./dsh <<EOF
nonexistentcommand
exit
EOF
#expect an error message that matches the expected output
[[ "$output" == *"Command not found in PATH"* ]]
}
@test "Exit command" {
run ./dsh <<EOF
exit
EOF
[[ "$status" -eq 0 ]]
}
@test "Extra Credit - Invalid command handling (ENOENT)" {
run ./dsh <<EOF
notacommand
rc
exit
EOF
#should see "Command not found" error followed by 2
echo "ACTUAL OUTPUT: $output"
[[ "$output" == *"Command not found in PATH"* ]]
[[ "$output" == *"2"* ]]
}
@test "Extra Credit - Permission denied command (EACCES)" {
touch ./no_exec
chmod -rwx ./no_exec
run ./dsh <<EOF
./no_exec
rc
exit
EOF
echo "ACTUAL OUTPUT: $output"
#match error message with filename
[[ "$output" == *"Permission denied: ./no_exec"* ]]
#exit code 13
[[ "$output" == *"13"* ]]
rm -f ./no_exec
}
@test "Extra Credit - RC command returns last exit code" {
run ./dsh <<EOF
notacommand
rc
exit
EOF
echo "Debug: output of rc test"
echo "$output"
#make sure rc outputs 2 after a failed command
[[ "$output" == *$'2\n'* ]]
}
@test "Pipeline with 3 commands" {
run ./dsh <<EOF
echo "hello" | tr 'h' 'H' | sed 's/ello//'
exit
EOF
[[ "$output" == *"H"* ]]
}
@test "Pipeline with command failure" {
run ./dsh 2>&1 <<EOF
ls | nonexistentcommand | wc -l
EOF
[[ "$output" == *"Command not found"* ]]
}
@test "Pipeline exit code reflects last command" {
run ./dsh <<EOF
ls | grep .c | false
exit
EOF
[ "$status" -eq 0 ]
}
\ No newline at end of file
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//same as in your main.c from 3-part1
const char *compressed_dragon =
"ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg"
"ICAgICAgICAgICAgICAgQCUlJSUgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAg"
"ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAl"
"JSUlJSUgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAg"
"ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAlICUlJSUlJSUgICAgICAgICAg"
"IEAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg"
"ICAgICAgICAgICAgICAgICAgICAgICAgJSUlJSUlJSUlJSAgICAgICAgJSUlJSUlJSAgICAgICAg"
"ICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICUlJSUlJSUgICUlJSVAICAgICAg"
"ICAgJSUlJSUlJSUlJSUlQCAgICAlJSUlJSUgIEAlJSUlICAgICAgICAKICAgICAgICAgICAgICAg"
"ICAgICAgICAgICAgICAgICAgICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgICAgICAlJSUlJSUlJSUl"
"JSUlJSUlJSUlJSUlJSUlICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg"
"ICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlICAgJSUlJSUlJSUlJSUlICUlJSUlJSUlJSUlJSUl"
"JSAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAlJSUlJSUlJSUlJSUl"
"JSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlICAgICAgICAgICAgICAK"
"ICAgICAgICAgICAgICAgICAgICAgICAgICAgICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUl"
"JSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlQCUlJSUlJUAgICAgICAgICAgICAgIAogICAgICAl"
"JSUlJSUlJUAgICAgICAgICAgICUlJSUlJSUlJSUlJSUlJSUgICAgICAgICUlJSUlJSUlJSUlJSUl"
"JSUlJSUlJSUlJSUlICAgICAgJSUgICAgICAgICAgICAgICAgCiAgICAlJSUlJSUlJSUlJSUlICAg"
"ICAgICAgJSVAJSUlJSUlJSUlJSUlICAgICAgICAgICAlJSUlJSUlJSUlJSAlJSUlJSUlJSUlJSUg"
"ICAgICBAJSAgICAgICAgICAgICAgICAKICAlJSUlJSUlJSUlICAgJSUlICAgICAgICAlJSUlJSUl"
"JSUlJSUlJSAgICAgICAgICAgICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAgICAgICAgICAgICAg"
"ICAgICAgICAgIAogJSUlJSUlJSUlICAgICAgICUgICAgICAgICAlJSUlJSUlJSUlJSUlICAgICAg"
"ICAgICAgICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlICAgICAgICAgICAgICAgICAgICAgICAg"
"CiUlJSUlJSUlJUAgICAgICAgICAgICAgICAgJSAlJSUlJSUlJSUlJSUlICAgICAgICAgICAgQCUl"
"JSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgICAgICAgICAgICAgICAgICAgICAKJSUlJSUlJSVAICAg"
"ICAgICAgICAgICAgICAlJUAlJSUlJSUlJSUlJSUgICAgICAgICAgICBAJSUlJSUlJSUlJSUlJSUl"
"JSUlJSUlJSUlJSUlJSAgICAgICAgICAgICAgICAgIAolJSUlJSUlJSUlICAgICAgICAgICAgICAg"
"ICAgJSUlJSUlJSUlJSUlJSUlJSUgICAgICAgICUlJSUlJSUlJSUlJSUgICAgICAlJSUlJSUlJSUl"
"JSUlJSUlJSUgJSUlJSUlJSUlCiUlJSUlJSUlJSUgICAgICAgICAgICAgICAgICAlJSUlJSUlJSUl"
"JSUlJSUgICAgICAgICAgJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgICAgICAl"
"JSUlICAKJSUlJSUlJSUlQCAgICAgICAgICAgICAgICAgICBAJSUlJSUlJSUlJSUlJSUgICAgICAg"
"ICAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlICAlJSUgCiAgICUlJSUl"
"JSUlJSUlJSAgQCAgICAgICAgICAgJSUlJSUlJSUlJSUlJSUlJSUlICAgICAgICAlJSUlJSUlJSUl"
"JSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlICAlJSUgCiAgICAlJSUlJSUlJSUlJSUlJSUl"
"JSUlJSAlJSUlJSUlJSUlJSUlJSUlJSUlICAgICAgICAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUl"
"JSUlJSUlJSUlJSUlJSUlJSUlICAKICAgICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUl"
"JSUlJSUlJSAgICAgICAgICAgICAgJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgICAgICAg"
"ICAgICAgICAKICAgICAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlICAgICAg"
"ICAgICAgICAgICAgICAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlICAgICAgICAgICAgICAKICAg"
"ICAgICAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAgICAgICAgICAgICAgICAgICAg"
"ICAgICAgICAlJSUlJSUlJSUlJSUlJSUlJSUlICAgICAgICAK";
void decode_dragon() {
FILE *pipe = popen("base64 -d", "w");
if (!pipe) {
printf("Error: Could not open decoding pipe\n");
return;
}
fwrite(compressed_dragon, 1, strlen(compressed_dragon), pipe);
pclose(pipe);
}
//function called from dsh to print the dragon
void print_dragon() {
decode_dragon();
}
\ No newline at end of file
#ifndef DRAGON_H
#define DRAGON_H
void print_dragon();
#endif
\ No newline at end of file
File added
#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
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdbool.h>
#include <unistd.h>
#include <errno.h>
#include <sys/wait.h>
#include "dshlib.h"
#include "dragon.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()
*/
// int exec_local_cmd_loop()
// {
// return OK;
// }
// #include "dragon.h"
int last_return_code = 0;
int alloc_cmd_buff(cmd_buff_t *cmd_buff) {
cmd_buff->_cmd_buffer = malloc(SH_CMD_MAX);
if (!cmd_buff->_cmd_buffer) return ERR_MEMORY;
memset(cmd_buff->_cmd_buffer, 0, SH_CMD_MAX);
cmd_buff->argc = 0;
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;
cmd_buff->argc = 0;
return OK;
}
int clear_cmd_buff(cmd_buff_t *cmd_buff) {
if (cmd_buff->_cmd_buffer) memset(cmd_buff->_cmd_buffer, 0, SH_CMD_MAX);
cmd_buff->argc = 0;
return OK;
}
int build_cmd_buff(char *cmd_line, cmd_buff_t *cmd_buff) {
if (!cmd_line || !*cmd_line) return WARN_NO_CMDS;
clear_cmd_buff(cmd_buff);
char *dst = cmd_buff->_cmd_buffer;
bool in_quote = false;
//trim leading spaces
while (*cmd_line == ' ') cmd_line++;
//parse command line
for (char *src = cmd_line; *src; src++) {
if (*src == '"' || *src == '\'') {
in_quote = !in_quote;
continue;
}
if (!in_quote && *src == ' ') {
//handle multiple spaces
if (dst == cmd_buff->_cmd_buffer || *(dst-1) == '\0') continue;
*dst++ = '\0';
} else {
*dst++ = *src;
}
}
*dst = '\0';
if (dst > cmd_buff->_cmd_buffer && *(dst-1) == '\0') dst--;
//populate argv
char *ptr = cmd_buff->_cmd_buffer;
while (*ptr && cmd_buff->argc < CMD_ARGV_MAX-1) {
cmd_buff->argv[cmd_buff->argc++] = ptr;
ptr += strlen(ptr) + 1;
}
cmd_buff->argv[cmd_buff->argc] = NULL;
return OK;
}
Built_In_Cmds exec_built_in_cmd(cmd_buff_t *cmd) {
if (cmd->argc == 0) return BI_NOT_BI;
if (strcmp(cmd->argv[0], "cd") == 0) {
if (cmd->argc == 1) return BI_EXECUTED;
if (chdir(cmd->argv[1])) {
fprintf(stderr, "cd: %s\n", strerror(errno));
last_return_code = 1;
}
return BI_EXECUTED;
}
if (strcmp(cmd->argv[0], "rc") == 0) {
printf("%d\n", last_return_code);
fflush(stdout);
return BI_EXECUTED;
}
if (strcmp(cmd->argv[0], "dragon") == 0) {
print_dragon();
return BI_EXECUTED;
}
if (strcmp(cmd->argv[0], EXIT_CMD) == 0) {
exit(0);
}
return BI_NOT_BI;
}
int exec_cmd(cmd_buff_t *cmd) {
pid_t pid = fork();
if (pid < 0) {
perror("fork failed");
return ERR_EXEC_CMD;
}
if (pid == 0) { //child process
execvp(cmd->argv[0], cmd->argv);
//deals with execvp errors
switch (errno) {
case ENOENT:
fprintf(stderr, "Command not found in PATH\n");
break;
case EACCES:
fprintf(stderr, "Permission denied: %s\n", cmd->argv[0]);
break;
default:
fprintf(stderr, "Execution failed: %s\n", strerror(errno));
}
exit(errno);
}
//parent process
int status;
waitpid(pid, &status, 0);
if (WIFEXITED(status)) {
last_return_code = WEXITSTATUS(status);
} else {
last_return_code = 1;
}
return last_return_code;
}
int build_cmd_list(char *cmd_line, command_list_t *clist) {
char *saveptr = NULL;
char *token;
int i = 0;
clist->num = 0;
token = strtok_r(cmd_line, "|", &saveptr);
while (token != NULL && i < CMD_MAX) {
while (*token == ' ') token++;
char *end = token + strlen(token) - 1;
while (end > token && *end == ' ') end--;
*(end + 1) = '\0';
if (strlen(token) == 0) {
token = strtok_r(NULL, "|", &saveptr);
continue;
}
if (alloc_cmd_buff(&clist->commands[i]) != OK) {
for (int j = 0; j < i; j++) {
free_cmd_buff(&clist->commands[j]);
}
clist->num = 0;
return ERR_MEMORY;
}
int rc = build_cmd_buff(token, &clist->commands[i]);
if (rc != OK) {
for (int j = 0; j <= i; j++) {
free_cmd_buff(&clist->commands[j]);
}
clist->num = 0;
return rc;
}
i++;
token = strtok_r(NULL, "|", &saveptr);
}
if (i == 0) {
return WARN_NO_CMDS;
}
if (token != NULL) {
for (int j = 0; j < i; j++) {
free_cmd_buff(&clist->commands[j]);
}
clist->num = 0;
return ERR_TOO_MANY_COMMANDS;
}
clist->num = i;
return OK;
}
int free_cmd_list(command_list_t *cmd_lst) {
for (int i = 0; i < cmd_lst->num; i++) {
free_cmd_buff(&cmd_lst->commands[i]);
}
cmd_lst->num = 0;
return OK;
}
int execute_pipeline(command_list_t *clist) {
int num_commands = clist->num;
if (num_commands == 0) {
return WARN_NO_CMDS;
}
int pipes[num_commands - 1][2];
for (int i = 0; i < num_commands - 1; i++) {
if (pipe(pipes[i]) == -1) {
perror("pipe");
return ERR_EXEC_CMD;
}
}
pid_t pids[num_commands];
for (int j = 0; j < num_commands; j++) {
pids[j] = fork();
if (pids[j] == -1) {
perror("fork");
for (int k = 0; k < num_commands - 1; k++) {
close(pipes[k][0]);
close(pipes[k][1]);
}
return ERR_EXEC_CMD;
} else if (pids[j] == 0) {
if (j != 0) {
dup2(pipes[j-1][0], STDIN_FILENO);
}
if (j != num_commands - 1) {
dup2(pipes[j][1], STDOUT_FILENO);
}
for (int k = 0; k < num_commands - 1; k++) {
close(pipes[k][0]);
close(pipes[k][1]);
}
execvp(clist->commands[j].argv[0], clist->commands[j].argv);
switch (errno) {
case ENOENT:
fprintf(stderr, "Command not found in PATH\n");
break;
case EACCES:
fprintf(stderr, "Permission denied: %s\n", clist->commands[j].argv[0]);
break;
default:
fprintf(stderr, "Execution failed: %s\n", strerror(errno));
}
exit(errno);
}
}
for (int i = 0; i < num_commands - 1; i++) {
close(pipes[i][0]);
close(pipes[i][1]);
}
int status;
int last_status = 0;
for (int j = 0; j < num_commands; j++) {
waitpid(pids[j], &status, 0);
if (j == num_commands - 1) {
last_status = WIFEXITED(status) ? WEXITSTATUS(status) : 1;
last_return_code = last_status;
}
}
return last_status;
}
int exec_local_cmd_loop() {
char input[SH_CMD_MAX];
command_list_t clist = {0};
while (1) {
printf("%s", SH_PROMPT);
if (fgets(input, SH_CMD_MAX, stdin) == NULL) {
printf("\n");
break;
}
input[strcspn(input, "\n")] = '\0';
if (strlen(input) == 0) {
printf(CMD_WARN_NO_CMD);
continue;
}
int rc = build_cmd_list(input, &clist);
if (rc == WARN_NO_CMDS) {
printf(CMD_WARN_NO_CMD);
continue;
} else if (rc == ERR_TOO_MANY_COMMANDS) {
printf(CMD_ERR_PIPE_LIMIT, CMD_MAX);
continue;
} else if (rc != OK) {
continue;
}
// printf("PARSED COMMAND LINE - TOTAL COMMANDS %d\n", clist.num);
// for (int i = 0; i < clist.num; i++) {
// printf("<%d> ", i + 1);
// cmd_buff_t *cmd = &clist.commands[i];
// for (int j = 0; j < cmd->argc; j++) {
// printf("%s ", cmd->argv[j]);
// }
// printf("\n");
// }
// uncomment for dsh> test for 3-ShellP1
if (clist.num == 1) {
cmd_buff_t *cmd = &clist.commands[0];
if (exec_built_in_cmd(cmd) != BI_NOT_BI) {
free_cmd_list(&clist);
continue;
}
exec_cmd(cmd);
} else {
int pipeline_rc = execute_pipeline(&clist);
last_return_code = pipeline_rc;
}
free_cmd_list(&clist);
clist.num = 0;
}
free_cmd_list(&clist);
return last_return_code;
}
#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;
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;
*/
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 "dsh3> "
// #define SH_PROMPT "dsh> "
// - for 3Shell-P1 (cmd, test, etc)
// The commands work but it shows unecessary "Command not in PATH" in addition
// to the correct answers.
#define EXIT_CMD "exit"
#define EXIT_SC 99
//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_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"
#endif
\ No newline at end of file
# Compiler settings
CC = gcc
CFLAGS = -Wall -Wextra -g
# Target executable name
TARGET = dsh
# Find all source and header files
SRCS = $(wildcard *.c)
HDRS = $(wildcard *.h)
# Default target
all: $(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
hello, class
5-ShellP3/starter/test-passing-screenshot.png

200 KiB

0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment