Multiple Pipe enabled Simple UNIX shell

kharri5 0 Tallied Votes 204 Views Share

This is a little code for a shell that allows for simple redirection and multiple yes! MULTIPLE piping abilities. It is slightly glitched for background processes, and this is only my second program written with C/C++ so it looks to be quite inefficient, but the piping works pretty nicely. The original project was meant to run something like a history command giving you statistics on times that each process used (system and user time) but the important factor is the piping. I could find absolutely no code snippets on the internet that allowed for more than one pipe between processes. The important thing to note is the close on the pipe. Not many will tell you that closing the pipe is necessary at certain points and frankly it seems like common sense, but that's only in hindsight, so...here is the code. I hope that it helps some poor schlub like me who had to work his/her ass off and get as much help as possible to understand how to do a pipeline...it sucked hard and long

/**
 * Name: Keshan Harris
 * cs ID: (taken out)
 * Language: C/C++
 * Program: This is a shell that reports stats of running times of processes initiated in 
 * the shell as well as the shell itself's process time 
 */

#define P_NAME "PATH"
#define STDI 0
#define STDO 1
#define STD 2
#include <time.h>
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/times.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/socket.h>
#include <signal.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <string>
#include <iostream>
#include <iomanip>
#include <cstdlib>
#include <vector>

using namespace std;

struct lnkLst
{
    char* process;
    double sysTm;   
    double useTm;
    struct lnkLst* nxt;
};

struct command_t
{
    char* file; //the file of the actual command
    char* args[255]; //the arguments
    int argN; //the number of arguments
    int inrd; //if the command had an in redirection
    int outrd; //if the command had an out redirection
};

//lnkLst functions

lnkLst* adde(lnkLst* lsptr, char* pr, double sT, double uT);
lnkLst* remv(lnkLst* lsptr);
void prntLst(lnkLst* p);
void freeList(lnkLst* head);

//program functions

char* takeInpt(command_t cmds[], char* cmnd, char inpt[], int &cmNm, char prcs[]); 

char* checkExe(char* com); //check the file if it can be executed
char* prcName(char* p);
void runProcess(command_t cmds[], lnkLst* l,char* infile, char* outfile, int cmNm,
		char* prNm, int bg);		
//run process

int status = 0;
//background arrays
pid_t allPids[255];
int pdCnt = 0;
char* bPNames[255];

//time structs and variables

struct rusage theTime;
double uTime = 0;
double sTime = 0;

//various shell conditions
int inredType = STD; //the type for a read redirection
int outredType = STD;  //the type for a write redirection
int runMode = 0; //0 for regular 1 for background mode
bool badMode = false; //badMode if the arguments for commands get messy
bool piped = false; //piped or not
bool badRed = false; //badRedirect if the redirect won't work
char* outredFile = NULL; //file for redirection STDOUTto file  > smybol
char* inredFile = NULL; //file for redireciton STDINfrom file < symbol

/* linked List functions */

/**
 *  function: adde
 *  returns: lnkLst pointer
 *  parameters: lnkLst pointer, char*, two doubles
 *  Description: This function adds an element to the list (adde)lement
 */

lnkLst* adde(lnkLst* lsptr, char* pr, double sT, double uT)
{
    lnkLst* p = lsptr;

    if (lsptr != NULL) 
    {
	while (lsptr->nxt != NULL)
	{
	    lsptr = lsptr->nxt;
        }
	lsptr->nxt = (struct lnkLst*) malloc (sizeof (lnkLst));
	lsptr = lsptr->nxt;
        lsptr->nxt = NULL;
	lsptr->process = pr;
	lsptr->sysTm = sT;
	lsptr->useTm = uT;
	return p;
    }
    else 
    {
	lsptr = (struct lnkLst*) malloc (sizeof (lsptr));
	lsptr->nxt = NULL;

	lsptr->process = pr;
        lsptr->sysTm = sT;
        lsptr->useTm = uT;
	return lsptr;
    }
}

