Skip to content
Snippets Groups Projects
Commit 24981eb7 authored by dtt47's avatar dtt47
Browse files

update assignment 5

parent c2fc27bc
Branches
No related tags found
No related merge requests found
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "dshlib.h"
/*
* DO NOT EDIT
* main() simply calls exec_local_cmd_loop() from dshlib.c
*/
int main()
{
int rc = exec_local_cmd_loop();
printf("cmd loop returned %d\n", rc);
return 0;
}
#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"
/* -------------------------
parse_line (from Part 2)
Handles quotes within a single command
------------------------- */
static int parse_line(char *line, char *argv[])
{
int argc = 0;
char *p = line;
// Skip leading spaces
while (*p && isspace((unsigned char)*p))
{
p++;
}
while (*p != '\0')
{
if (argc >= CMD_ARGV_MAX - 1)
{
break;
}
if (*p == '"')
{
// Quoted argument
p++;
argv[argc++] = p;
while (*p && *p != '"')
{
p++;
}
if (*p == '"')
{
*p = '\0';
p++;
}
}
else
{
// Unquoted argument
argv[argc++] = p;
while (*p && !isspace((unsigned char)*p))
{
p++;
}
if (*p != '\0')
{
*p = '\0';
p++;
}
}
// Skip trailing spaces
while (*p && isspace((unsigned char)*p))
{
p++;
}
}
argv[argc] = NULL;
return argc;
}
/* -------------------------
build_cmd_list:
Splits the user input by '|' into multiple commands.
Then calls parse_line() for each subcommand to fill out
cmd_buff_t structures in command_list_t.
------------------------- */
int build_cmd_list(char *line, command_list_t *clist)
{
clist->num = 0;
// Trim leading/trailing spaces from the entire line
// (optional but recommended)
// ...
// If line is empty, return WARN_NO_CMDS
if (strlen(line) == 0)
{
return WARN_NO_CMDS;
}
char *saveptr;
char *subcmd = strtok_r(line, "|", &saveptr);
while (subcmd)
{
if (clist->num >= CMD_MAX)
{
return ERR_TOO_MANY_COMMANDS;
}
// Trim leading/trailing spaces from subcmd if you want
while (*subcmd && isspace((unsigned char)*subcmd))
subcmd++;
char *end = subcmd + strlen(subcmd) - 1;
while (end > subcmd && isspace((unsigned char)*end))
{
*end = '\0';
end--;
}
cmd_buff_t *cmd_b = &clist->commands[clist->num];
cmd_b->argc = 0;
memset(cmd_b->argv, 0, sizeof(cmd_b->argv));
cmd_b->_cmd_buffer = NULL; // Not strictly needed if you parse in place
// parse_line to fill argv
cmd_b->argc = parse_line(subcmd, cmd_b->argv);
clist->num++;
subcmd = strtok_r(NULL, "|", &saveptr);
}
if (clist->num == 0)
{
return WARN_NO_CMDS;
}
return OK;
}
/* -------------------------
run_single_command (from Part 2):
------------------------- */
static bool run_single_command(cmd_buff_t *cmd_b)
{
int argc = cmd_b->argc;
char **argv = cmd_b->argv;
if (argc == 0)
{
printf("%s", CMD_WARN_NO_CMD);
return false;
}
// Built-in commands
if (strcmp(argv[0], EXIT_CMD) == 0)
{
// "exit"
return true; // signal to stop shell
}
else if (strcmp(argv[0], "cd") == 0)
{
// "cd"
if (argc > 1)
{
if (chdir(argv[1]) != 0)
{
perror("cd");
}
}
return false;
}
else if (strcmp(argv[0], "dragon") == 0)
{
// Extra credit
print_dragon();
return false;
}
else
{
// External command
pid_t pid = fork();
if (pid == 0)
{
// Child
execvp(argv[0], argv);
perror("execvp");
printf(CMD_ERR_EXECUTE);
_exit(1);
}
else if (pid < 0)
{
// Fork failed
perror("fork");
}
else
{
// Parent waits
int status;
waitpid(pid, &status, 0);
}
}
return false;
}
/* -------------------------
execute_pipeline:
------------------------- */
int execute_pipeline(command_list_t *clist)
{
int n = clist->num;
if (n == 0)
{
return WARN_NO_CMDS;
}
if (n == 1)
{
// Just run single command logic
run_single_command(&clist->commands[0]);
return OK;
}
// More than 1 command -> pipeline
int pipe_count = n - 1;
int fds[2 * pipe_count];
// Create pipes
for (int i = 0; i < pipe_count; i++)
{
if (pipe(fds + i * 2) < 0)
{
perror("pipe");
return -1;
}
}
// Fork each command
pid_t pids[CMD_MAX];
for (int i = 0; i < n; i++)
{
pid_t pid = fork();
if (pid == 0)
{
// Child i
// If not first command, read from previous pipe
if (i > 0)
{
dup2(fds[(i - 1) * 2], STDIN_FILENO);
}
// If not last command, write to current pipe
if (i < n - 1)
{
dup2(fds[i * 2 + 1], STDOUT_FILENO);
}
// Close all pipe fds
for (int j = 0; j < 2 * pipe_count; j++)
{
close(fds[j]);
}
// Now exec
char **argv = clist->commands[i].argv;
if (argv[0] != NULL)
{
// Check if built-in is typed in a pipeline
// Usually, built-ins in the middle of a pipeline is tricky,
// but let's keep it simple and just do external
execvp(argv[0], argv);
perror("execvp");
_exit(1);
}
_exit(0);
}
else if (pid < 0)
{
perror("fork");
}
else
{
pids[i] = pid;
}
}
// Parent closes all pipes
for (int j = 0; j < 2 * pipe_count; j++)
{
close(fds[j]);
}
// Wait for each child
for (int i = 0; i < n; i++)
{
waitpid(pids[i], NULL, 0);
}
return OK;
}
/*
* 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(void)
{
char line[SH_CMD_MAX];
while (true)
{
// Print prompt
printf("%s", SH_PROMPT);
fflush(stdout);
// Read line
if (!fgets(line, sizeof(line), stdin))
{
// EOF
printf("\n");
break;
}
// Remove newline
line[strcspn(line, "\n")] = '\0';
command_list_t clist;
memset(&clist, 0, sizeof(clist));
// Build the list of commands (split on '|')
int rc = build_cmd_list(line, &clist);
if (rc == WARN_NO_CMDS)
{
printf("%s", CMD_WARN_NO_CMD);
continue;
}
else if (rc == ERR_TOO_MANY_COMMANDS)
{
printf(CMD_ERR_PIPE_LIMIT, CMD_MAX);
continue;
}
// If there's exactly 1 command and it's "exit", break
if (clist.num == 1)
{
cmd_buff_t *cmd_b = &clist.commands[0];
if (cmd_b->argc > 0 && strcmp(cmd_b->argv[0], EXIT_CMD) == 0)
{
printf("exiting...\n");
break;
}
}
// Otherwise, run pipeline
execute_pipeline(&clist);
}
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
# 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
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 here_ : I think my shell calls waitpid() for each child process in the pipeline. This ensures the shell doesn’t move on to the next user command until all children have finished. Without calling waitpid(), the child processes would become “zombies,” lingering in the process table and potentially causing resource leaks.
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 here_ : After using dup2() to redirect a child’s STDIN/STDOUT, any pipe file descriptors not needed by that child should be closed. If you leave these pipe ends open, the kernel won’t detect an end-of-stream (EOF) correctly, and you risk running out of file descriptors. It can also cause hangs if the reading process waits forever for a pipe to close.
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 here_ : cd changes the current working directory of the shell process itself. If it were an external command, it would run in a child process, change that process’s directory, and then exit—leaving the shell’s directory unchanged. Implementing cd as a built-in avoids that problem by calling chdir() in the shell’s own process space.
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 here_ : For now, there’s a fixed limit (CMD_MAX). To support an arbitrary number of commands, I would dynamically allocate the array (e.g., using realloc as I parse more commands). The main trade-offs are complexity (managing memory carefully) and performance (frequent resizing vs. a static array). But it lets the shell handle pipelines of any length without a hard-coded limit.
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment