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
Multiple Pipe enabled Simple UNIX shell
/**
* 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
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.