/* ********************************************************************** */ /* * * */ /* * myshell.c * */ /* * * */ /* ********************************************************************** */ /* * * */ /* * Homework #2 - Problem #1 * */ /* * * */ /* * This program is designed to demonstrate the basic fork/exec * */ /* * method for running an external program along with using "dup2" * */ /* * to show input and output redirection. * */ /* * * */ /* ********************************************************************** */ /* * * */ /* * 07/05/2010 - Program Created * */ /* * * */ /* ********************************************************************** */ #include #include #include #include #include #include #include #include #include #include #include extern int errno; /* Global error variable */ int debug = 0; #define TRUE 1 #define FALSE 0 #define LEFT 0 #define RIGHT 1 #define MIDDLE 2 #define NONE 3 #define MAX_ARGS 128 #define MAX_CMD 64 mode_t mode = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IROTH; char **pipes; /* command broken down by pipes into an array */ int num_pipes; /* number of elements in pipe array */ int bkg = FALSE; /* Background flag */ int cmd_status = 0; /* Exit status from the last command */ /* ********************************************************************** */ /* * * */ /* * strTrimLeft: This function is designed to trim any leading * */ /* * spaces from a string. * */ /* * * */ /* ********************************************************************** */ int strTrimLeft(char *str) { int i = 0; /* Loop variable */ /* If first char is a space, then move every char one to the left */ while (strlen(str) > 0 && str[0] == ' ') { for (i = 1; i <= strlen(str); i++) str[i-1] = str[i]; str[strlen(str)] = '\0'; } return(TRUE); } /* ********************************************************************** */ /* * * */ /* * strTrimRight: This function is designed to trim any trailing * */ /* * spaces from a string. * */ /* * * */ /* ********************************************************************** */ int strTrimRight(char *str) { int len = strlen(str); /* String length */ while ((--len) > 0 && str[len] == ' ') { str[len] = '\0'; } return(TRUE); } /* ********************************************************************** */ /* * * */ /* * strTrim: This function is designed to trim any leading and * */ /* * trailing spaces from a string. * */ /* * * */ /* ********************************************************************** */ int strTrim(char *str) { strTrimLeft(str); strTrimRight(str); return(TRUE); } /* ********************************************************************** */ /* * * */ /* * sig_child: This function will wait for a terminated child and * */ /* * set the global status variable. Handles the SIGCHLD signal. * */ /* * * */ /* ********************************************************************** */ void sig_child(int signo) { int status; if (waitpid(0, &status, 0) < 0) { perror("waitpid"); } cmd_status = WEXITSTATUS(status); } /* ********************************************************************** */ /* * * */ /* * sig_alrm: Used in mysleep. * */ /* * * */ /* ********************************************************************** */ void sig_alrm(int signo) { return; } /* ********************************************************************** */ /* * * */ /* * mysleep: Implement sleep function. * */ /* * * */ /* ********************************************************************** */ unsigned int mysleep(unsigned int nsecs) { struct sigaction newact, oldact; sigset_t newmask, oldmask, suspmask; unsigned int unslept; /* setup SIGALRM handler */ newact.sa_handler = sig_alrm; sigemptyset(&newact.sa_mask); newact.sa_flags = 0; sigaction(SIGALRM, &newact, &oldact); /* Block SIGALRM */ sigemptyset(&newmask); sigaddset(&newmask, SIGALRM); sigprocmask(SIG_BLOCK, &newmask, &oldmask); alarm(nsecs); /* Make sure SIGALRM is unblocked and suspend */ suspmask = oldmask; sigdelset(&suspmask, SIGALRM); sigsuspend(&suspmask); /* We caught signal, SIGALRM is now blocked */ unslept = alarm(0); /* Reset things and return */ sigaction(SIGALRM, &oldact, NULL); sigprocmask(SIG_SETMASK, &oldmask, NULL); return(unslept); } /* ********************************************************************** */ /* * * */ /* * split_str: This function is designed to take a string and break * */ /* * it into an array based on a delimiter. * */ /* * * */ /* ********************************************************************** */ char **split_str(char *command, char *delim) { char *ptr; /* Temporary pointer */ char *buf; /* Copy of user command */ char **argv; int argc = 0; /* Allocate the argv array */ argv = (char **) malloc(sizeof(char *) * MAX_ARGS); /* First token is the command name */ buf = (char *) malloc(strlen(command) + 1); strcpy(buf, command); ptr = strtok(buf, delim); argv[argc] = (char *) malloc(strlen(ptr) + 1); strcpy(argv[argc], ptr); strTrim(argv[argc]); if (debug) fprintf(stderr, "split_str: %s\n", argv[argc]); argc++; /* Loop for remaining tokens */ while ((ptr = strtok(NULL, delim)) != NULL) { argv[argc] = (char *) malloc(strlen(ptr) + 1); strcpy(argv[argc], ptr); strTrim(argv[argc]); if (debug) fprintf(stderr, "split_str: %s\n", argv[argc]); argc++; if (argc > MAX_ARGS) { fprintf(stderr, "MAX_ARGS exceeded\n"); exit(1); } } argv[argc] = NULL; free(buf); return(argv); } /* ********************************************************************** */ /* * * */ /* * count_array: This function is designed to count the elements in * */ /* * an array. * */ /* * * */ /* ********************************************************************** */ int count_array(char **a) { int argc = 0; while (a[argc++] != NULL); return(argc-1); } /* ********************************************************************** */ /* * * */ /* * run_command: This function is designed to take a parsed command * */ /* * and execute it in the same manner as the shell would. * */ /* * * */ /* ********************************************************************** */ int run_command(char *buf) { int i; int fd[2]; /* pipe */ pid_t pid; /* Temporary pids */ sigset_t block; /* Signal set to block SIGCHLD */ sigset_t empty; /* Empty signal set */ if (debug) fprintf(stderr, "run_command: (%s) (bkg = %d)\n", buf, bkg); /* Prevent a race to sigsuspend by blocking SIGCHLD until sigsuspend */ sigemptyset(&empty); sigemptyset(&block); sigaddset(&block, SIGCHLD); if (sigprocmask(SIG_BLOCK, &block, NULL) < 0) { perror("sigprocmask"); exit(1); } /* Create a new process to execute command */ pid = fork(); if (pid > 0) /* parent */ { if (!bkg) sigsuspend(&empty); } else if (pid < 0) /* error condition */ { perror("fork"); exit(1); } else if (pid == 0) { /* Break down the pipelines */ pipes = split_str(buf, "|"); num_pipes = count_array(pipes); if (num_pipes > 2) { fprintf(stderr, "only 1 pipe supported in this version\n"); exit(1); } if (debug) { fprintf(stderr, "num_pipes = %d\n", num_pipes); for (i = 0; i < num_pipes; i++) fprintf(stderr, "pipes = %s\n", pipes[i]); } if (num_pipes == 1) { /* There is no pipe, so just run the command */ run_child(NONE, 0, 0, 1); } else { pipe(fd); if ((pid = fork()) < 0) { perror("fork"); exit(1); } else if (pid == 0) run_child(LEFT, 0, fd, num_pipes-1); else run_child(RIGHT, fd, 0, num_pipes); } } return 0; } /* ********************************************************************** */ /* * * */ /* * run_child: This function is designed to take a portion of a * */ /* * pipeline, parse it, and run it. * */ /* * * */ /* ********************************************************************** */ int run_child(int pos, int lp[2], int rp[2], int cmdnum) { int i; /* Loop variable */ int ifd, ofd; /* Filedes for input & output */ char **argv; /* Temporary command array */ int argc = 0; /* Num of elements in command array */ int append; /* Do we have >> in command */ char infile[256]; /* Input filename */ char outfile[256]; /* Output filename */ strcpy(infile, ""); strcpy(outfile, ""); append = 0; /* Parse the pieces */ if (debug) { fprintf(stderr, "----------------------------------------\n"); fprintf(stderr, "run_child: %s %d %d\n\n", pipes[cmdnum-1], getpid(), getppid()); } argv = split_str(pipes[cmdnum-1], " "); argc = count_array(argv); while ((argc > 2) && (argv[argc-2][0] == '>' || argv[argc-2][0] == '<')) { if (debug) { fprintf(stderr, "check redir: %s\n", argv[argc-2]); } if (argv[argc-2][0] == '>') { strcpy(outfile, argv[argc-1]); if (argv[argc-2][1] == '>') append = 1; argc -= 2; continue; } if (argv[argc-2][0] == '<') { strcpy(infile, argv[argc-1]); argc -= 2; continue; } } if (debug) { for (i = 0; i < argc; i ++) fprintf(stderr, "argv[%d] = %s\n", i, argv[i]); fprintf(stderr, "infile = %s\n", infile); fprintf(stderr, "outfile = %s\n", outfile); fprintf(stderr, "append = %d\n", append); fprintf(stderr, "----------------------------------------\n"); } /* Handle the right pipe */ if (pos == LEFT) { close(rp[0]); dup2(rp[1], 1); close(rp[0]); } /* Handle the left pipe */ if (pos == RIGHT) { close(lp[1]); dup2(lp[0], 0); close(lp[1]); } /* Input redirection */ if (strcmp(infile, "") != 0) { if (pos == RIGHT) { fprintf(stderr, "Input redir not allowed here\n"); exit(1); } if ((ifd = open(infile, O_RDONLY)) < 0) { perror("open"); exit(1); } if (dup2(ifd, 0) < 0) { perror("dup2 input"); exit(1); } close(ifd); } /* Output redirection */ if (strcmp(outfile, "") != 0) { if (pos == LEFT) { fprintf(stderr, "Output redir not allowed here\n"); exit(1); } if (append) { if ((ofd = open(outfile, O_WRONLY|O_CREAT|O_APPEND, mode)) < 0) { perror("open"); exit(1); } } else { if ((ofd = open(outfile, O_WRONLY|O_CREAT|O_TRUNC, mode)) < 0) { perror("open"); exit(1); } } if (dup2(ofd, 1) < 0) { perror("dup2 output"); exit(1); } close(ofd); } argv[argc] = NULL; /* Run our command */ if (execvp(argv[0], argv) < 0) { perror("execvp"); exit(1); } } /* ********************************************************************** */ /* * * */ /* * main: This function is designed to perform basic program setup. * */ /* * * */ /* ********************************************************************** */ int main(int argc, char **argv) { int i, n; /* Loop variable */ int done = FALSE; /* Should we exit yet? */ struct sigaction act; /* Signal structure */ char buf[512]; /* Buffer to hold user input */ if (argc > 1) { if (strcmp(argv[1], "debug") == 0) debug = 1; } /* We need to track our children */ act.sa_handler = sig_child; sigemptyset(&act.sa_mask); act.sa_flags = 0; if (sigaction(SIGCHLD, &act, NULL) < 0) { perror("sigaction SIGCHLD"); exit(1); } /* Run user commands until they type 'exit' */ while (!done) { /* Print the prompt */ fflush(stdout); printf("\ncs590> "); /* Read the command */ fgets(buf, 256, stdin); buf[strlen(buf)-1] = 0; strTrim(buf); if (buf[strlen(buf)-1] == '&') { bkg = TRUE; buf[strlen(buf)-1] = 0; } /* Skip empty commands */ if (strcmp(buf, "") == 0) continue; /* Builtin commands */ if (strcmp(buf, "exit") == 0) { done = TRUE; break; } if (strcmp(buf, "showstatus") == 0) { fprintf(stdout, "%d\n", cmd_status); continue; } if (strncmp(buf, "sleep", 5) == 0) { mysleep(atoi(buf+5)); continue; } if (debug) fprintf(stderr, "user input = (%s)\n", buf); /* Run command */ run_command(buf); } printf("\n"); }