Pipe commands with fork and dup2 - c

I wrote the following code in order to pipe two commands:
#include <stdlib.h>
#include <unistd.h>
char *program_1[3] = {"/bin/cat", "/dev/random", NULL};
char *program_2[2] = {"/bin/ls", NULL};
char *program_3[2] = {"/usr/bin/sort", NULL};
int main(void)
{
int fd[2];
int pid;
pipe(fd);
if ((pid = fork()) == 0) //Child process
{
dup2(fd[1], STDOUT_FILENO);
close(fd[0]);
execve(program_3[0], program_3, NULL);
}
else if (pid > 0) //Parent process
{
dup2(fd[0], STDIN_FILENO);
close(fd[1]);
execve(program_2[0], program_2, NULL);
}
return (EXIT_SUCCESS);
}
Each pair of program_x / program_y where x != y works fine, except this one.
When i pipe sort into ls, ls well prints its output on stdout, but then, sort throw this error: sort: Input/output error.
When I type sort | ls into bash, it prints ls result as my program, but then waits for input.
Am I doing someting wrong ?
edit: I'm trying to reimplement the shell's behaviour

The problem is that when ls finishes, the parent process will exit which will close the read-end of the pipe, which will lead to an error being propagated to the write-end of the pipe which is detected by sort and it write the error message.
That it doesn't happen in the shell is because shells handle pipes differently than your simple example program, and it keeps the right-hand side of the pipe open and running (possibly in the background) until you pass EOF (Ctrl-D) to the sort program.

Your program isn't quite equivalent to what a shell typically does.
You're replacing the parent with ls; whereas shell would create who child processes and connect them and wait for them to finish.
It's more like:
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
char *program_2[2] = {"/bin/ls", NULL};
char *program_3[2] = {"/usr/bin/sort", NULL};
int main(void)
{
int fd[2];
pid_t pid;
pid_t pid2;
pipe(fd);
if ((pid = fork()) == 0) //Child process
{
dup2(fd[1], STDOUT_FILENO);
close(fd[0]);
execve(program_3[0], program_3, NULL);
}
else if (pid > 0) //Parent process
{
if ( (pid2 = fork()) == 0) {
dup2(fd[0], STDIN_FILENO);
close(fd[1]);
execve(program_2[0], program_2, NULL);
}
}
waitpid(pid, 0, 0);
waitpid(pid2, 0, 0);
return (EXIT_SUCCESS);
}

I finally found the solution, we were close to:
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
char *cat[3] = {"/bin/cat", "/dev/random", NULL};
char *ls[2] = {"/bin/ls", NULL};
char *sort[2] = {"/usr/bin/sort", NULL};
int main(void)
{
int fd[2];
pid_t pid;
pid_t pid2;
pipe(fd);
if ((pid = fork()) == 0)
{
dup2(fd[1], STDOUT_FILENO);
close(fd[0]);
execve(cat[0], cat, NULL);
}
else if (pid > 0)
{
if ( (pid2 = fork()) == 0)
{
dup2(fd[0], STDIN_FILENO);
close(fd[1]);
execve(ls[0], ls, NULL);
}
waitpid(pid2, 0, 0);
close(fd[0]);
}
waitpid(pid, 0, 0);
return (EXIT_SUCCESS);
}
We need to close the read end of the pipe once the last process ends, this way, if the first process tries to write on the pipe, an error will be throwed and the process will exit, else if it only reads from stdin as sort, it will keep reading as stdin is still open

Related

Trying to replicate basic bash pipe but i get a stdin: Input/output error

