assignment 1
This commit is contained in:
300
cmd.c
Normal file
300
cmd.c
Normal file
@@ -0,0 +1,300 @@
|
||||
#include "cmd.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
#include "scanner.h"
|
||||
|
||||
/**
|
||||
* This function instantiates an empty command.
|
||||
*/
|
||||
Command _newCommand() {
|
||||
Command cmd;
|
||||
cmd.capacity = INITIAL_ARRAY_SIZE;
|
||||
cmd.arguments = calloc(INITIAL_ARRAY_SIZE, sizeof(char**));
|
||||
cmd.numArguments = 0;
|
||||
return cmd;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function instantiates an empty commandlist.
|
||||
*/
|
||||
CommandList _newCommandList() {
|
||||
CommandList list;
|
||||
list.capacity = INITIAL_ARRAY_SIZE;
|
||||
list.commands = calloc(INITIAL_ARRAY_SIZE, sizeof(Command));
|
||||
list.numCommands = 0;
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function instantiates an empty chain.
|
||||
*/
|
||||
Chain _newChain() {
|
||||
Chain chain;
|
||||
chain.commands = _newCommandList();
|
||||
chain.runInBackground = false;
|
||||
return chain;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function frees a command.
|
||||
* Note that it only frees the char**, not the strings/char*s themselves, as these are part of the TokenList.
|
||||
*/
|
||||
void freeCommand(Command cmd) {
|
||||
free(cmd.arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function frees a commandlist.
|
||||
* To do so, it frees all commands in .numCommands
|
||||
*/
|
||||
void _freeCommandList(CommandList list) {
|
||||
for (int i=0; i < list.numCommands; i++) {
|
||||
freeCommand(list.commands[i]);
|
||||
}
|
||||
free(list.commands);
|
||||
}
|
||||
|
||||
// This function frees a chain.
|
||||
void freeChain(Chain chain) {
|
||||
_freeCommandList(chain.commands);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function inserts a given argument into a given command.
|
||||
* @param cmd the command to insert the argument into.
|
||||
* @param str the argument to be inserted.
|
||||
*/
|
||||
void insertArgument_(Command *cmd, char* str) {
|
||||
//resize arguments pointer if necessary.
|
||||
if (cmd->numArguments >= cmd->capacity) {
|
||||
cmd->arguments = realloc(cmd->arguments, 2*cmd->capacity*sizeof(Command));
|
||||
cmd->capacity *= 2;
|
||||
}
|
||||
cmd->arguments[cmd->numArguments++] = str;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function builds a command from a given listPointer.
|
||||
* It expects the current listPointer to be either of type EXECUTABLE of BUILTIN,
|
||||
* followed by any number of OPTIONs.
|
||||
* @param lp the list to read from.
|
||||
* @return a new Command.
|
||||
*/
|
||||
Command buildCommand(List *lp) {
|
||||
Command cmd = _newCommand();
|
||||
|
||||
//exit on invalid syntax. This should not be reachable, if our parser is correct.
|
||||
if ((*lp)->type != EXECUTABLE && (*lp)->type != BUILTIN) exit(1);
|
||||
|
||||
// insert command as first entry
|
||||
insertArgument_(&cmd, (*lp)->t);
|
||||
*lp = (*lp)->next;
|
||||
|
||||
//insert rest of arguments
|
||||
while (*lp != NULL && (*lp)->type == OPTION) {
|
||||
insertArgument_(&cmd, (*lp)->t);
|
||||
*lp = (*lp)->next;
|
||||
}
|
||||
|
||||
// execvp() expects a NULL terminated list of options.
|
||||
insertArgument_(&cmd, NULL);
|
||||
|
||||
//for ease of access we define the command attribute as pointing towards the first arguments entry (which contains the command to be executed)
|
||||
cmd.command = cmd.arguments[0];
|
||||
return cmd;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function inserts a command into a given commandList.
|
||||
* @param cmd the Command to insert.
|
||||
* @param List the commandList to insert the command into.
|
||||
*/
|
||||
void _insertCommand(Command cmd, CommandList *list) {
|
||||
//resize commands pointer if necessary.
|
||||
if (list->numCommands >= list->capacity) {
|
||||
list->commands = realloc(list->commands, 2*list->capacity*sizeof(Command));
|
||||
list->capacity *= 2;
|
||||
}
|
||||
list->commands[list->numCommands++] = cmd;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function build up one chain of commands from a given listPointer.
|
||||
* It parses one 'unit' of executables, meaning any number of commands+options chained together by pipes and redirects.
|
||||
* It closely models the given grammar for a chain, witth the exception of also parsing the background operator '&'. (and it doesn't like builtins either, we handle those seperately).
|
||||
* @param lp the list to construct a chain from.
|
||||
* @return a new Chain.
|
||||
*/
|
||||
Chain buildChain(List *lp) {
|
||||
Chain chain = _newChain();
|
||||
Command cmd;
|
||||
|
||||
//exit on invalid syntax. This should not be reachable, if our parser is correct.
|
||||
if ((*lp) == NULL || (*lp)->type != EXECUTABLE) exit(1);
|
||||
|
||||
// so long as we have tokens to parse, do so. We exit from within the loop if:
|
||||
// we encounter a syntax error (OPTION, BUILTIN, or FILENAME - these should be consumed as we construct the Chain) or
|
||||
// we encounter a COMPOSITION operator - meaning this chain is complete, and the remainder of lp is to be processed elsewhere.
|
||||
while ((*lp) != NULL) {
|
||||
switch ((*lp)->type) {
|
||||
// finding a command means we must insert it into the commandList.
|
||||
case EXECUTABLE:
|
||||
cmd = buildCommand(lp);
|
||||
_insertCommand(cmd, &chain.commands);
|
||||
break;
|
||||
|
||||
//finding a chain operator means we want to continue evaluating, so we consume the operator.
|
||||
case PIPELINE:
|
||||
*lp = (*lp)->next;
|
||||
break;
|
||||
|
||||
// finding a redirect means a bunch of things we haven't thought out yet.
|
||||
case REDIRECT:
|
||||
//TODO: implement redirect
|
||||
break;
|
||||
|
||||
// finding a background operator means two things:
|
||||
// 1. we must run this chain in the background.
|
||||
// 2. we have reached the end of this chain.
|
||||
case BACKGROUND:
|
||||
chain.runInBackground = true;
|
||||
*lp = (*lp)->next;
|
||||
|
||||
// finding a COMPOSITION operator indicates we reached the end of this chain.
|
||||
case COMPOSITION:
|
||||
return chain;
|
||||
|
||||
// finding any of an OPTION, BUILTIN, or FILENAME means a syntax error (and thus a faulty parser)- these should not be reachable.
|
||||
case OPTION:
|
||||
case BUILTIN:
|
||||
case FILENAME:
|
||||
freeChain(chain);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
return chain;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function executes a given command in a child process.
|
||||
* To make this a blocking operation, the parent process will wait until the child exits - simultaneously updating the status variable.
|
||||
* @param cmd the Command to execute.
|
||||
* @param status variable which will contain the exit status of the command.
|
||||
*/
|
||||
void _executeCommand(Command cmd, int* status) {
|
||||
pid_t pid = fork();
|
||||
|
||||
switch (pid) {
|
||||
// fork failed.
|
||||
case -1:
|
||||
printf("ERROR: failed to create child!\n");
|
||||
*status = -1;
|
||||
break;
|
||||
case 0:
|
||||
// child process
|
||||
execvp(cmd.command, cmd.arguments);
|
||||
//this line is only ever reached if execvp fails (for example, when an executable can't be found).
|
||||
exit(127);
|
||||
break;
|
||||
default:;
|
||||
// parent process; wait for child and update status.
|
||||
int stat;
|
||||
wait(&stat);
|
||||
if (WIFEXITED(stat)) {
|
||||
*status = WEXITSTATUS(stat);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: extend to include I/O redirection and background operation
|
||||
/**
|
||||
* This function executes all commands in a given chain.
|
||||
* It updates the status as it does so.
|
||||
* This function is barebones at the moment, but will be expanded to include support for redirection and non-blocking execution.
|
||||
* @param chain the Chain to execute.
|
||||
* @param status variable which will contain the exit status after execution.
|
||||
*/
|
||||
void executeChain(Chain chain, int* status) {
|
||||
for (int i=0; i < chain.commands.numCommands; i++) {
|
||||
_executeCommand(chain.commands.commands[i], status);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function executes a BUILTIN.
|
||||
* BUILTINs are pre-defined commands ran within the shell itself, as opposed to executables found in $PATH or $CWD.
|
||||
* @param cmd the BUILTIN to execute.
|
||||
* @param status the return status of the last run command.
|
||||
* @return a boolean which indicates whether to keep running the shell or not.
|
||||
*/
|
||||
#if EXT_PROMPT
|
||||
bool executeBuiltin(Command cmd, int *status, bool *debug) {
|
||||
#else
|
||||
bool executeBuiltin(Command cmd, int *status) {
|
||||
#endif
|
||||
if (!strcmp(cmd.command, "status")) {
|
||||
printf("The most recent exit code is: %d.\n", *status);
|
||||
} else if (!strcmp(cmd.command, "exit")) {
|
||||
return false;
|
||||
} else if (!strcmp(cmd.command, "true")) {
|
||||
*status = 0;
|
||||
} else if (!strcmp(cmd.command, "false")) {
|
||||
*status = 1;
|
||||
#if EXT_PROMPT
|
||||
} else if (!strcmp(cmd.command, "debug")) {
|
||||
*debug = ! *debug;
|
||||
printf("Toggled debug to %d.\n", *debug);
|
||||
} else if (!strcmp(cmd.command, "cd")) {
|
||||
if (cmd.numArguments == 2) {
|
||||
char *PWD = getenv("HOME");
|
||||
*status = chdir(PWD);
|
||||
} else {
|
||||
*status = chdir(cmd.arguments[1]);
|
||||
}
|
||||
#endif
|
||||
}// can be expanded by growing the if/else chain.
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function prints one Command in json.
|
||||
* Intended to be used in tandem with printChain and printCommandList.
|
||||
* @param cmd the command to print.
|
||||
*/
|
||||
void _printCommand(Command cmd) {
|
||||
printf("{\n \"arguments\": [\n");
|
||||
for (int i=0; i < cmd.numArguments; i++) {
|
||||
printf(" \"%s\"", cmd.arguments[i]);
|
||||
if (i < cmd.numArguments-1) printf(",\n");
|
||||
}
|
||||
printf("\n ],\n \"command\": \"%s\",\n \"capacity\": %d,\n \"numArguments\": %d\n }", cmd.command, cmd.capacity, cmd.numArguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function prints a commandList in json.
|
||||
* Intended to be used with printCommand and printChain.
|
||||
* @param list the list to print.
|
||||
*/
|
||||
void _printCommandList(CommandList list) {
|
||||
printf("{\n \"commands\": [\n ");
|
||||
for (int i=0; i < list.numCommands; i++) {
|
||||
_printCommand(list.commands[i]);
|
||||
if (i < list.numCommands-1) printf(", ");
|
||||
}
|
||||
printf("],\n \"capacity\": %d,\n \"numCommands\": %d\n }", list.capacity, list.numCommands);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function prints a Chain in json.
|
||||
* Intended to be used with printCommand and printCommandList.
|
||||
* @param chain the chain to print.
|
||||
*/
|
||||
void printChain(Chain chain) {
|
||||
printf("{\n\"commandList\": ");
|
||||
_printCommandList(chain.commands);
|
||||
printf(",\n\"runInBackground\": %d\n}\n", chain.runInBackground);
|
||||
}
|
||||
Reference in New Issue
Block a user