feat: implemented pipes and redirects + background operator
This commit is contained in:
186
cmd.c
186
cmd.c
@@ -3,6 +3,8 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include "scanner.h"
|
||||
|
||||
@@ -35,6 +37,8 @@ Chain _newChain() {
|
||||
Chain chain;
|
||||
chain.commands = _newCommandList();
|
||||
chain.runInBackground = false;
|
||||
chain.in = NULL;
|
||||
chain.out = NULL;
|
||||
return chain;
|
||||
}
|
||||
|
||||
@@ -122,6 +126,21 @@ void _insertCommand(Command cmd, CommandList *list) {
|
||||
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.
|
||||
@@ -152,10 +171,11 @@ Chain buildChain(List *lp) {
|
||||
*lp = (*lp)->next;
|
||||
break;
|
||||
|
||||
// finding a redirect means a bunch of things we haven't thought out yet.
|
||||
// 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:
|
||||
//TODO: implement redirect
|
||||
break;
|
||||
addRedirect(lp, &chain);
|
||||
break;
|
||||
|
||||
// finding a background operator means two things:
|
||||
// 1. we must run this chain in the background.
|
||||
@@ -179,48 +199,113 @@ Chain buildChain(List *lp) {
|
||||
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.
|
||||
* 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) {
|
||||
for (int i=0; i < chain.commands.numCommands; i++) {
|
||||
_executeCommand(chain.commands.commands[i], 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,26 +322,27 @@ bool executeBuiltin(Command cmd, int *status, bool *debug) {
|
||||
bool executeBuiltin(Command cmd, int *status) {
|
||||
#endif
|
||||
if (!strcmp(cmd.command, "status")) {
|
||||
printf("The most recent exit code is: %d.\n", *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);
|
||||
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
|
||||
}// can be expanded by growing the if/else chain.
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user