I am currently working on a university project to basically built my own simple shell. Everything is working great so far. The only thing giving me trouble is pipes. To make it easier for myself to figure out why they are not working as intended I wrote this little testing program where I try to replicate the bash behaviour of cat | ls. But i now sadly get this error cat: stdin: Input/output error and i really can't figure it out.
Here is my program:
#include <unistd.h>
#include <stdlib.h>
int main(void)
{
extern char **environ;
char *argv1[] = {"cat",NULL};
char *argv2[] = {"ls",NULL};
int fd[2];
pid_t pid;
int ret;
pipe(fd);
pid = fork();
if (pid == 0)
{
close(fd[0]);
dup2(fd[1], STDOUT_FILENO);
execve("/bin/cat", argv1, environ);
exit (0);
}
else if (pid > 0)
{
close(fd[1]);
dup2(fd[1], STDIN_FILENO);
execve("/bin/ls", argv2, environ);
waitpid(pid, &ret, 0);
}
return (0);
}
You want a pipe like:
ls | cat
But, you're setting this up like:
cat | ls
And, in your current code, for the ls side, you're doing:
close(fd[1]);
dup2(fd[1], STDIN_FILENO);
This is wrong for two reasons:
You're closing the wrong side of the pipe, so the dup2 gets a closed fd as its first argument
You're attaching the output side of the pipe to the command's input side
So, we need to reverse the pipe order and fix the closing.
Also, after doing dup2(X,...) we want to do close(X).
Also, note that doing waitpid _after execve will have no effect unless the execve fails.
Here is the refactored and working code:
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int
main(void)
{
extern char **environ;
char *argv_cat[] = { "cat", NULL };
char *argv_ls[] = { "ls", NULL };
int fd[2];
pid_t pid;
int ret;
pipe(fd);
pid = fork();
// we want:
// ls | cat
if (pid == 0) {
close(fd[1]);
dup2(fd[0], STDIN_FILENO);
close(fd[0]);
execve("/bin/cat", argv_cat, environ);
exit(0);
}
else if (pid > 0) {
close(fd[0]);
dup2(fd[1], STDOUT_FILENO);
close(fd[1]);
execve("/bin/ls", argv_ls, environ);
waitpid(pid, &ret, 0);
}
return (0);
}
UPDATE:
waitpid after execve is pointless. –
William Pursell
Not quite. It reaps the [stuck] child process so that it doesn't become a child of the init/systemd process.
In the above example, I had forgotten to add a close(STDOUT_FILENO) before the waitpid to "release" the cat process.
Here is the adjusted code:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/wait.h>
int opt_f;
int
main(int argc,char **argv)
{
extern char **environ;
char *argv_cat[] = { "cat", NULL };
char *argv_ls[] = { "ls", NULL };
int fd[2];
pid_t pid;
int status;
--argc;
++argv;
for (; argc > 0; --argc, ++argv) {
char *cp = *argv;
if (*cp != '-')
break;
cp += 2;
switch (cp[-1]) {
case 'f':
opt_f = ! opt_f;
break;
}
}
pipe(fd);
pid = fork();
// we want:
// ls | cat
if (pid == 0) {
if (opt_f)
fprintf(stderr,"cld: getpid=%d\n",getpid());
close(fd[1]);
dup2(fd[0], STDIN_FILENO);
close(fd[0]);
execve("/bin/cat", argv_cat, environ);
exit(0);
}
else if (pid > 0) {
close(fd[0]);
dup2(fd[1], STDOUT_FILENO);
close(fd[1]);
const char *ls = opt_f ? "/bin/gooch" : "/bin/ls";
execve(ls, argv_ls, environ);
fprintf(stderr,"execve failure of '%s' -- %s\n",ls,strerror(errno));
// release other process (cat)
close(STDOUT_FILENO);
// reap the child
pid_t ret = waitpid(pid, &status, 0);
fprintf(stderr,"ret=%d pid=%d status=%8.8X\n",ret,pid,status);
}
return (0);
}

How to implement env | grep LANG with fork execve and pipe

