Can't read from a pipe after dup2() and fork. C - c

I'm writing a code that echo a string and sed it two times. My output is correct, but when I try to place that string on an array it blocks on read and goes on with the other calls.
Here's the code:
#include <unistd.h>
#include <sys/types.h>
#include <dirent.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
char **sendout=NULL;
int send_i=0;
void sender2(char* str_) {
int fd[2];
int fd1[2];
int fd2[2];
int pid;
char* echo[] = {"echo", str_, NULL};
char* sed[] = {"sed", "regex1", NULL};
char* sed2[] = {"sed", "regex2", NULL};
int status;
if (pipe(fd) < 0) {
exit(100);
}
pid = fork();
if (pid == 0) {
close(fd[0]);
dup2(fd[1], 1);
close(fd[1]);
execvp(echo[0], echo);
printf("Error in execvp1\n");
}
if (pipe(fd1) < 0) {
exit(100);
}
pid = fork();
if (pid == 0) {
close(fd[1]);
close(fd1[0]);
dup2(fd[0], 0);
dup2(fd1[1], 1);
dup2(fd1[1], 2);
close(fd[0]);
close(fd1[1]);
execvp(sed2[0], sed2);
printf("Error in execvp2\n");
}
if (pipe(fd2) < 0) {
exit(100);
}
pid = fork();
if (pid == 0) {
close(fd1[1]);
close(fd2[0]);
dup2(fd1[0], 0);
dup2(fd2[1], 1);
dup2(fd2[1], 2);
close(fd2[1]);
close(fd1[0]);
execvp(sed[0], sed);
}
pid = fork();
if (pid == 0) {
close(fd2[1]);
char* line = NULL;
size_t len = 0;
ssize_t read_;
FILE* f_pipe;
f_pipe = fdopen(fd2[0], "r");
printf("1\n");
while ((read_ = getline(&line, &len, f_pipe)) != -1) {
printf("2\n");
sendout = realloc(sendout, sizeof(char*) * (send_i + 1));
sendout[send_i] = strdup(line);
send_i++;
printf("%s\n", line);
}
fclose(f_pipe);
close(fd2[0]);
return;
}
close(fd[1]);
close(fd[0]);
close(fd1[1]);
close(fd1[0]);
close(fd2[1]);
close(fd2[0]);
if (pid != 0) {
wait(&status);
}
}
int main() {
sender2("hello");
}
Like I said it all worked until the read. If I pass 3 string to the function the output is like:
1
1
1
If I don't dup to the last pipe it prints pretty well what I need, I also used return in the last fork because it's the only child process that isn't killed from execvp. But it doesn't even reach the first print. I even tried opening the pipe as a file or with a classic open, so it goes that I tried open and also fopen, as you can see. I'm failing because it can't read anything. That would be a time problem.

