Here is my pipe to pipe function:
int *pip is the pipe that I want to read from
char **cmd is the command that I want to execute
char **envp points to the environment variables
I want to take the pipe (pip) as STDIN with dup2
and take the new pipe (fd) as STDOUT also with dup2
The problem is that when it comes to execve the command is executed but the program is stuck in an infinite loop
int *pipe_to_pipe(int *pip, char **cmd, char **envp) {
int *fd;
int pid;
fd = (int *)malloc(sizeof(int) * 2);
if (!fd || pipe(fd) == -1)
error(1);
pid = fork();
if (pid == -1)
error(1);
if (pid == 0)
{
dup2(pip[0], STDIN_FILENO);
dup2(fd[1], STDOUT_FILENO);
close(fd[0]);
close(pip[1]);
execve((command_path), cmd, envp);
exit(0);
}
wait(NULL);
free(pip);
return (fd);
}
As an example, if I have "hello world" in my int *pip
as a char **cmd I have cmd[0] = "cat", cmd[1] = "-e".
When I make execve(path, cmd, envp);
the program cat "hello world" and infinite loop here.
How can I fix this?
The problem comes from the fact that I didn't close the pipe "pip" in the parent process adding those lines that would be executed by the parent process fix everything
if (pid != 0){
close(pip[0]);
close(pip[1]);
wait(NULL);
}
Otherwise it fixes my problem I don't understand why if someone can explain to me I will be gratefull
Thanks to everyone that answered me <3
Related
I have a C program that should emulate the same thing as calling:
popen("ls | grep som")
Right now I have two processes that each execute one part of this command and the firsts' output is the second ones' input. When I execute the program I see the correct line being prompted in the terminal but I can't seem to save the output to a string. I always end up with the first thing ls command prints out.
Example:
if ls prints out:
one
two
three
the string is always equal to "one".
This is what the code looks like:
int fd[2];
pipe(fd);
pid_t pid1, pid2;
FILE *f;
int pid1 = fork();
if (pid1 == 0) {
dup2(fd[1], STDOUT_FILENO);
close(fd[1]);
close(fd[0]);
execlp("ls", "ls", NULL);
}
int pid2 = fork();
if (pid2 == 0) {
dup2(fd[0], STDIN_FILENO);
close(fd[1]);
close(fd[0]);
execlp("grep", "grep", "som",NULL);
}
f = fdopen(fd[0] ,"r");
// then I read the output with snprintf
//and once again I close fd's
close(fd[0]);
close(fd[1]);
waitpid(// first process)
waitpid(// second process)
Add another pipe for grep's stdout and read from it from you main process.
I am working to make a shell like bash, but i have trouble solving heredoc << so i made a test code as simple as possible for this question.
void pipeline()
{
int i = 0;
int fd[2];
pid_t pid;
int fdd = 0;
while (i < 2)
{
pipe(fd);
pid = fork();
if (pid == 0)
{
//dup2(fd[1],1); if i dup in the first pipe cat dont finalize
if (i == 0)
dup2(fd[0],0);
write(fd[1], "hello\nhow\nare\nyou\n", 17);
close(fd[0]);
close(fd[1]);
dup2(fdd, 0);
if (i == 0)
execlp("cat", "cat", NULL);
else
execlp("grep", "grep", "you" , NULL);
perror("error");
exit(1);
}
else
{
close(fd[1]);
fdd = fd[0];
wait(NULL);
i++;
}
}
}
int main(int *argc, char **argv, char **env)
{
pipeline();
}
I know that cat and grep need an EOF to run; what I'm doing is writing in stdin and running cat, but my question is: how do I save stdout for grep without duping stdout on the first pipe?
If I dup on dup2(fd[1],1) cat does not work in the first pipe, could someone help me out to make this code work? And make it as similar to bash heredoc as well if possible.
how do I save stdout for grep without duping stdout on the first pipe?
I'd rearrange the creation of the child processes from rightmost to leftmost - then grep is created first and can output to the initial output descriptor. A necessary change is to run all child processes before waiting on one as well as before writing, so that there's no deadlock even if the pipe buffer wouldn't suffice for the heredoc.
void pipeline()
{
int i = 2; // create children from last to first
int fd[2];
pid_t pid;
int fdd = 1; // output of last child is STDOUT
while (i--)
{
pipe(fd);
pid = fork();
if (pid == 0)
{
dup2(fdd, 1); // child's output
dup2(fd[0], 0);
close(fd[0]);
close(fd[1]);
if (i == 0)
execlp("cat", "cat", "-A", NULL);
else
execlp("grep", "grep", "you" , NULL);
perror("error");
exit(1);
}
if (fdd != 1) close(fdd); // close if a pipe write end
fdd = fd[1]; // preceding child's output is pipe write end
close(fd[0]);
}
write(fd[1], "hello\nhow\nare\nyou\n", 17);
close(fd[1]); // signal EOF to child
while (wait(NULL) > 0) ; // wait for all children
}
I tried to run the following simple code:
int main(int argc, char* argv[]) {
int fds[2];
pipe(fds);
int pid1, pid2;
if ((pid1 = fork()) == 0){
close(fds[0]); // close read end
dup2(fds[1], 1); // connect write end
char *argv1[2] = {"echo", "hi"};
execvp(argv1[0], argv1);
}
if ((pid2 = fork()) == 0){
close(fds[1]); // close write end
dup2(fds[0], 0); // connect read end
char *argv2[2] = {"wc", "-c"};
execvp(argv2[0], argv2);
}
int status;
waitpid(pid1, &status, 0);
printf("pid=%d terminated with status=%d\n", pid1, status);
waitpid(pid2, &status, 0);
printf("pid=%d terminated with status=%d\n", pid2, status);
return 0;
}
I was expecting both the echo and wc to terminate, but the wc never does.
The output I get for the above code is just something like below (and the main process gets suspended waiting for the wc to finish).
pid=2802 terminated with status=0
Any ideas why the second forked process doesn't finish?
I have checked, by trying to read from fds[0] after a short sleep at the main process, that the wc command has indeed read the hi message from the echo.
You need to close all of the write ends of the pipe. There are 4 copies of that file descriptor, and wc is not going to terminate until all of them are closed. The echo will close both of its copies when it terminates, but it's best practice to explicitly close the unused copy before you exec with:
dup2(fds[1], STDOUT_FILENO);
close(fds[1]);
You need to also close that file descriptor in the parent before you call waitpid
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.
Let's say within a program I want to execute two processes, one to execute a ls -al command, then piping the result into the wc command, and displaying the output on the terminal. How can I do this using pipe file descriptors? So far the code I have written:
int main(int argc, char* argv[]) {
int pipefd[2];
int pipefd2[2];
pipe(pipefd2);
if ((fork()) == 0) {
dup2(pipefd2[1], STDOUT_FILENO);
close(pipefd2[0]);
close(pipefd2[1]);
execl("ls", "ls", "-al", NULL);
exit(EXIT_FAILURE);
}
if ((fork()) == 0){
dup2(pipefd2[0], STDIN_FILENO);
close(pipefd2[0]);
close(pipefd2[1]);
execl("/usr/bin/wc", "wc", NULL);
exit(EXIT_FAILURE);
}
close(pipefd[0]);
close(pipefd[1]);
close(pipefd2[0]);
close(pipefd2[1]);
}
An example would be greatly helpful.
Your example code was syntactically and semantically broken (e.g. pipefd2 not decared, confusion between pipefd and pipefd2, etc.) Since this smells like homework, please make sure you understand my annotations below and ask more if you need to. I've omitted error checks on pipe, fork and dup, but they should be there, ideally.
int main(int argc, char *argv[]) {
int pipefd[2];
pid_t ls_pid, wc_pid;
pipe(pipefd);
// this child is generating output to the pipe
//
if ((ls_pid = fork()) == 0) {
// attach stdout to the left side of pipe
// and inherit stdin and stdout from parent
dup2(pipefd[1],STDOUT_FILENO);
close(pipefd[0]); // not using the right side
execl("/bin/ls", "ls","-al", NULL);
perror("exec ls failed");
exit(EXIT_FAILURE);
}
// this child is consuming input from the pipe
//
if ((wc_pid = fork()) == 0) {
// attach stdin to the right side of pipe
// and inherit stdout and stderr from parent
dup2(pipefd[0], STDIN_FILENO);
close(pipefd[1]); // not using the left side
execl("/usr/bin/wc", "wc", NULL);
perror("exec wc failed");
exit(EXIT_FAILURE);
}
// explicitly not waiting for ls_pid here
// wc_pid isn't even my child, it belongs to ls_pid
return EXIT_SUCCESS;
}