I'm working on a custom shell that can handle multiple pipes. But every time I execute a new pipeline and check the process with ls -l /proc/pid/fd
I get something like in the picture below and the list keeps expanding with every new pipeline executed:
Question: Is this considered as a fd leak? And how do I fix it?
Here's a code snippet for my pipeline execution:
enum PIPES {READ, WRITE};
void execute_pipeline(char*** pipeline)
{
int fd[2];
int fd_backup = 0;
pid_t child_pid;
while (*pipeline != '\0')
{
pipe(fd);
child_pid = fork();
if(child_pid == -1)
{
perror("fork");
exit(1);
}
else if(child_pid == 0)
{
dup2(fd_backup, 0);// (old, new)
close(fd[READ]);
if(*(pipeline + 1) != '\0')
{
dup2(fd[WRITE], 1);
}
execvp((*pipeline)[0], *pipeline);
exit(1);
}
else// Parent process
{
wait(NULL);
close(fd[WRITE]);
fd_backup = fd[READ];
pipeline++;
}
}
}
EDIT
An example how to call execute_pipeline:
char *ls[] = {"ls", "-l", NULL};
char *sort[] = {"sort", "-r", NULL};
char *head[] = {"head", "-n", "3", NULL};
char **pipeline[] = {ls, sort, head, NULL};
execute_pipeline(pipeline);
Let's be accurate regarding the file descriptors and bear in mind that during fork and execvp file descriptors are inherited by child processes unless marked as w/ CLOEXEC flag. Check the man pages:
fork(2): The child inherits copies of the parent's set of open file descriptors. Each file descriptor in the child refers to the same open file description (see open(2)) as the corresponding file descriptor in the parent.
open(2): By default, the new file descriptor is set to remain open across an execve(2) (i.e., the FD_CLOEXEC file descriptor flag described in fcntl(2) is initially disabled); the O_CLOEXEC flag, described below, can be used to change this default. The file offset is set to the beginning of the file (see lseek(2)).
But this behaviour, I supposed, exactly what you were relying on by calling fork after pipe...
No other words, lets' draw this:
I hope the picture is self explanatory.
Child processes will die anyway and all their fds will be closed (so no issue with them). But the parent process is left with 3 opened fds and they keep growing with each pipe executed.
As tadman pointed out, it is easier to use a command struct to pass things around.
We can't [well, we could but shouldn't] do a
wait
[in the parent] during pipe construction. That has to be a separate loop later. We would hang the parent after the first child is created.If the first child had a large amount of output, the kernel pipe buffers might fill up and the first child would block. But, since the second child has not been created, there is nothing to read/drain the first child's output and unblock it.
Also, it is important to close the pipe units after doing
dup2
and ensure the previous pipe stage units are closed in the parent.Here's a refactored version that does all that.
As to your original issue with file descriptor leakage, I think I fixed that by adding some more
close
calls. The program has some self verification code on this: