opSys-shell/cmd.c

387 lines
11 KiB
C

#include "cmd.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <sys/syscall.h>
#include <fcntl.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;
chain.in = NULL;
chain.out = NULL;
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;
}
void addRedirect(List *lp, Chain *chain) {
// exit on invalid syntax; should be unreachable.
if ((*lp)->type != REDIRECT || (*lp)->next->type != FILENAME) exit(1);
if ((*lp)->t[0] == '>' ) {
// out redirect
chain->out = (*lp)->next->t;
} else {
// in redirect
chain->in = (*lp)->next->t;
}
*lp = (*lp)->next;
*lp = (*lp)->next;
}
/**
* 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 we need to take this into account during chain execution;
// add the redirect to the chain struct, and we can deal with it down the line.
case REDIRECT:
addRedirect(lp, &chain);
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 all commands in a given chain.
* It updates the status as it does so.
* The function uses the variables chain.in and chain.out for redirection, if necessary.
* Additionally it supports non-blocking execution through the chain,runInBackground attribute.
* @param chain the Chain to execute.
* @param status variable which will contain the exit status after execution.
*/
void executeChain(Chain chain, int* status) {
// variables for clarity
int numCommands = chain.commands.numCommands;
int numPipes = chain.commands.numCommands-1;
pid_t pids[numCommands];
Command cmd;
// error if input and output are identical files.
if (chain.in != NULL && chain.out != NULL && !strcmp(chain.in, chain.out)) {
printf("Error: input and output files cannot be equal!\n");
*status = 2;
return;
}
// pipe creation
int *pipes = calloc(numPipes*2, sizeof(int));
for (int i=0; i < numPipes; i++) {
if (pipe(pipes + i*2) < 0) {
perror("ERROR: failed to create pipes!");
return;
}
}
// command execution
for (int i=0; i < numCommands; i++) {
cmd = chain.commands.commands[i];
pids[i] = fork();
switch (pids[i]) {
case -1:
printf("ERROR: failed to create child!\n");
*status =-1;
break;
case 0:
// child process
// if not first command, connect input to previous output
if (i > 0) {
if (dup2(pipes[(i-1)*2], 0) < 0) {
perror("ERROR: failed to connect pipes!");
return;
}
} else if (chain.in != NULL) {
// in redirection
int fd0 = open(chain.in, O_RDONLY, 0);
if (dup2(fd0, 0) < 0) {
perror("ERROR: failed to redirect input!");
return;
}
close(fd0);
}
// if not last command, connect output to next input
if (i+1 < numCommands) {
if (dup2(pipes[i*2+1], 1) < 0) {
perror("ERROR: failed to connect pipes!");
return;
}
} else if (chain.out != NULL) {
// output redirection
int fd1 = open(chain.out, O_WRONLY|O_CREAT, 0755);
if (dup2(fd1, 1) < 0) {
perror("ERROR: failed to redirect output!");
return;
}
close(fd1);
}
// close all pipes
for (int i = 0; i < numPipes; i++) {
close(pipes[i*2]);
close(pipes[i*2+1]);
}
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
break;
}
}
// close pipes
for (int i = 0; i < numPipes; i++) {
close(pipes[i*2]);
close(pipes[i*2+1]);
}
// wait on children
int stat;
#if EXT_PROMPT
if (chain.runInBackground) return;
#endif
for (int i=0; i < numCommands; i++) {
waitpid(pids[i], &stat, 0);
if (WIFEXITED(stat)) {
*status = WEXITSTATUS(stat);
}
}
}
/**
* 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, "cd")) {
if (cmd.numArguments == 2) {
printf("Error: cd requires folder to navigate to!\n");
*status = 2;
} else {
*status = chdir(cmd.arguments[1]);
if (*status) {
printf("Error: cd directory not found!\n");
*status = 2;
}
}
}
#if EXT_PROMPT
else if (!strcmp(cmd.command, "debug")) {
*debug = ! *debug;
printf("Toggled debug to %d.\n", *debug);
}
#endif
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);
}