I'm writing my minishell and I can't understand why execve doesn't work when calling pid_2 ?
My main task is to implement env | grep LANG
int main(void)
{
pid_t pid_1, pid_2;
int fd[2];
int status;
char *mass_1[] = {"env", NULL};
char *mass_2[] = {"grep", "LANG", NULL};
pid_1 = fork();
pipe(fd);
if (pid_1 == 0)
{
dup2(fd[1], 0);
close(fd[0]);
execve(mass_1[0], mass_1, NULL);
exit(1);
}
pid_2 = fork();
if (pid_2 == 0)
{
dup2(fd[0], 0);
close(fd[1]);
execve(mass_2[0], mass_2, NULL);
exit(1);
}
close(fd[0]);
close(fd[1]);
waitpid(pid_1, &status, WUNTRACED);
waitpid(pid_2, &status, WUNTRACED);
return (0);
}
There are three issues in your code:
execve() doesn't search for the command in $PATH so generally it won't find neither env nor grep. Use execvp() instead.
You should call pipe() before the first fork() to prevent another pipe to be created and used by the child process
as #KamilCuk has pointed out: in the first child process, you should dup2() stdout instead of stdin
Fixing these bugs lead to this working code
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdio.h>
#include <errno.h>
int main(void)
{
pid_t pid_1, pid_2;
int fd[2];
int status;
char *mass_1[] = {"env", NULL};
char *mass_2[] = {"grep", "LANG", NULL};
pipe(fd);
pid_1 = fork();
if (pid_1 == 0)
{
dup2(fd[1], 1);
close(fd[0]);
execvp(mass_1[0], mass_1);
exit(1);
}
pid_2 = fork();
if (pid_2 == 0)
{
dup2(fd[0], 0);
close(fd[1]);
execvp(mass_2[0], mass_2);
exit(1);
}
close(fd[0]);
close(fd[1]);
waitpid(pid_1, &status, WUNTRACED);
waitpid(pid_2, &status, WUNTRACED);
return (0);
}
Btw. you should add some error handling e.g. pipe() or fork() could fail
If you must not call execvp() you have to provide pathnames to execve() like "/usr/bin/env" or "/bin/grep". In this case you also have to provide the parent's environment at least to env otherwise it won't print anything.
The easiest way to do so is:
change the signature of main() to int main(int argc, char *argv[], char *envp[])
call execve(mass_1[0], mass_1, envp);

Piping two child processes, one for ls, the other for sort, but sort is not working

I'm trying to create two child processes and pipe them, but the second child is not sorting the output produced by the first child which does ls. What am I doing wrong?
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
int pipefd[2];
pid_t ls_pid, wc_pid;
pipe(pipefd);
if ((ls_pid = fork()) == 0) {
dup2(pipefd[1],STDOUT_FILENO);
close(pipefd[0]);
execl("/bin/ls", "ls", 0);
perror("exec ls failed");
exit(EXIT_FAILURE);
}
if ((wc_pid = fork()) == 0) {
dup2(pipefd[0], STDIN_FILENO);
close(pipefd[1]);
execl("/usr/bin/sort", "sort", NULL);
perror("exec wc failed");
exit(EXIT_FAILURE);
}
return EXIT_SUCCESS;
}
The sort should work, but there are 2 caveats in your code, first, make sure close fd in all the processes that holds references to the fd, otherwise the fd won't close, and that's why the sort process hangs there when done, because it does not receive the EOF from stdin, and that's because the pipefd in the parent process were not closed. The other one is make sure wait the children to exit and check their exit status. Add the following to the send of main function:
close(pipefd[0]);
close(pipefd[1]);
int status;
int pid = waitpid(ls_pid, &status, 0);
pid = waitpid(wc_pid, &status, 0);
You have to pass NULL as the third parameter of the firtexecl just like you do in the second one. What happens is that execl executes correctly (that's why you dont get an error) but the ls command does not work as you give it an invalid command.
Btw, you should make error control on all OS requests, like in fork()
Combining all comments, and tested:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
int pipefd[2];
pid_t ls_pid, wc_pid;
int status;
pipe(pipefd);
if ((ls_pid = fork()) == 0) {
dup2(pipefd[1],STDOUT_FILENO);
close(pipefd[0]);
close(pipefd[1]);
execlp("ls", "ls", NULL);
perror("exec ls failed");
exit(EXIT_FAILURE);
}
if ((wc_pid = fork()) == 0) {
dup2(pipefd[0], STDIN_FILENO);
close(pipefd[0]);
close(pipefd[1]);
execlp("sort", "sort", NULL);
perror("exec sort failed");
exit(EXIT_FAILURE);
}
close (pipefd[0]);
close (pipefd[1]);
/* wait for two children to finish */
wait(&status);
wait(&status);
return EXIT_SUCCESS;
}

strange behavior of stdout redirected to a pipe

