I’m attempting to create my own shell I believe i have the forking done correctly but i cannot figure out how to pipe correctly. Any help or tips would be appreciated.
basically my pipes aren’t working and i have spent ages attempting to figure out how to get them to properly transmit the data between the processes.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#include "ourhdr.h" // from Steven's book Advanced programing in the UNIX Enviroment
extern int makeargv(char *, char * , char ***);
int main()
{ char **argp;
int i,j,vpret;
char buf[80];
pid_t pid;
int pnum;
//pipe number
int ploc[16];
//pipe location
//(this has a max of 12 possible pipes)
int targ;
int fdleft[2], fdright[2];
printf(" <(^_^)> \nHello \n I am your console and I am here to help you \n");
printf(" If you dont need me anymore just say \"bye\" ");
fflush(stdout);
write(1,"\n(>^_^)> ",8);
while(strcmp(fgets(buf, 80, stdin), "bye\n")!=0)
{
j=makeargv(buf," \n",&argp); //this breaks the line up and returns the number of commands
pnum = 0;
ploc[0] = 0;
if (j > 16) j = 16;
for (i=0;i<j;i++)
{
if ( strcmp(argp[i], "|") == 0)
{
argp[i]= NULL;
ploc[pnum+1] = (i+1);
pnum++;
}
}
for (i = 0; i < (pnum+1); i++)
{
pipe(fdright);
if (i != 0)
{
dup2(fdright[1], fdleft[0]);
}
pid = fork();
switch (pid)
{
case -1:
err_sys("fork failed");
break;
case 0: // child
if (i != pnum)
{
dup2(fdright[1],1);
}
if ( i != 0);
{
dup2(fdright[0],0);
}
//printf("(^o^) running pipe[%i]\n" , i);
targ =(ploc[i]) ;
execvp(argp[targ],&argp[targ]);
write(1,"(-_-) I'm sorry the exec failed \n",33);
exit(1);
default:
dup2(fdleft[1], fdright[1]);
waitpid(pid,NULL, 0);
close(fdright[0]);
close(fdright[1]);
//waitpid(pid,NULL, 0);
}
}
//waitpid(pid, NULL, 0);
write(1,"\n(>^_^)> ",8);
}
printf(" v(^o^)^ BYE BYE!\n");
}
thanks
Various comments:
while (strcmp(fgets(buf, 80, stdin), "bye\n")!=0)
This crashes if the shell is given EOF instead of
bye
. Don't combine the two function calls like that. If you want it all in one loop condition, then use:We'll discuss limits on the length of the command line another time.
Since you've not supplied
makeargv()
to look at, we have to assume it does its job OK.Your loop that splits things up into commands and pipes is:
Let's assume we have a command line input:
ls -l | grep lemon
. It appears that yourmakeargv()
would return 5 and setargp
as follows:This loop of yours would give you:
Your code has a pair of file descriptors in the
fdleft
array, but you never initialize the array (callpipe()
, for example), even though you use it in calls todup2()
.The main
for
loop then has to run twice, once for each command. For the general case of three or more commands in a pipeline (who | grep me | sort
, for example), your first command (who
) needs its standard input untouched, but its standard output going to the pipe that connectswho
andgrep
. The 'middle' commands (second to penultimate commands, orgrep me
in the example) each needs its standard input to come from the previous pipe and needs to create a new pipe for its standard output to go to. The last command (in this example, the third command,sort
), needs its standard input to come from the last pipe and its standard output unchanged.Your code doesn't do that, or get anywhere close.
When you use
pipe()
and thendup()
ordup2()
to map any of the descriptors to standard I/O descriptors, you need to close both ends of the pipe. You simply don't have enoughclose()
calls around either.Your parent process has to launch each of the children in turn, and only waits for them to exit after it has launched them all. There are different ways of organizing the processes. The parent could fork once; the child could be responsible for launching the leading commands in the pipeline, finally executing the last command itself. The parent has just one direct child (the others are grandchildren), so it only has to wait for the one command to finish. The alternative is that the parent knows about each of the processes in the pipeline and it waits for them all to finish.
If your parent processes waits for each command in a pipeline to complete before launching the remainder, you can end up with a form of deadlock. One child writes so much data to its pipe that it is blocked by the kernel until some process reads from the pipe, but the process to read from the pipe is not yet launched and the parent is waiting for the child to exit. You also lose the benefits of multi-processing and concurrency.
In your 'case 0', you have an extraneous semi-colon. My compiler warned me about it. If yours didn't warn you about it, you need to use more compilation warnings or get a better compiler.
There are many questions about pipe lines in mini-shells on SO, including:
The answer for 13636252 is most nearly generic. The only snag is it uses
char ***
which is apt to be confusing, and it is so tightly written it has mutually recursive functions with minimal repetition. OTOH, it works correctly, and yourmakeargv()
function also uses achar ***
argument.Reworked code
Here is your code reworked so that it works. It includes implementations of
err_sys()
andmakeargv()
. Mymakeargv()
simply assumes that there will be fewer than 32 words in the command line. It is not, by any stretch of my imagination, a robust command line parser. It does allow you to typels | wc
and gives the correct answer; it also allowswho | grep me | sort
and gives the correct answer; it also allowsls
and gives the correct answer. The spaces around the pipe symbols are crucial, though (in a normal shell, they're optional, sowho|grep me|sort
should work too, but it won't with this code.Sample output: