I'm trying to implement a minishell which must be able to pipe commands.
Initially, I was executing the processes sequentially, that is, I was waiting for n
th a process to terminate before starting the n + 1
th process (this is a minimal reproducible example which executes ls | wc -l
):
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
#include <stdlib.h>
pid_t family[2];
int pipefd[2];
extern char **environ;
void ft_exec(int i)
{
char **ls = malloc(sizeof(char *) * 2);
ls[0] = "/bin/ls";
ls[1] = NULL;
char **wc = malloc(sizeof(char *) * 3);
wc[0] = "/usr/bin/wc";
wc[1] = "-l";
wc[2] = NULL;
printf("ENTERED BY %d\n", getpid());
if (i == 0)
{
dup2(pipefd[1], 1);
execve(ls[0], ls, environ);
}
if (i == 1)
{
dup2(pipefd[0], 0);
execve(wc[0], wc, environ);
}
}
void ft_interpret(void)
{
int i;
i = 0;
while (i < 2)
{
family[i] = fork();
printf("CREATED %d INSIDE %d\n", family[i], getpid());
if (family[i] == 0)
ft_exec(i);
waitpid(family[i], NULL, 0);
printf("TERMINATED %d IN %d\n", family[i], getpid());
if (i == 0)
{
printf("CLOSED W%d\n", pipefd[1]);
close(pipefd[1]);
}
else if (i == 1)
{
printf("CLOSED R%d\n", pipefd[0]);
close(pipefd[0]);
}
i++;
}
}
int main(int argc, char **argv)
{
(void)argc;
(void)argv;
pipe(pipefd);
printf("PARENT ID IS %d\n", getpid());
printf("CREATED PIPE WITH FDS R%d AND W%d\n", pipefd[0], pipefd[1]);
ft_interpret();
}
However, I realized that this is not how the shell works, and in case if a command like cat /dev/urandom | head -c 100
is given, the program will be stuck in a deadlock.
So I decided to fork the processes first, then whichever one of them exits, I close the associated write-ends of pipes.
For some reason, the reading command (wc
in this case; if we change arguments to non-reading command, such as printenv
, it'll execute correctly) gets stuck on reading, even though the main process has closed the write-end of the pipe and must've sent an EOF to wc
.
Here's the modified code:
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
#include <stdlib.h>
pid_t family[2];
int pipefd[2];
extern char **environ;
void ft_exec(int i)
{
char **ls = malloc(sizeof(char *) * 2);
ls[0] = "/bin/ls";
ls[1] = NULL;
char **wc = malloc(sizeof(char *) * 3);
wc[0] = "/usr/bin/wc";
wc[1] = "-l";
wc[2] = NULL;
printf("ENTERED BY %d\n", getpid());
if (i == 0)
{
dup2(pipefd[1], 1);
execve(ls[0], ls, environ);
}
if (i == 1)
{
dup2(pipefd[0], 0);
execve(wc[0], wc, environ);
}
}
void ft_block_main_process(void)
{
int i;
pid_t terminated;
i = 0;
while (i < 2)
{
terminated = waitpid(-1, NULL, 0);
printf("TERMINATED %d IN %d\n", terminated, getpid());
if (terminated == family[0])
{
printf("CLOSED W%d\n", pipefd[1]);
close(pipefd[1]);
}
else if (terminated == family[1])
{
printf("CLOSED R%d\n", pipefd[0]);
close(pipefd[0]);
}
i++;
}
}
void ft_interpret(void)
{
int i;
i = 0;
while (i < 2)
{
family[i] = fork();
printf("CREATED %d INSIDE %d\n", family[i], getpid());
if (family[i] == 0)
ft_exec(i);
i++;
}
ft_block_main_process();
}
int main(int argc, char **argv)
{
(void)argc;
(void)argv;
pipe(pipefd);
printf("PARENT ID IS %d\n", getpid());
printf("CREATED PIPE WITH FDS R%d AND W%d\n", pipefd[0], pipefd[1]);
ft_interpret();
}
Why does this happen?
Thanks for the insightful comments.
If I add close(pipefd[0])
and close(pipefd[1])
immediately after dup2()
s, the issue gets solved.
I thought that it was enough to close the pipe only from the main, shell process, but it turned out that all occurrences of fds have to be closed, including the ones inside children who inherit a copy.
The reason I got confused is the fact that the first piece of code worked. Now I realize that there, I was waiting for the first (ls
) process to exit, so when it exits, its copy of the write-end gets destroyed, and I close the last copy of it in the main process. Then wc
doesn't inherit any.
So now in my shell, during execution inside a child, after dup2()
ing the descriptors, I close each and every descriptor created by pipe()
, and it works.