This project focuses on building a custom shell implementation in C, addressing complex challenges like signal handling, job control, and pipe-based inter-process communication. The implementation demonstrates accomplishments in processing user input to handle commands, redirection, and piping. Unique highlights include a dynamic system for managing background jobs with suspended state handling and an elegant mechanism for orderly job reactivation. Through modularized functions like execute_command, this project tackles intricate challenges such as robust memory management, error handling, and multi-process coordination, showcasing significant achievements in system-level programming.
//This program was created as part of the Operating Systems coursework at New York University with project specifications provided by Professor Yang Tang.
//Developed by Travis Perry
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <pwd.h>
#include <fcntl.h>
#include <sys/types.h>
typedef struct {
pid_t pid;
char commandName[256];
int status;
int index;
} jobs;
jobs* jobList[100];
int any_suspended_jobs(jobs *jobList[]){
int i=0;
while (jobList[i]!=NULL){
if(jobList[i]->status==1){
return 1;
}
i++;
}
return 0;
}
void sigtstp_handler(int signo) {
(void) signo;
}
void execute_command(char *cmd, char *inputFile, char *outputFile, int appendTF, int input_fd, int output_fd) {
char *argList[100];
char *args = strtok(cmd, " ");
int j = 0;
while (args != NULL) {
argList[j++] = args;
args = strtok(NULL, " ");
}
argList[j] = NULL;
if (inputFile) {
int fd = open(inputFile, O_RDONLY);
if (fd < 0) {
fprintf(stderr,"Error: could not open file");
exit(1);
}
dup2(fd, STDIN_FILENO);
close(fd);
}
else if (input_fd != -1) {
dup2(input_fd, STDIN_FILENO);
}
if (outputFile) {
int fd;
if (appendTF) {
fd = open(outputFile, O_WRONLY | O_CREAT | O_APPEND, 0644);
}
else {
fd = open(outputFile, O_WRONLY | O_CREAT | O_TRUNC, 0644);
}
if (fd < 0) {
fprintf(stderr,"Error: could not open file");
exit(1);
}
dup2(fd, STDOUT_FILENO);
close(fd);
}
else if (output_fd != -1) {
dup2(output_fd, STDOUT_FILENO);
}
execvp(argList[0], argList);
fprintf(stderr, "Error: exec failed");
exit(1);
}
int main(void) {
signal(SIGTSTP, sigtstp_handler);
int numJobs=0;
while (1) {
// Milestone 1
char cwd[256];
char prompt[256];
getcwd(cwd, sizeof(cwd));
size_t i = strlen(cwd) - 1;
if (i == 0) {
prompt[0] = '/';
} else {
while (i > 0) {
if (cwd[i] == '/') {
break;
}
i--;
}
}
i++;
int arrIndex = 0;
for (; i < strlen(cwd); i++) {
prompt[arrIndex] = cwd[i];
arrIndex++;
}
prompt[arrIndex] = '\0';
if (strcmp(cwd, "/") == 0) {
printf("[nyush /]$ ");
} else {
printf("[nyush %s]$ ", prompt);
}
fflush(stdout);
// Milestone 2:Get user input
signal(SIGINT, SIG_IGN);
signal(SIGQUIT, SIG_IGN);
signal(SIGTSTP, SIG_IGN);
char *buffer;
size_t maxSize = 1000;
buffer = malloc(maxSize * sizeof(char));
ssize_t command = getline(&buffer, &maxSize, stdin);
if (command == -1) {
free(buffer);
exit(0);
}
char storedBuffer[1000];
strcpy(storedBuffer,buffer);
buffer[strlen(buffer) - 1] = '\0';
int numPipes=0;
int numArgs=1;
for (size_t i=0; i") == 0) {
args = strtok(NULL, " ");
if (args != NULL) {
outputFile = args;
}
} else if (strcmp(args, "<") == 0) {
args = strtok(NULL, " ");
if (args != NULL) {
inputFile = args;
}
} else {
argList[j++] = args;
}
args = strtok(NULL, " ");
}
argList[j] = NULL;
jobList[numJobs]=malloc(sizeof(jobs)+1);
jobList[numJobs]->pid=getpid();
strcpy(jobList[numJobs]->commandName,storedBuffer);
jobList[numJobs]->status=0;
jobList[numJobs]->index=0;
numJobs++;
if (strlen(buffer)==0){
continue;
}
else if (strcmp(argList[0],"cd")==0){
if (numArgs != 2) {
fprintf(stderr,"Error: invalid command\n");
}
else {
if (strcmp(argList[1],prompt)==0){
fprintf(stderr,"Error: invalid directory\n");
}
else {
chdir(argList[1]);
}
}
}
else if (strcmp(buffer,"exit")==0){
if (numArgs>1){
fprintf(stderr,"Error: invalid command\n");
}
else{
//Milestone 10
if(any_suspended_jobs(jobList)){
fprintf(stderr,"Error: there are suspended jobs\n");
fflush(stderr);
}
else {
exit(0);
}
}
}
else if (strcmp(argList[0],"jobs")==0){
if (numArgs>1){
fprintf(stderr,"Error: invalid command\n");
continue;
}
if (!any_suspended_jobs(jobList)){
printf("\n");
continue;
}
jobs *suspendedJobs[100]; // Assuming max 100 suspended jobs
int numSuspended = 0;
int i=0;
while(jobList[i]!=NULL){
if (jobList[i]->status==1){
suspendedJobs[numSuspended] = jobList[i];
numSuspended++;
}
i++;
}
for (int j = 0; j < numSuspended - 1; j++) {
for (int k = j + 1; k < numSuspended; k++) {
if (suspendedJobs[j]->index > suspendedJobs[k]->index) {
jobs *temp = suspendedJobs[j];
suspendedJobs[j] = suspendedJobs[k];
suspendedJobs[k] = temp;
}
}
}
for (int j = 0; j < numSuspended; j++) {
printf("[%d] %s", suspendedJobs[j]->index, suspendedJobs[j]->commandName);
}
}
else if (strcmp(argList[0], "fg") == 0) {
if (numArgs != 2) {
fprintf(stderr, "Error: invalid command\n");
continue;
}
else if (!any_suspended_jobs(jobList)){
fprintf(stderr,"Error: invalid job\n");
continue;
}
int i=0;
int jobIndex=0;
while(jobList[i]!=NULL){
if (jobList[i]->index==atoi(argList[1])){
jobIndex=i;
break;
}
i++;
}
pid_t pid = jobList[jobIndex]->pid;
kill(pid, SIGCONT);
jobList[jobIndex]->status = 0;
i=0;
int k=0;
while(jobList[k]!=NULL){
if (jobList[k]->index > jobList[jobIndex]->index) {
jobList[k]->index--;
}
k++;
}
jobList[jobIndex]->index = 0;
int status;
waitpid(pid, &status, WUNTRACED);
if (WIFSTOPPED(status)) {
jobList[jobIndex]->status=1;
fflush(stdout);
int maxIndex=0;
int i=0;
while (jobList[i]!=NULL){
if(jobList[i]->index>maxIndex){
maxIndex=jobList[i]->index;
}
i++;
}
jobList[jobIndex]->index=maxIndex+1;
}
}
else {
int pid = fork();
if (pid < 0) {
fprintf(stderr, "Error: Child process creation failure\n");
}
//Milestones 6&7
else if (pid == 0) {
signal(SIGINT, SIG_DFL);
signal(SIGQUIT, SIG_DFL);
signal(SIGTSTP, SIG_DFL);
if (outputFile) {
int fd;
if (appendTF) {
fd = open(outputFile, O_WRONLY | O_CREAT | O_APPEND, 0644);
} else {
fd = open(outputFile, O_WRONLY | O_CREAT | O_TRUNC, 0644);
}
if (fd < 0) {
fprintf(stderr,"Error: invalid file\n");
exit(1);
}
if (dup2(fd, STDOUT_FILENO) < 0) {
fprintf(stderr,"Error: invalid file\n");
close(fd);
exit(1);
}
close(fd);
}
if (inputFile){
int fd = open(inputFile, O_RDONLY);
if (fd < 0) {
fprintf(stderr,"Error: invalid file\n");
exit(1);
}
dup2(fd, STDIN_FILENO);
close(fd);
}
//Milestone 8
if (strcmp(argList[0],"fsck")==0){
fprintf(stderr,"Error: invalid program\n");
exit(1);
}
execvp(argList[0], argList);
fprintf(stderr,"Error: invalid program\n");
fflush(stderr);
exit(1);
}
else {
jobList[numJobs]=malloc(sizeof(jobs));
jobList[numJobs]->pid=pid;
strcpy(jobList[numJobs]->commandName,storedBuffer);
numJobs++;
int status;
waitpid(pid, &status, WUNTRACED);
if (WIFSTOPPED(status)) {
int maxIndex=0;
int i=0;
int suspendedJob=0;
while (jobList[i]!=NULL){
if (pid==jobList[i]->pid){
jobList[i]->status=1;
suspendedJob=i;
}
if(jobList[i]->index>maxIndex){
maxIndex=jobList[i]->index;
}
i++;
}
jobList[suspendedJob]->index=maxIndex+1;
}
}
}
}
else {
char *inputFile = NULL, *outputFile = NULL;
int appendTF = 0;
char *commands[100];
int numPipes = 0;
char *command = strtok(buffer, "|");
while (command != NULL) {
commands[numPipes++] = command;
command = strtok(NULL, "|");
}
int pipes[numPipes - 1][2];
for (int i = 0; i < numPipes - 1; i++) {
if(pipe(pipes[i])==-1){
fprintf(stderr,"Error: invalid command\n");
exit(1);
};
}
for (int i = 0; i < numPipes; i++) {
pid_t pid = fork();
if (pid < 0) {
fprintf(stderr, "Error: fork failed");
exit(1);
}
if (pid == 0) {
char *redir_cmd = strtok(commands[i], ">");
if (redir_cmd != NULL && i == numPipes - 1) {
char *output_part = strtok(NULL, " ");
if (output_part != NULL) {
outputFile = output_part;
}
}
redir_cmd = strtok(commands[i], "<");
if (redir_cmd != NULL && i == 0) {
char *input_part = strtok(NULL, " ");
if (input_part != NULL) {
inputFile = input_part;
}
}
if (i > 0) {
dup2(pipes[i - 1][0], STDIN_FILENO); // Read from previous pipe
}
if (i < numPipes - 1) {
dup2(pipes[i][1], STDOUT_FILENO); // Write to next pipe
}
for (int j = 0; j < numPipes - 1; j++) {
close(pipes[j][0]);
close(pipes[j][1]);
}
execute_command(commands[i], inputFile, outputFile, appendTF, -1, -1);
}
}
for (int i = 0; i < numPipes - 1; i++) {
close(pipes[i][0]);
close(pipes[i][1]);
}
for (int i = 0; i < numPipes; i++) {
wait(NULL);
}
}
}
return 1;
}