/**
 *  function: remv
 *  returns: lnkLst pointer
 *  parameters: lnkLst pointer
 *  Description: This function removes an item from the list if necessary
 */

lnkLst* remv(lnkLst* lsptr) 
{
    lnkLst* temp;

    temp = lsptr->nxt;
    free (lsptr);
    return temp;
}

/**
 *  function: prntLst
 *  returns: nothing
 *  parameters: lnkLst pointer
 *  Description: This function prints out the list data
 */

void prntLst(lnkLst* p)
{
	if(p==NULL)
	{
	     return;
	}  //stopping condition
	else
	{
	   cout << "Process: " << p->process << " :  ";
	   cout << "System Time: " << p->sysTm << " : ";
	   cout << "User Time: "  << p->useTm << endl;
        }
	prntLst(p->nxt);  //recursive call
}

/**
 *  function: freeList
 *  returns: nothing
 *  parameters: lnkLst pointer
 *  Description: This function frees up the memory taken by the list
 */


void freeList(lnkLst* head) 
{
    lnkLst *tmp;

    while (head != NULL) 
    {
      tmp = head->nxt;
      free(head);
      head = tmp;
    }
}

/* Program functions */

/**
 *  function: takeInpt
 *  returns: nothing
 *  parameters: none
 *  Description: This function takes the input for the shell
 */

