Skip to content
Snippets Groups Projects
Commit d4385069 authored by wn73's avatar wn73
Browse files

update

parent 517abd49
Branches
No related tags found
No related merge requests found
#!/usr/bin/env bats
############################ DO NOT EDIT THIS FILE #####################################
# File: assignement_tests.sh
#
# DO NOT EDIT THIS FILE
#
# Add/Edit Student tests in student_tests.sh
#
# All tests in this file must pass - it is used as part of grading!
########################################################################################
@test "Pipes" {
run "./dsh" <<EOF
ls | grep dshlib.c
EOF
# Strip all whitespace (spaces, tabs, newlines) from the output
stripped_output=$(echo "$output" | tr -d '[:space:]')
# Expected output with all whitespace removed for easier matching
expected_output="dshlib.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
# Helper function to strip shell prompt from output
strip_prompt() {
echo "$1" | sed 's/dsh3> //g'
}
@test "Example: check ls runs without errors" {
run ./dsh <<EOF
ls
EOF
# Assertions
[ "$status" -eq 0 ]
}
# Test basic pipe functionality
@test "Basic pipe: ls | grep .c" {
run ./dsh <<EOF
ls | grep .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="dsh_cli.cdshlib.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}"
echo "${expected_output}"
# Check exact match
[ "$stripped_output" = "$expected_output" ]
[ "$status" -eq 0 ]
}
@test "Multiple pipes: ls | grep .c | wc -l" {
run ./dsh <<EOF
ls | grep .c | wc -l
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="2dsh3>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}"
echo "${expected_output}"
# Check exact match
[ "$stripped_output" = "$expected_output" ]
[ "$status" -eq 0 ]
}
# Test built-in commands
@test "Built-in: exit command" {
run ./dsh <<< "exit"
[ "$status" -eq 0 ]
}
@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-testdsh3>dsh3>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 ]
}
@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="/tmpdsh3>dsh3>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 ]
}
@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/whichdsh3>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" ]
}
@test "Built-in: dragon command" {
run ./dsh <<< "dragon"
[ "$status" -eq 0 ]
stripped_output=$(strip_prompt "$output")
[[ "$stripped_output" =~ "@%%%%" ]]
}
# Test error cases
@test "Error: empty command" {
run ./dsh <<< ""
[ "$status" -eq 0 ]
[[ "$output" =~ "warning: no commands provided" ]]
}
@test "Error: too many pipes" {
run ./dsh <<< "ls | grep .c | wc -l | cat | grep 1 | cat | grep 2 | cat | grep 3"
[ "$status" -eq 0 ]
[[ "$output" =~ "error: piping limited to" ]]
}
# Test complex pipe scenarios
@test "Complex pipe: ls -l | grep .c | sort | wc -l" {
run ./dsh <<EOF
ls -l | grep .c | sort | wc -l
EOF
[ "$status" -eq 0 ]
stripped_output=$(echo "$output" | tr -d '[:space:]')
expected_output="2dsh3>dsh3>cmdloopreturned0"
echo "${stripped_output}"
echo "${expected_output}"
[ "$stripped_output" = "$expected_output" ]
}
# Test command not found
@test "Error: command not found" {
run ./dsh <<< "nonexistentcommand"
[ "$status" -eq 0 ]
[[ "$output" =~ "execvp" ]]
}
# Test command with arguments
@test "Command with arguments" {
run ./dsh <<< "ls -l | grep .c"
[ "$status" -eq 0 ]
stripped_output=$(strip_prompt "$output")
[[ "$stripped_output" =~ "dshlib.c" ]]
[[ "$stripped_output" =~ "dsh_cli.c" ]]
}
# Test output redirection
@test "Output redirection" {
# Create a temporary file for output
temp_file=$(mktemp)
# Run command with output redirection
run ./dsh <<EOF
ls -l | grep .c > $temp_file
cat $temp_file
EOF
# Clean up
rm -f $temp_file
# Check if output contains the expected files
stripped_output=$(strip_prompt "$output")
[[ "$stripped_output" =~ "dshlib.c" ]]
[[ "$stripped_output" =~ "dsh_cli.c" ]]
[ "$status" -eq 0 ]
}
# Test input redirection
@test "Input redirection" {
# Create a temporary file with test content
temp_file=$(mktemp)
echo "dshlib.c" > $temp_file
echo "dsh_cli.c" >> $temp_file
# Run command with input redirection
run ./dsh <<EOF
grep .c < $temp_file
EOF
# Clean up
rm -f $temp_file
# Check if output contains the expected files
stripped_output=$(strip_prompt "$output")
[[ "$stripped_output" =~ "dshlib.c" ]]
[[ "$stripped_output" =~ "dsh_cli.c" ]]
[ "$status" -eq 0 ]
}
# Test append redirection
@test "Append redirection" {
# Create a temporary file for output
temp_file=$(mktemp)
# First command with output redirection
run ./dsh <<EOF
ls -l | grep dshlib.c > $temp_file
EOF
# Second command with append redirection
run ./dsh <<EOF
ls -l | grep dsh_cli.c >> $temp_file
cat $temp_file
EOF
# Clean up
rm -f $temp_file
# Check if output contains both files
stripped_output=$(strip_prompt "$output")
[[ "$stripped_output" =~ "dshlib.c" ]]
[[ "$stripped_output" =~ "dsh_cli.c" ]]
[ "$status" -eq 0 ]
}
#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 <fcntl.h>
#include <sys/wait.h>
#include <errno.h>
#include "dshlib.h"
void printDragon(){
const char dragon[39][103] = {
" @%%%% \n",
" %%%%%% \n",
" %%%%%% \n",
" % %%%%%%% @ \n",
" %%%%%%%%%% %%%%%%% \n",
" %%%%%%% %%%%@ %%%%%%%%%%%%@ %%%%%% @%%%% \n",
" %%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%% \n",
" %%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%% %%%%%%%%%%%%%%% \n",
" %%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%% %%% \n",
" %%%%%%%%%%%%%%%%%%%%%%%%%%%%@ @%%%%%%%%%%%%%%%%%% %% \n",
" %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%% \n",
" %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \n",
" %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%@%%%%%%@ \n",
" %%%%%%%%@ %%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%% %% \n",
" %%%%%%%%%%%%% %%@%%%%%%%%%%%% %%%%%%%%%%% %%%%%%%%%%%% @% \n",
" %%%%%%%%%% %%% %%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%% \n",
" %%%%%%%%% % %%%%%%%%%%%%% %%%%%%%%%%%%@%%%%%%%%%%% \n",
"%%%%%%%%%@ % %%%%%%%%%%%%% @%%%%%%%%%%%%%%%%%%%%%%%%% \n",
"%%%%%%%%@ %%@%%%%%%%%%%%% @%%%%%%%%%%%%%%%%%%%%%%%%%%%% \n",
"%%%%%%%@ %%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \n",
"%%%%%%%%%% %%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%% \n",
"%%%%%%%%%@ @%%%%%%%%%%%%%% %%%%%%%%%%%%@ %%%% %%%%%%%%%%%%%%%%% %%%%%%%%\n",
"%%%%%%%%%% %%%%%%%%%%%%%%%%% %%%%%%%%%%%%% %%%%%%%%%%%%%%%%%% %%%%%%%%%\n",
"%%%%%%%%%@%%@ %%%%%%%%%%%%%%%%@ %%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%% %%\n",
" %%%%%%%%%% % %%%%%%%%%%%%%%@ %%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%% %%\n",
" %%%%%%%%%%%% @ %%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% \n",
" %%%%%%%%%%%%% %% % %@ %%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% \n",
" %%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%% @%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%% \n",
" %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% \n",
" @%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%% \n",
" %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%% %%%%%%% \n",
" %%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%% @%%%%%%%%% \n",
" %%%%%%%%%%%%%%%%%%%% @%@% @%%%%%%%%%%%%%%%%%% %%% \n",
" %%%%%%%%%%%%%%% %%%%%%%%%% %%%%%%%%%%%%%%% % \n",
" %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%% \n",
" %%%%%%%%%%%%%%%%%%%%%%%%%% %%%% %%% %%%%%%%%%% %%%@ \n",
" %%%%%%%%%%%%%%%%%%% %%%%%% %% %%%%%%%%%%%%%@ \n",
" %%%%%%%@ \n"
};
for (int i = 0; i < sizeof(dragon) / sizeof(dragon[0]); i++) {
for (int j = 0; j < sizeof(dragon[0]) / sizeof(dragon[0][0]); j++) {
if (dragon[i][j] == 0) break;
putchar((char)dragon[i][j]);
}
}
}
/*
* 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 build_cmd_list(char *cmd_line, command_list_t *clist)
{
if (strlen(cmd_line) == 0) {
printf(CMD_WARN_NO_CMD);
return OK;
}
// Initialize command list
clist->num = 0;
memset(clist->commands, 0, sizeof(clist->commands));
// Split by pipe
char *saveptr1; // For the pipe tokenization
char *saveptr2; // For the argument tokenization
char *cmd_copy = strdup(cmd_line);
if (!cmd_copy) return ERR_MEMORY;
char *cmd = strtok_r(cmd_copy, "|", &saveptr1);
while (cmd != NULL) {
// Trim leading/trailing spaces
while (*cmd == ' ') cmd++;
char *end = cmd + strlen(cmd) - 1;
while (end > cmd && *end == ' ') {
*end = '\0';
end--;
}
if (strlen(cmd) == 0) {
cmd = strtok_r(NULL, "|", &saveptr1);
continue;
}
// Check command limit
if (clist->num >= CMD_MAX) {
free(cmd_copy);
return ERR_TOO_MANY_COMMANDS;
}
// Parse command and arguments
cmd_buff_t *curr_cmd = &clist->commands[clist->num];
curr_cmd->argc = 0;
// Split command into arguments
char *arg = strtok_r(cmd, " ", &saveptr2);
while (arg != NULL) {
if (curr_cmd->argc >= CMD_ARGV_MAX) {
free(cmd_copy);
return ERR_CMD_OR_ARGS_TOO_BIG;
}
curr_cmd->argv[curr_cmd->argc++] = strdup(arg);
arg = strtok_r(NULL, " ", &saveptr2);
}
curr_cmd->argv[curr_cmd->argc] = NULL;
clist->num++;
cmd = strtok_r(NULL, "|", &saveptr1);
}
free(cmd_copy);
return OK;
}
int exec_local_cmd_loop()
{
char cmd_buff[SH_CMD_MAX];
int rc = 0;
command_list_t clist;
memset(&clist, 0, sizeof(command_list_t));
while(1) {
printf("%s", SH_PROMPT);
if (fgets(cmd_buff, SH_CMD_MAX, stdin) == NULL) {
printf("\n");
break;
}
// Remove trailing newline
cmd_buff[strcspn(cmd_buff, "\n")] = '\0';
// Skip empty commands
if (strlen(cmd_buff) == 0) {
printf(CMD_WARN_NO_CMD);
continue;
}
// Parse commands by pipe
rc = build_cmd_list(cmd_buff, &clist);
if (rc != OK) {
if (rc == ERR_TOO_MANY_COMMANDS) {
printf(CMD_ERR_PIPE_LIMIT, CMD_MAX);
}
continue;
}
// Handle built-in commands
if (clist.num == 1) {
if (strcmp(clist.commands[0].argv[0], EXIT_CMD) == 0) {
break;
} else if (strcmp(clist.commands[0].argv[0], "cd") == 0) {
if (clist.commands[0].argv[1]) {
if (chdir(clist.commands[0].argv[1]) != 0) {
perror("cd");
rc = ERR_CMD_ARGS_BAD;
}
}
continue;
} else if (strcmp(clist.commands[0].argv[0], "dragon") == 0) {
printDragon();
continue;
} else if (strcmp(clist.commands[0].argv[0], "rc") == 0) {
printf("%d\n", rc);
continue;
}
}
// Handle external commands with pipes
pid_t pids[CMD_MAX];
int pipes[CMD_MAX - 1][2];
// Create pipes
for (int i = 0; i < clist.num - 1; i++) {
if (pipe(pipes[i]) == -1) {
perror("pipe");
rc = ERR_MEMORY;
continue;
}
}
// Execute each command
for (int i = 0; i < clist.num; i++) {
pids[i] = fork();
if (pids[i] < 0) {
perror("fork");
rc = ERR_MEMORY;
break;
}
if (pids[i] == 0) { // Child process
// Set up pipe connections
if (i > 0) {
// Connect to previous pipe's read end
dup2(pipes[i-1][0], STDIN_FILENO);
}
if (i < clist.num - 1) {
// Connect to next pipe's write end
dup2(pipes[i][1], STDOUT_FILENO);
}
// Handle redirections by checking arguments
char *new_argv[CMD_ARGV_MAX];
int new_argc = 0;
for (int j = 0; j < clist.commands[i].argc; j++) {
char *arg = clist.commands[i].argv[j];
if (strcmp(arg, "<") == 0 && j + 1 < clist.commands[i].argc) {
// Input redirection
int fd = open(clist.commands[i].argv[j + 1], O_RDONLY);
if (fd == -1) {
perror("open input file");
exit(1);
}
dup2(fd, STDIN_FILENO);
close(fd);
j++; // Skip the filename
continue;
}
if (strcmp(arg, ">") == 0 && j + 1 < clist.commands[i].argc) {
// Output redirection (overwrite)
int fd = open(clist.commands[i].argv[j + 1], O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd == -1) {
perror("open output file");
exit(1);
}
dup2(fd, STDOUT_FILENO);
close(fd);
j++; // Skip the filename
continue;
}
if (strcmp(arg, ">>") == 0 && j + 1 < clist.commands[i].argc) {
// Output redirection (append)
int fd = open(clist.commands[i].argv[j + 1], O_WRONLY | O_CREAT | O_APPEND, 0644);
if (fd == -1) {
perror("open output file");
exit(1);
}
dup2(fd, STDOUT_FILENO);
close(fd);
j++; // Skip the filename
continue;
}
// If not a redirection operator, add to new argv
new_argv[new_argc++] = arg;
}
new_argv[new_argc] = NULL;
// Close all pipe ends in child
for (int j = 0; j < clist.num - 1; j++) {
close(pipes[j][0]);
close(pipes[j][1]);
}
// Execute the command with cleaned up arguments
execvp(new_argv[0], new_argv);
perror("execvp");
exit(errno);
}
}
// Parent process: close all pipe ends
for (int i = 0; i < clist.num - 1; i++) {
close(pipes[i][0]);
close(pipes[i][1]);
}
// Wait for all children to complete and get the last command's status
for (int i = 0; i < clist.num; i++) {
int status;
waitpid(pids[i], &status, 0);
if (i == clist.num - 1) { // Only use the last command's status
if (WIFEXITED(status)) {
rc = WEXITSTATUS(status);
} else {
rc = ERR_EXEC_CMD;
}
}
}
}
return OK;
}
#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 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
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?
Answer: My shell uses waitpid() in a loop to wait for each child process to finish before moving on. Basically, after forking all the processes needed for the pipe commands, the parent shell waits for each child to complete using their PIDs. This is important because:
1. It stops zombie processes from building up - without waitpid(), dead processes would stick around as zombies
2. It keeps things in order - the shell won't ask for new commands until the current ones are done
3. It cleans up properly - all process resources get freed up when they're done
If I forgot to use waitpid():
- Zombie processes would pile up and waste system resources
- The shell might ask for new commands while old ones are still running, which would be messy
- Memory and other resources wouldn't get cleaned up properly
- We wouldn't know if the commands actually worked or failed
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?
Answer: We need to close unused pipe ends after dup2() for a few key reasons:
1. System Resources: Each open pipe takes up a file descriptor. If we don't close them, we're just wasting these limited resources.
2. Avoiding Deadlocks: If we don't close read ends, processes will hang waiting for EOF since the write end is still open. Like if you do "cmd1 | cmd2" and don't close cmd1's write end, cmd2 will wait forever for more input.
3. Memory Issues: Child processes inherit open file descriptors. Without closing them:
- Each new process gets copies of all previous open descriptors
- System resources get eaten up
- You could hit system limits
4. Data Flow Problems: Leaving pipes open means:
- Programs never get their EOF signal
- Output gets stuck in buffers
- The pipeline gets clogged up
So basically, closing unused pipe ends keeps things running smoothly and prevents the program from getting stuck or running out of resources.
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?
Answer: The cd command has to be built-in because it needs to change the shell's own working directory. Because:
1. When you fork() a process, it gets its own copy of everything - including its current directory. If cd was an external command:
- It would change its own directory (the child process)
- But when it exits, the shell (parent process) would still be in the old directory
- So basically, cd wouldn't actually work!
2. Other problems that would pop up:
- Relative paths would break since the shell wouldn't know where you cd'd to
- Scripts that use cd between commands wouldn't work right
- You'd have to do weird workarounds to tell the parent shell where you went
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?
Answer: I'd switch from fixed arrays to dynamic memory allocation. Instead of:
```c
command_list_t commands[CMD_MAX];
```
I'd use:
```c
command_list_t *commands = malloc(size * sizeof(command_list_t));
```
Trade-offs:
- Good: No more fixed limits on pipes
- Bad: Slower (malloc/realloc overhead)
- Bad: Harder to code (needs to manage memory)
- Bad: Could eat up too much memory if there's way too many pipes
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment