A problem was issued during the production of the minishell, using pipe() and fork() to apply the pipeline, while producing code that performs the same role as "command | command" in bash
This code above has made it perform the same role as "ls -al | head" and is actually done and terminated
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <errno.h>
int main (){
int fd[2], status;
pipe(fd);
if(fork() == 0){
close(fd[0]);
dup2(fd[1], STDOUT_FILENO);
close(fd[1]);
execlp("ls", "ls", "-al", NULL);
}
else
wait(&status);
if(fork() == 0){
close(fd[1]);
dup2(fd[0], STDIN_FILENO);
close(fd[0]);
execlp("head", "head", NULL);
}
else
wait(&status);
close(fd[1]);
close(fd[0]);
}
This code has the same function as "ls-al | greptxt", and it does exactly the output, but the code does not exit because it does not advance further in the second wait.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <errno.h>
int main (){
int fd[2], status;
pipe(fd);
if(fork() == 0){
close(fd[0]);
dup2(fd[1], STDOUT_FILENO);
close(fd[1]);
execlp("ls", "ls", "-al", NULL);
}
else
wait(&status);
if(fork() == 0){
close(fd[1]);
dup2(fd[0], STDIN_FILENO);
close(fd[0]);
execlp("grep", "grep", "txt", NULL);
}
else
wait(&status);
close(fd[1]);
close(fd[0]);
}
I think the grep does not end because if the pipeline tries to input after the end of the value that is currently in the pipe, it does not recognize it and continues to wait for another value to enter the pipeline. But I don't know how to solve this.
Currently, the method applied to solve this problem is implemented by using usleep() to remove the child process after a certain time, but this is too unstable. I certainly hope that the parent process will resume when the child process completes the execute.
CodePudding user response:
You aren't closing enough file descriptors in the parent process.
Also, you must run all the processes in the pipeline before waiting for any of them to finish. Suppose the first process creates a gigabyte of data. It will fill the pipe buffer (usually 64 Kib these days, but it used to be 5 Kib once upon another millennium, and POSIX only mandates a pipe buffer size of at least 4 KiB). And then it will block, meaning it won't exit, so the wait()
won't complete, so the second process won't be started. You avoid that by starting all the processes, then the parent closes its copy of the pipe (so that the process can get EOF properly), and only then goes into a loop to wait()
for the commands to complete.
So, you need to:
- Remove the first
wait()
. - Close the pipe in the parent process.
- Replace the second
wait()
with a loop that executeswait()
until there are no children left. - Or use
waitpid()
in a loop and wait for the two children to die, waiting on each child PID in turn (and keeping a record of the PIDs, therefore).
Note that fork()
can fail. Often you should diagnose that. It probably won't matter here, but a lot of programming is about taking care of errors.
Rule of thumb: If you
dup2()
one end of a pipe to standard input or standard output, close both of the
original file descriptors returned by
pipe()
as soon as possible.
In particular, you should close them before using any of the
exec*()
family of functions.
The rule also applies if you duplicate the descriptors with either
dup()
or
fcntl()
with F_DUPFD
or F_DUPFD_CLOEXEC
.
If the parent process will not communicate with any of its children via
the pipe, it must ensure that it closes both ends of the pipe early
enough (before waiting, for example) so that its children can receive
EOF indications on read (or get SIGPIPE signals or write errors on
write), rather than blocking indefinitely.
Even if the parent uses the pipe without using dup2()
, it should
normally close at least one end of the pipe — it is extremely rare for
a program to read and write on both ends of a single pipe.
Note that the O_CLOEXEC
option to
open()
,
and the FD_CLOEXEC
and F_DUPFD_CLOEXEC
options to fcntl()
can also factor
into this discussion.
If you use
posix_spawn()
and its extensive family of support functions (21 functions in total),
you will need to review how to close file descriptors in the spawned process
(posix_spawn_file_actions_addclose()
,
etc.).
Note that using dup2(a, b)
is safer than using close(b); dup(a);
for a variety of reasons.
One is that if you want to force the file descriptor to a larger than
usual number, dup2()
is the only sensible way to do that.
Another is that if a
is the same as b
(e.g. both 0
), then dup2()
handles it correctly (it doesn't close b
before duplicating a
)
whereas the separate close()
and dup()
fails horribly.
This is an unlikely, but not impossible, circumstance.