char* takeInpt(command_t cmds[], char* cmnd, char inpt[], int &cmNm, char prcs[])
{
	int argCount=0; //set the initial argument count
	int comCount=0; //and command count
	
	//ask for and get the input	
        cout << "$tatsh: ";
        cin.getline(inpt,255); //leave plenty of room for input

	strcpy(prcs,inpt); //get the whole line for all processes names
	cmnd = strtok(inpt," ");  //get the first command
        
	//first thing should be some command
	cmNm++; //up the number of commands at the beginning to at least 1 command
	cmds[comCount].file = cmnd;
	cmds[comCount].args[argCount] = cmnd;
	cmds[comCount].argN++;

        //all the rest will either be commands or arguments to a command
	cmnd = strtok(NULL, " "); //get next item
	
	    while(cmnd != NULL)
	    {	//get ready to get the next coming command

	        if(strcmp(cmnd,"|") == 0) //if input is a pipe
	        {
	            piped = true;
	            cmnd = strtok(NULL, " "); //go over the pipe and get next token
	       
	            if(cmnd != NULL)
	            {
			//if after the pipe one of these symbols shows its bad
			if( strcmp(cmnd,"|") == 0 || strcmp(cmnd,"&") == 0 ||
			    strcmp(cmnd,"<") == 0 || strcmp(cmnd,">") == 0)
                        {
			    piped = false;
                            badMode = true;
                            cmnd = NULL; //breaks the while loop
                        }
                        else
                        {  //otherwise it's a new command to be piped into
			    comCount++;
			    cmNm++;
			    cmds[comCount].file = cmnd;
			    cmds[comCount].args[0] = cmnd; //arg 0 of that
			    cmds[comCount].argN++;
			}
	            }
	            else
	            {  //if it has nothing after it, then its not a command
		        cerr << "You didn't pipe into anything" << endl;
			piped = false;
			badMode = true;
			cmnd = NULL; //breaks the while loop
	            }
	        }//end piping if

	        else if(strcmp(cmnd,">") == 0) //case of STDOUT from command to file
	        {
		    outredType = STDO; //change condition of out redirection type

	            cmnd = strtok(NULL, " "); //go over '>' and get next token     
	            
		    if(cmnd != NULL)
		    {	//if one of these symbols comes right after its a bad redirection
			if( strcmp(cmnd,"|") == 0 || strcmp(cmnd,"&") == 0 || 
			    strcmp(cmnd,">") == 0 || strcmp(cmnd,"<") == 0 )
			{
			    badRed = true;
			    cmnd = NULL; //breaks the while loop
			}
			else
			{ //otherwise if either redirection hasn't been filled
			    if(outredFile == NULL || inredFile == NULL)
			    {
		                outredFile = cmnd; //first redirection file
			    }
			    else //if both redirections have already been filled
			    { 
				cerr<< "Ambigious input redirect" << endl;
				badRed = true;
				cmnd = NULL;  //breaks the while loop
			    }
			}
		    } 
	    	    else 
	    	    {  //if they put nothing to redirect
              	        cerr<< "Redirect to what now?? " << endl;
			badRed = true;
            	    }
                } //end STDOUT if

	        else if (strcmp(cmnd, "<") == 0)  //redirection of STDIN to a process
	        {
		    //change condition of in redirection type            
		    inredType = STDI;
		    cmnd = strtok (NULL, " "); //go over < and get next token

            	    if (cmnd != NULL)
	    	    { //if its a bad symbol for redirection
                        if( strcmp(cmnd,"|") == 0 || strcmp(cmnd,"&") == 0 ||
                            strcmp(cmnd,">") == 0 || strcmp(cmnd,"<") == 0 )
                        {
                            badRed = true;
                            cmnd = NULL;  //breaks the while loop
                        }   
                        else
                        { //if one of the is still undefined
                            if(outredFile == NULL || inredFile == NULL)
                            {
                                inredFile = cmnd; //first redirection file
                            }
                            else
                            { //if both redirects are defined already
                                cerr<< "Ambigious output redirect" << endl;
			        badRed = true;
                                cmnd = NULL;  //breaks the while loop
                            }
                        }
                    }   
	    	    else 
	    	    { //if they put nothing after the redirect
                        cerr<<  "Redirect what now?? " << endl;
			badRed = true;
            	    }
	        } //end STDIN if
	
		else if( strcmp(cmnd, "&") == 0) //a background process is called
		{
		     runMode = 1; //change to background mode

		     cmnd = strtok(NULL, " ");
		     if( cmnd != NULL)
		     { //if they put a symbol after it
			if( strcmp(cmnd,"|") == 0 || strcmp(cmnd,"&") == 0 ||
                            strcmp(cmnd,">") == 0 || strcmp(cmnd,"<") == 0 )
			{
			     badMode = true;
			     cmnd = NULL;  //breaks the while loop
		 	}
		     }
		} // end & if

	        else //get arguments to the current command and if a redirect was made
		     //make sure it gets registered to that particular command
	        {
		     argCount++;
		     cmds[comCount].args[argCount] = cmnd;
		     cmds[comCount].argN++;

		     /* both outrd and inrd of each command are safeties as an extra
			thing that I could use to make sure I knew which command had
			in particular an output or input redirect. Their use may or 
			may not be shown to be obselescent, but to stay on the safe side
			it doesn't hurt to keep them in there.
		     */

		     if(outredType != STD)
		     {
			cmds[comCount].outrd = outredType; //outrd is safety container
		     }
		     if(inredType != STD)
		     {
			cmds[comCount].inrd = inredType; //inrd is safety container
		     }
	        }

	        cmnd = strtok(NULL, " "); //go now to the next token for commands

	} //end while loop

	return prcs; //return the whole line of input for all process names which will
		     //be used later and parsed out by a function called prcNames(prcs);
}

/**
 *  function: checkExe
 *  returns: char*
 *  parameters: char*
 *  Description: This function checks if the file is a valid file to exec
 *  and should return the full path of the file if it was found and NULL otherwise
 */