Fork and File Descriptors
When you fork a process, copies of all file descriptors are inherited. Since those are copies, the descriptors must be closed in both the child and the parent. You should always close them as soon as possible. This is especially true if you fork several times.
It's very easy to miss something here. It is therefore best to check very carefully that all file descriptors have been closed.
Minimum Amount of Changes
So the minimum number of changes for your code to get a result would be as follows.
If the first fork in line 41 is successful then in the parent you need to close the pipe file descriptors fd[0] and fd[1], e.g. in line 56.
pid = fork();
if (pid == 0) {
...
}
close(fd[0]); //<-- add these two lines
close(fd[1]);
if (pipe(fd2) < 0) {
...
Likewise you need to do the same after the second fork for fd1, so:
pid = fork();
if (pid == 0) {
...
}
close(fd1[0]); //<-- add these two lines
close(fd1[1]);
pid = fork();
When you now run your code you would already get as output:
1
2
hello
Better Test Case
This would not yet verify that both sed commands would run correctly. For a test case change the call in main to:
sender2("hello mars");
and change your sed commands to:
char* sed[] = {"sed", "s/moon/world/", NULL};
char* sed2[] = {"sed", "s/mars/moon/", NULL};
(sed2 command is executed before sed in your code, it would make the code a bit easier to understand if sed is executed before sed2)
This gives as output then:
1
2
hello world
So both sed commands are executed.
Additional Remarks
Below are some remarks in no particular order, mainly concerning error handling.
A call to fork returns pid_t and not int. So you should change your definition of the variable pid to: pid_t pid;.
If execvp fails one should print the error cause and exit with an error status, e.g. something like this:
perror("execvp of command xyz failed");
exit(EXIT_FAILURE);
If opening a pipe fails, also print a descriptive message on stderr.
Also fork calls can fail, this should also be handled. In this case fork returns -1. Same as above, print error message on stderr and return an error status.
In main you should return a success or failure state (e.g. return EXIT_SUCCESS;).
You don't use the the variable read_. Then the variable can be removed.
If fdopen fails it returns NULL. This error case should be handled.
The memory allocated with realloc is never released.

Related

Unable to exit loop after reading using two pipes in C (processes)

I have taken a look at this and also this stack overflow links.
I am having trouble understanding the process for closing write ends of pipes. In the code below, I have 3 processes, one parent, a child of the parent, and a child of the child. I am trying to simulate a pipe for the command - cat xxx | grep 28 | sort. I have written some code for this, and it essentially creates the sorts, "grips"/filters and prints my input, but it hangs at the end. I have to ctrl + c to exit. My code is a little messy, but if you can help me spot the problem that would be nice.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
/**
* Executes the command "cat scores | grep Lakers". In this quick-and-dirty
* implementation the parent doesn't wait for the child to finish and
* so the command prompt may reappear before the child terminates.
*
*/
int main(int argc, char **argv)
{
int pipefd[2];
int pipefd2[2];
int pid;
char *cat_args[] = {"cat", "scores", NULL};
char *grep_args[] = {"grep", "28", NULL};
char *sort_args[] = {"sort", NULL};
// make a pipe (fds go in pipefd[0] and pipefd[1])
if (pipe(pipefd) != 0){
return 1;
}
if (pipe(pipefd2) != 0){
return 1;
}
pid = fork();
if (pid < 0)
{
fprintf(stderr, "fork Failed" );
exit(EXIT_FAILURE);
}
else if (pid == 0)
{
int pid2;
pid2 = fork();
if (pid2 < 0){
fprintf(stderr, "fork Failed" );
return 1;
}
else if (pid2 == 0){
// replace standard input with input part of pipe
// close(0);
// close(pipefd[1]);
// close(pipefd2[1]);
dup2(pipefd2[0], 0);
// close unused hald of pipe
close(pipefd2[0]);
close(pipefd[1]);
close(pipefd2[1]);
close(pipefd[0]);
// execute grep
execvp("sort", sort_args);
close(pipefd[1]);
close(pipefd2[1]);
exit(0);
}else{
// replace standard input with input part of pipe
// close(pipefd[1]);
// close(pipefd2[1]);
dup2(pipefd[0], 0);
dup2(pipefd2[1], 1);
// close unused hald of pipe
close(pipefd[0]);
close(pipefd2[1]);
close(pipefd[1]);
close(pipefd2[0]);
// execute grep
execvp("grep", grep_args);
waitpid(pid2, NULL, 0);
close(pipefd[1]);
close(pipefd[0]);
close(pipefd2[1]);
close(pipefd2[0]);
exit(0);
waitpid(pid2, NULL, 0);
}
}
else
{
// close(pipefd[1]);
// close(pipefd2[1]);
dup2(pipefd[1], 1);
// close unused unput half of pipe
close(pipefd[1]);
close(pipefd[0]);
close(pipefd2[1]);
close(pipefd2[0]);
// execute cat
execvp("cat", cat_args);
exit(0);
waitpid(pid, NULL, 0);
}
close(pipefd[1]);
close(pipefd[0]);
close(pipefd2[1]);
close(pipefd2[0]);
}
here is the output I am getting. Not sure it is relevant but as you can see, the result is sorted by team name. It just doesn't terminate.
Houston 44 28 .611
Indiana 45 28 .616
Oklahoma City 44 28 .611
Utah 44 28 .611
^C
Calling execvp replaces the current process image with a new process image. If no error occured, your code will never reach any line after that, so your close() and waitpid() function calls are useless.
EDIT
Here's a fully functional code to your problem. The comments should be self explanatory. Notice that the command executing order is different and I'm waiting for processes to finish.
Reading from an empty pipe will block until there is some data to read or an error occured, so this is not the only solution.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
static void die (const char *msg) {
perror (msg);
exit (EXIT_FAILURE);
}
int main (int argc, char** argv) {
int pipefd[2];
int pid;
char *cat_args[] = {"cat", "scores", NULL};
char *grep_args[] = {"grep", "28", NULL};
char *sort_args[] = {"sort", NULL};
//make a pipe (file descriptor to read is pipefd[0], fd to write is pipefd[1])
if (pipe (pipefd) < 0)
die ("creating a pipe failed");
pid = fork();
if (pid < 0)
die ("fork");
else if (pid == 0) {
//child process
int pipefd2[2]; //only visible to the affected processes
if (pipe (pipefd2) < 0)
die ("pipe");
int pid2;
pid2 = fork();
if (pid2 < 0)
die ("fork");
else if (pid2 == 0) {
//child of child will execute cat command
close (pipefd2[0]); //we only need to write to the second pipe. close its reading end
//first pipe is for communication between parent and grandparent only
close (pipefd[0]);
close (pipefd[1]);
dup2 (pipefd2[1], STDOUT_FILENO); //write the output to the second pipe instead of the standard output
close (pipefd2[1]); //close writing end of second pipe
execvp("cat", cat_args); //execute cat command
die ("execvp should never return");
}
else {
//child process will execute the grep command
close (pipefd2[1]); // we only need to read from the second pipe. close its writing end
close(pipefd[0]); //we won't read from the first pipe
waitpid (pid2, NULL, 0); //wait for cat command to finish
dup2 (pipefd2[0], STDIN_FILENO); //read from the second pipe instead of the stdin
close (pipefd2[0]); //child finished. close reading end of second pipe
dup2 (pipefd[1], STDOUT_FILENO); //write the results of grep command to first pipe instead of standard output
close (pipefd[1]); //we dup2 the output, close the writing end of first pipe
execvp ("grep", grep_args);
die ("execvp should never return");
}
} else {
//parent process will execute the sort command
close (pipefd[1]); //we won't write to the first pipe
waitpid (pid, NULL, 0); //wait for child to write grep output to the first pipe
dup2 (pipefd[0], STDIN_FILENO); //read from the first pipe instead of stdin
close (pipefd[0]); //child finished. close reading end of first pipe
execvp ("sort", sort_args); //execute command
die ("execvp should never return");
}
//exit (EXIT_SUCCESS); we don't need this. the programm will never reach this line
}

(SIMULATING A UNIX SHELL IN C) How to implement multiple pipes in a for loop?

I'm trying to simulate a unix shell in a C program and it's still in the beginning and working for at most two pipes. I have a vector of commands (char *com[3][3]), which were separated considering the character "|", but my question is how to proceed to more pipes in a for loop? In the follow the current implementation, I'm trying to execute 3 commands separeted by pipes:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
int main(int argc, char **argv){
//Vector with positions of pipes found, position 0 reserved for the total amount of commands.
char* com[3][3] = { { "/bin/ls", "-la", 0 },
{ "/bin/grep", ".", 0}, { "/usr/bin/wc", "-l", 0 }};
//EXECUTE COMMANDS
pid_t fork1, fork2, fork3;
int fd1[2], fd2[2];
if(pipe(fd1) < 0){
perror("pipe1");
}
if(pipe(fd2) < 0){
perror("pipe2");
}
//COMMAND 1
fork1 = fork();
if(fork1 == 0){
dup2(fd1[1], STDOUT_FILENO);
close(fd1[0]);
close(fd2[0]);
close(fd2[1]);
execvp(com[0][0], com[0]);
perror("execvp 1");
exit(EXIT_FAILURE);
}
//COMMAND 2
fork2 = fork();
if(fork2 == 0){
dup2(fd1[0], STDIN_FILENO);
dup2(fd2[1], STDOUT_FILENO);
close(fd1[1]);
close(fd2[0]);
execvp(com[1][0], com[1]);
perror("execvp 2");
exit(EXIT_FAILURE);
}
//COMMAND 3
fork3 = fork();
if(fork3 == 0){
dup2(fd2[0], STDIN_FILENO);
close(fd2[1]);
close(fd1[0]);
close(fd1[1]);
execvp(com[2][0], com[2]);
perror("execvp 3");
exit(EXIT_FAILURE);
}
close(fd1[0]);
close(fd1[1]);
close(fd2[0]);
close(fd2[1]);
waitpid(-1, NULL, 0);
waitpid(-1, NULL, 0);
waitpid(-1, NULL, 0);
return 0;
}
How do I make to com[n][3], in a for loop?
"To iterate is human, to recurse is divine" -- Anon.
I'd attack this with a recursive approach. This is one of those very rare occasions when being a Three Star programmer is almost justified. ;)
This is completely untested, but should get you pointed in the correct direction.
// You'll need to rearrange your command strings into this three dimensional array
// of pointers, but by doing so you allow an arbitrary number of commands, each with
// an arbitrary number of arguments.
int executePipe(char ***commands, int inputfd)
{
// commands is NULL terminated
if (commands[1] == NULL)
{
// If we get here there's no further commands to execute, so run the
// current one, and send its result back.
pid_t pid;
int status;
if ((pid = fork()) == 0)
{
// Set up stdin for this process. Leave stdout alone so output goes to the
// terminal. If you want '>' / '>>' redirection to work, you'd do that here
if (inputfd != -1)
{
dup2(inputfd, STDIN_FILENO);
close(inputfd);
}
execvp(commands[0][0], commands[0]);
perror("execvp");
exit(EXIT_FAILURE);
}
else if (pid < 0)
{
perror("fork");
exit(EXIT_FAILURE);
}
waitpid(pid, &status, 0);
return status;
}
else
{
// Somewhat similar to the above, except we also redirect stdout for the
// next process in the chain
int fds[2];
if (pipe(fds) != 0)
{
perror("pipe");
exit(EXIT_FAILURE);
}
pid_t pid;
int status;
if ((pid = fork()) == 0)
{
// Redirect stdin if needed
if (inputfd != -1)
{
dup2(inputfd, STDIN_FILENO);
close(inputfd);
}
dup2(fds[1], STDOUT_FILENO);
close(fds[1]);
execvp(commands[0][0], commands[0]);
perror("execvp");
exit(EXIT_FAILURE);
}
else if (pid < 0)
{
perror("fork");
exit(EXIT_FAILURE);
}
// This is where we handle piped commands. We've just executed
// commands[0], and we know there's another command in the chain.
// We have everything needed to execute that next command, so call
// ourselves recursively to do the heavy lifting.
status = executePipe(++commands, fds[0]);
// As written, this returns the exit status of the very last command
// in the chain. If you pass &status as the second parameter here
// to waitpid, you'll get the exit status of the first command.
// It is left as an exercise to the reader to figure how to get the
// the complete list of exit statuses
waitpid(pid, NULL, 0);
return status;
}
}
To use this, call it initially with the commands array set up as described, and inputfd initially -1.
If you want to handle < type redirection, you probably want to check for inputfd == -1 at the very top, do redirection if requested and replace inputfd with the appropriate value before entering the remainder of the body.

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.

process termination doesn't affect waitpid()

I need to simulate the following bash commands using C under Linux (with fork, exec, kill, signal, wait, waitpid, dup2, open, sleep, pipe etc).
[0] echo 'tail-f $1' > /tmp/rtail
[1]/tmp/rtail ~/.bash_history >> /tmp/1.txt &
PID of process [1] should be saved.
[2] Expect termination of the command started on step [1]. After termination print on the screen: "Program 1 terminated."
So far I have this code:
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(int argc, char *argv[]) {
pid_t pID = fork();
if (pID == 0) // child
{
int file = open("/tmp/rtail", O_CREAT | O_WRONLY);
//Now we redirect standard output to the file using dup2
dup2(file, 1);
puts("tail -f $1");
close(file);
system("chmod 777 /tmp/rtail");
exit(0);
} else if (pID < 0) // failed to fork
{
printf("Failed to fork");
exit(1);
// Throw exception
} else // parent
{
pid_t pID2 = fork();
if (pID2 == 0) {
char tmp1[20];
sprintf(tmp1, "echo %i > /tmp/pidprog1", getpid());
system(tmp1);
int file = open("/tmp/1.txt", O_APPEND | O_WRONLY);
//Now we redirect standard output to the file using dup2
dup2(file, 1);
FILE* proc = popen("sh /tmp/rtail ~/.bash_history", "r");
char tmp[20];
while (fgets(tmp, 40, proc) != NULL) {
printf(tmp);
}
fclose(proc);
exit(0);
}
else if (pID2 < 0) // failed to fork
{
printf("Failed to fork");
exit(1);
// Throw exception
} else {
FILE* fl = fopen("/tmp/pidprog1", "r");
char buff[10];
fgets(buff, 10, fl);
int pidc = atoi(buff);
fclose(fl);
int status;
waitpid(pidc, &status, 0);
printf("Program 1 terminated\n");
}
}
// Code executed by both parent and child.
return 0;
}
The problem is that when I manually kill the process using PID saved into /tmp/pidprog1, parent process doesn't stop waiting and doesn't print "Program 1 terminated" line.
The parent is very likely reading a garbage value into pidc. You are doing nothing to ensure that the grandchild has actually written the pid before the parent tries to read it. You need to use wait to ensure that valid pids are in the file. (Or, just keep track of the pids from the return value of fork.)
You are not doing enough error checking: what happens if any open fails? (eg, when you try
to open /tmp/1.txt for appending but it doesn't already exist?)
Why are you using fgets to read 40 characters into a buffer of size 20?
Why are you dup'ing and using fputs instead of just writing to the fd?
Why are you printing error messages to stdout instead of stderr ( use perror ).

Resources