Here is a minimal example demonstrating my problem. I have a program forking a new subprocess and redirecting stdout to it. It works fine. Then I fork a second subprocess and redirect stdout to it and I close the first pipe. I would expect that the first subprocess receives EOF in its input pipe and terminates. Instead it remains in reading state until the main task exits. I do not understand why. I would expect the first pipe to be closed and the first child process to become a zombie.
Here is the code demonstrating the issue:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int popenin(char *command) {
int pin[2];
pid_t pid;
if (pipe(pin) != 0) exit(1);
pid = fork();
if (pid < 0) exit(1);
if (pid == 0) {
close(pin[1]);
dup2(pin[0], 0);
close(pin[0]);
execlp("bash", "bash", "-c", command, NULL);
perror("Error:");
exit(1);
} else {
close(pin[0]);
return(pin[1]);
}
}
int main() {
int fd;
fd = popenin("gzip > foo1.gz");
dup2(fd, 1);
close(fd);
printf("foo 1 content\n");fflush(stdout);
fd = popenin("gzip > foo2.gz");
close(1);
dup(fd);
close(fd);
printf("foo 2 content\n");fflush(stdout);
sleep(10000);
}
This program creates two files foo1.gz and foo2.gz, both empty and there are two gzip processes running in the system. I'd expect to see the first file completed, closed and the first gzip process to exit.
If I modify the minimal example in the following way, it works as expected.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int popenin(char *command) {
int pin[2];
pid_t pid;
if (pipe(pin) != 0) exit(1);
pid = fork();
if (pid < 0) exit(1);
if (pid == 0) {
close(pin[1]);
dup2(pin[0], 0);
close(pin[0]);
execlp("bash", "bash", "-c", command, NULL);
perror("Error:");
exit(1);
} else {
close(pin[0]);
return(pin[1]);
}
}
int main() {
int fd;
fd = popenin("gzip > foo1.gz");
dup2(fd, 1);
close(fd);
printf("foo 1 content\n");fflush(stdout);
close(1); // close(1) is moved before popenin
fd = popenin("gzip > foo2.gz");
dup(fd);
close(fd);
printf("foo 2 content\n");fflush(stdout);
sleep(10000);
}
Can somebody explain why the first version does not work?

Having issues with pipe, fork, dup2

I am using pipes, fork , dup2 to implement “ls | more” or “ls | sort” etc.
I am just not able to understand the issue here.
When I run my program, I get this error:
./a.out
Missing filename ("less --help" for help)
Why am I getting "less" ??
What is wrong with this code ? If I change “more” to “ls” again, it works fine. I mean, its like doing ls | ls.
#define STDIN 0
#define STDOUT 1
int main()
{
int fd[2];
int pid;
char *lschar[20]={"ls",NULL};
char *morechar[20]={"more",NULL};
pid = fork();
if (pid == 0) {
/* child */
int cpid;
cpid = fork();
if(cpid == 0) {
//printf("\n in ls \n");
pipe(fd);
dup2(fd[1], STDOUT);
close(fd[0]);
close (fd[1]);
execvp("ls",lschar);
} else if(cpid>0) {
waitpid(cpid, NULL,0);
dup2(fd[0],STDIN);
close(fd[0]);
close(fd[1]);
execvp("more", morechar);
}
} else if (pid > 0) {
/* Parent */
waitpid(pid, NULL,0);
}
return 0;
}
Appreciate your help.
Your main problem lies in your placement of the pipe() call. You must call it before you fork():
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#define STDIN 0
#define STDOUT 1
int main()
{
int fd[2];
int pid;
char *lschar[20]={"ls",NULL};
char *morechar[20]={"more", NULL};
pid = fork();
if (pid == 0) {
/* child */
int cpid;
pipe(fd);
cpid = fork();
if(cpid == 0) {
//printf("\n in ls \n");
dup2(fd[1], STDOUT);
close(fd[0]);
close (fd[1]);
execvp("ls",lschar);
} else if(cpid>0) {
dup2(fd[0],STDIN);
close(fd[0]);
close(fd[1]);
execvp("more", morechar);
}
} else if (pid > 0) {
/* Parent */
waitpid(pid, NULL,0);
}
return 0;
}
Otherwise, the more process doesn't have the correct file descriptors. Further, the waitpid() in your more process is problematic and unnecessary (more will wait for input on its own). If ls had a particularly long output the pipe could get full causing ls to block on its writes. The result is a deadlock and it waits forever. Hence, I've also removed the offending waitpid() call.
Also, if you make a good practice of checking the return values of functions like pipe() and dup2() this error would have been much easier to find -- you would have seen that your dup2() was failing.

Resources