char* checkExe(char* com)
{
    struct stat stbuf;
    char pth[1024];
    char* tmp = (char*)calloc(1024,sizeof(char));
  
    char* pT = getenv(P_NAME);
    strcpy(pth,pT);

    pT = strtok(pth,":");
 
    while(pT != NULL)
    {
	strcpy(tmp,pT);
	strcat(tmp,"/");
	strcat(tmp,com);

        //check if it is a file and can be executed
        if ( stat(tmp, &stbuf) != -1 && (S_ISREG(stbuf.st_mode)) &&  
            (stbuf.st_mode && (S_IXOTH | S_IXGRP | S_IXUSR)) && 
            access(tmp, X_OK) == 0 )
        {
	    //return it if so
	    return tmp;
        }
	else
	{
            //otherwise get the next path token
            tmp[0] = '0';
            pT = strtok((char*)0, ":");
	}
    }
    //if it reached the end without finding valid executable return NULL
    cerr << com << ": Command not found" << endl;
	
    return NULL;
}

/**
 *  function: prcName
 *  returns: char*
 *  parameters: char*
 *  Description: This function breaks apart the original full input of the user
 *  into each of the commands and their arguments one at a time, so when put into
 *  the list of processes run for the statistics call it can show the full process
 *  arguments and all included
 */

char* prcName(char* p)
{
    char prcss[1024];
    char* tmp = (char*)calloc(1024,sizeof(char));
    char* pC;
    
    strcpy(prcss,p);          
   
    pC = strtok(prcss," ");
 
    while(pC != NULL)
    {
	if( strcmp(pC, "|") == 0 )
	{
	    pC = strtok(NULL, " "); //skip over pipe
	    strcpy(p,pC);
	    pC = strtok(NULL, " "); //skip over already copied first cmnd
	    while(pC != NULL)
	    {
	       strcat(p," ");
	       strcat(p,pC);
	       pC = strtok(NULL, " ");
	    }
	    break;
	}
	else
	{
           strcat(tmp,pC);
	   strcat(tmp," ");
	}
	pC = strtok(NULL," ");
    }

    return tmp; //returns the process name for each call
}
 
/**
 *  function: runProcess
 *  returns: nothing   
 *  parameters: char* cmds[], lnkLst* tL, char* infiles,outfiles,prNm, int cmNm,bg
 *  Description: This function runs exec and forking commands for the commands by taking 
 *  in the number of commands, the struct array of commands, the in and outfiles if there
 *  is any, the background state if it exists, and the process names as well as the link
 *  list to store the statistical data
 */

void runProcess(command_t cmds[], lnkLst* tL, char* infile, char* outfile, int cmNm, 
		 char* prNm, int bg)
{
    int fid1 = -1; //for file redirection input initially a -1 for neither std value
    int fid2 = -1; //for file redirection output initially a -1 for neither std value
    int fildes[2];
    pid_t pid = 0;
    mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH;

    //The following must be created dynamically as it is illegal declaration at compile time    
    int* readin;
    readin = new int[cmNm];
    int* writeout;
    writeout = new int[cmNm];
    pid_t* pids;
    pids = new pid_t[cmNm];
    char** fpaths;
    fpaths = new char*[cmNm];
    char** pNames;
    pNames = new char*[cmNm]; 

    //check all commands to see if they are valid commands
    for(int i=0; i < cmNm; i++)
    {
	 fpaths[i] = checkExe(cmds[i].file);

         if(fpaths[i] == NULL)
         {
	     return;
         }
	 if(bg != 1)
	 {
             pNames[i] = prcName(prNm);
	 }
	 else
	 {
	    pNames[i] = prNm;
	 }
    }

    for(int p=0; p < cmNm; p++)
    {
	readin[p] = -1; //if it is -1 there is no pipe or redirection of stdin
	writeout[p] = -1; //if it is -1 there is no pipe or redirection of stdout
    }

    //do the redirect file here

    if(infile != NULL) // this "<" symbol
    {
            fid1 = open(infile, O_RDONLY, mode);
            if(fid1 == -1)
            {
                cerr<< infile << ":Doesn't exist" << endl;
                exit(1);
            }
            readin[0] = fid1;
    }
    if(outfile != NULL) // this ">" symbol
    {
            fid2 = open(outfile, O_EXCL | O_CREAT | O_TRUNC | O_WRONLY, mode);
            if(fid2 == -1)
            {
                cerr<< outfile << ":File already exists" << endl;
                exit(1);
            }
            writeout[cmNm-1] = fid2;
    }

    //make necessary amount of pipes for processes
    if(piped == true)
    {    
        for(int j=0; j < cmNm-1; j++)
        {	
	   //0 1 and 2 are currently filled by stdin,out, and err so first times this gives 3 and 4
           if( pipe(fildes) == -1 )
           {
	       perror("Pipe failure");
	       return;
           }
	   //the pipe goes from filedes 1 to filedes 0
	   //cerr << "The pipe is going from: " << fildes[1] << " to " << fildes[0] << endl;
	   readin[j+1] = fildes[0];
           writeout[j] = fildes[1];
        }
    }
    
    for(int k=0; k < cmNm; k++)
    {
        pid = fork();
	
	if(pid < 0)
        {
            cerr<< "Fork " << k << " Failed" << endl; 
            return;
        }
        else if( pid  > 0) //parent part
	{
	    pids[k] = pid;
	}
	else //child part
	{
	    //cerr << "Iteration " << k << ", child PID " << getpid() << " servicing " << cmds[k].file << endl;
	    if(writeout[k] != -1) //the stdout of cmd[k] should be redirected to 
	    {
		//cerr << "This is a test to see if we get to dup2 for writeout at " << k << endl;
		if( dup2(writeout[k],STDOUT_FILENO) == -1){
			perror("dup2 problem in runProc():");
			exit(1);

                }
		//cerr<< "The stdout is: " << writeout[k] << " and the PID is: " << getpid() <<  endl;
		//cout << "The new stdin is: " << STDIN_FILENO << endl;
	    }
	    
	    if(readin[k] != -1) //it should write out at first child though
	    {
		if( dup2(readin[k],STDIN_FILENO) == -1)
		{
		    perror("dup2 problem in runProc()");
		    exit(1);
		}

		//cerr << "The stdin is: " << readin[k] << "and the PID is: " << getpid() << endl;
		//cout << "The error number is: " << errno << endl;
	    }
	    //since fork gives exact copies we have to close out all copies pipe ends before we execvp
	    for(int h = 0; h < cmNm; h++)
	    {
		close(writeout[h]);
		close(readin[h]);
	    }

	    execvp(cmds[k].file,cmds[k].args);
	    cout<< cmds[k].file << ":Command not found" << endl;
	    exit(1);
	}
	
    }
    
    //the parent must wait for all forked off children
    for(int m=0; m < cmNm; m++)
    {
	    //cerr<< "The current pid being waited for is: " << pids[m] << endl;
	    if(bg == 0) //just a standard process
	    {
	        wait4(pids[m],&status,0,&theTime); //wait on children
 
	        //if we are done with this process we can close its stdin and sdtout

		if( writeout[m] != -1) close(writeout[m]);
                if( readin[m] != -1) close(readin[m]);
		
		//get the rusage times and store them
		uTime = theTime.ru_utime.tv_usec/1e6 + theTime.ru_utime.tv_sec;
                sTime = theTime.ru_stime.tv_usec/1e6 + theTime.ru_stime.tv_sec;
            
                adde(tL,pNames[m],sTime,uTime);
 	    }
	    else
	    { //it is a background process

		allPids[pdCnt] = pids[m];
		bPNames[pdCnt] = pNames[m];
	
		pdCnt++;
	    }
    }
    

    //delete dynamically allocated arrays
    delete fpaths;
    delete pNames;
    delete pids;
    delete readin;
    delete writeout;
    
}


/*
 *  function: main
 *  returns: int   
 *  parameters: none
 *  Description: This is main. This function runs the program
 */

int main()
{
   command_t comType[20];
   char* cmnd = NULL;
   char inpt[255];
   int cmNm = 0;
   char prcs[255];
   char* prName;
   
   //init link list
   lnkLst* head;
   head = new lnkLst;
   head->process = " ";
   head->sysTm = 0.0;
   head->useTm = 0.0;
   head->nxt = NULL;


   for(int p=0; p < 255; p++)
   {
        bPNames[p] = NULL;
	allPids[p] = 0;
   }
   
   while(1)
   {
	inredType = STD;
	outredType = STD;
	cmNm = 0;
	//init just in case
	piped = false;
	runMode = 0;   
        inredFile = NULL;
	outredFile = NULL;
	badRed = false;
	badMode = false;

	//initialize and NULL out all parts of commands array
	for(int i=0; i<20; i++)
	{
	     comType[i].file = NULL;
	     for(int j=0; j < 255; j++)
	     {
		comType[i].args[j] = NULL;	
	     }
	     comType[i].argN=0;
	     comType[i].inrd=STD;
	     comType[i].outrd=STD;
	}

	for(int i=0; i < 255; i++)
        {
            if(bPNames[i] != NULL)
            {
                if( wait4(allPids[i],&status,WNOHANG,&theTime) != 0)
                {
                     uTime = theTime.ru_utime.tv_usec/1e6 + theTime.ru_utime.tv_sec;
                     sTime = theTime.ru_stime.tv_usec/1e6 + theTime.ru_stime.tv_sec;
                     
		     adde(head,"bG Process",sTime,uTime); //problem here with bPNames!! don't know why
                     bPNames[i] = NULL;
                }
            } 
         
        }

	//take input from user 
   	prName = takeInpt(comType,cmnd,inpt,cmNm,prcs);

  	//no input
        if(comType[0].file == NULL)
        {
	    continue;
        }
	if( strcmp(comType[0].file, "help") == 0 || strcmp(comType[0].file, "h") == 0 )
	{
	    cout<< "Welcome to the Statsh Help Menu " << endl;
	    cout<< "================================" << endl;
	    cout<< "The shell handles the following " << endl;
	    cout<< "non standard commands: " << endl;
	    cout<< "Type            To Do This " << endl;
	    cout<< "------------------------- " << endl;
	    cout<< "exit(or e)	 exit shell " << endl;
 	    cout<< "quit(or q)	 alt exit  " << endl;
	    cout<< "stats        show time stats " << endl;
	    cout<< "help(or h)	to go here " << endl;
	}
	//exit status
   	else if(strcmp(comType[0].file, "exit") == 0 || strcmp(comType[0].file, "quit") == 0
	        || strcmp(comType[0].file, "e") == 0 || strcmp(comType[0].file, "q") == 0 )
   	{
	     getrusage(RUSAGE_SELF,&theTime);
	     uTime = theTime.ru_utime.tv_usec/1e6 + theTime.ru_utime.tv_sec;
             sTime = theTime.ru_stime.tv_usec/1e6 + theTime.ru_stime.tv_sec;

             adde(head,"The Stats Shell",sTime,uTime); 
	     cerr<<"Printing Exiting Process Statistics..." << endl;
             prntLst(head);
             break;
   	}
	//stats command
        else if(strcmp(comType[0].file, "stats") == 0 )
        {
	     prntLst(head);
        }
	//run procedures otherwise
	else
	{
	     if(badMode == false && badRed == false)
	     {
	        runProcess(comType,head,inredFile,outredFile,cmNm,prName,runMode);
	     }
	     else
	     {
		cerr<< "Invalid null command" << endl;
   	     }
	}

   } //end the program while loop
	
   freeList(head);
   return 0;

} //end main
bumsfeld 413 Nearly a Posting Virtuoso

Muchly great code man!

kboloor 0 Newbie Poster

Hi,

Great piece of code... really helped me for my project submission.
Thanks a lot for posting it!!!!

ZeRo 00 -4 Newbie Poster

dude...seemss to be somethin though i dint get all of it
i also need somethin similar to this..
i think this will be of grt help
thankz

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.