Interaction between pipes, execvpe, and shell scripts - c

I have the following code that forks and execvpe's a shell script and redirects its STDERR and STDOUT to the parent process.
#define _GNU_SOURCE
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#define BUFLEN 65536
int main () {
int pipes[2];
pipe(pipes);
pid_t pid = fork();
if (pid == -1) {return 1;}
else if (pid > 0) {
close(pipes[1]);
char buf[BUFLEN];
size_t n = read(pipes[0], buf, sizeof(buf));
int stat = 0;
waitpid(pid, &stat, 0);
printf("%.*s", n, buf);
} else {
dup2(pipes[1], STDOUT_FILENO);
dup2(pipes[1], STDERR_FILENO);
close(pipes[0]);
char *const argv[] = {"sh", "test", NULL};
execvpe(argv[0], argv, environ);
}
return 0;
}
As a minimal working example "test" is:
#!/bin/bash
cat file.txt
echo "Hello!"
echo "Goodbye!"
The output of the C program is the contents of file.txt and then the output from the echos are lost. If it's three echo statements then all of them get seen.
My best guess is that echo is a shell builtin and the shell will fork for cat and my pipes will be lost. In my project it seems the first command called in the script and the rest are lost.
If my assumption is correct how can I collect all the output from any and all children of what execvpe spawned?

I think the problem is simply a combination of timing and not checking for EOF before stopping reading from the pipe. If you wrap the read() call into a loop to read everything, all the data is read. When the cat completes, the read() returns everything that's available. The output from the echo commands is added to the pipe afterwards, but simply not read.
This code demonstrates, I believe. Note that execvpe() is not POSIX standard (and not available on macOS specifically) so I used my own surrogate header #include "execvpe.h" and implementation execvpe.c to obtain an implementation of it. Also, POSIX does not define a header that declares environ, so I declared it too. You're probably using Linux and the system headers there fix some gaps that POSIX leaves as holes.
Here's working code and data.
pipe17.c
/* SO 6412-3757 */
#define _GNU_SOURCE
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <stdio.h>
#define BUFLEN 65536
#include "execvpe.h" /* execvpe() is not in POSIX */
extern char **environ; /* No POSIX header declares environ */
int main(void)
{
int pipes[2];
pipe(pipes);
pid_t pid = fork();
if (pid == -1)
{
return 1;
}
else if (pid > 0)
{
close(pipes[1]);
char buffer[BUFLEN];
char *b_str = buffer;
size_t b_len = sizeof(buffer);
size_t t_len = 0;
ssize_t n_len;
while (b_len > 0 && (n_len = read(pipes[0], b_str, b_len)) > 0)
{
b_str += n_len;
b_len -= n_len;
t_len += n_len;
}
close(pipes[0]);
int status = 0;
int corpse = waitpid(pid, &status, 0);
printf("%d 0x%.4X: [[%.*s]]\n", corpse, status, (int)t_len, buffer);
}
else
{
dup2(pipes[1], STDOUT_FILENO);
dup2(pipes[1], STDERR_FILENO);
close(pipes[0]);
close(pipes[1]);
char *argv[] = {"sh", "test", NULL};
execvpe(argv[0], argv, environ);
fprintf(stderr, "failed to execute '%s'\n", argv[0]);
exit(1);
}
return 0;
}
test
#!/bin/bash
cat file.txt
echo "Hello!"
echo "Goodbye!"
echo "Errors go to standard error" >&2
file.txt
Line 1 of file1.txt
The very last line of file1.txt
Sample output
14171 0x0000: [[Line 1 of file1.txt
The very last line of file1.txt
Hello!
Goodbye!
Errors go to standard error
]]
Note that the code closes both ends of the pipe before calling execvpe(). It isn't crucial here, but it often can be crucial to do so. Your original code passed a size_t value, n, to printf() for use by the * in the format. You might get away with that on a 64-bit machine where sizeof(int) == sizeof(size_t), but it yields compilation warnings on 64-bit machines where sizeof(int) < sizeof(size_t).
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.

Related

With vs without the C pipe() function- what's causing this behavior?

I wrote a simple script (taken from a tutorial) which writes data to one end of a pipe in a child process, and reads it from the other end of the pipe in the parent process:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main()
{
pid_t pid;
int mypipefd[2];
int ret;
char buf[20];
ret = pipe(mypipefd);
if (ret == -1) {
printf("Pipe failed.\n");
exit(1);
}
if ((pid = fork()) == -1) {
printf("Fork failed.\n");
exit(1);
} else if (pid == 0) {
printf("Child process.\n");
char msg[] = "Hello there!";
write(mypipefd[1], msg, strlen(msg) + 1);
} else {
printf("Parent process.\n");
read(mypipefd[0], buf, 15);
printf("Buf: %s\n", buf);
}
return 0;
}
This works fine and outputs the results I expect:
Parent process.
Child process.
Buf: Hello there!
[ project ] $
Then as I got more familiar with the code, I wondered why we need to use mypipefd[2] and pipe() to achieve this goal, or whether mypipefd[1] by itself would work. So I tried it out with the following code:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main()
{
pid_t pid;
int my_array[1];
char buf[20];
if ((pid = fork()) == -1) {
printf("Fork failed.\n");
exit(1);
} else if (pid == 0) {
printf("Child process.\n");
char msg[] = "Hello there!\n";
write(my_array[0], msg, strlen(msg) + 1);
} else {
// wait(NULL);
printf("Parent process.\n");
read(my_array[0], buf, 15);
printf("Buf: %s\n", buf);
}
return 0;
}
This code outputs the same text, but it hangs after it finishes printing.
Parent process.
Child process.
Buf: Hello there!
No prompt, this time. I even tried un-commenting that call to wait(NULL), on the off-chance that the root cause was a conflict between parent and child processes. No such luck.
What's going on here? Why am I unable to read and write to a length-of-one array in this way without the program hanging? What exactly is the compiler stuck on?
A pipe, on computers as well as in real life, have two ends. And like pipes in real life, data flows from one end of the pipe (the write end) to the other (the read end).
The pipe function gives you those two ends by writing them to an array of two file-descriptors. The first element of the pair is read-only, and the second is write-only.
The pipe() function accepts an array of 2 integer as an input argument.
#include <unistd.h>
int pipe(int pipefd[2]);
#define _GNU_SOURCE /* See feature_test_macros(7) */
#include <fcntl.h> /* Obtain O_* constant definitions */
#include <unistd.h>
int pipe2(int pipefd[2], int flags);
It then generates a new pipe object, and initializes the pipefd array with file descriptors for read and write operation.
What you try to do is call read() and write() using some arbitrary, uninitialized ints (or file descriptor). Meaning the OS did not allocate a pipe object and did not provide you with file descriptors (the pipe's API) to be use with read() and write().
This (calling read() or write() with uninitialized file descriptor) will result in "undefined behavior".
"I find that a good working definition of "undefined behaviur" is "works for me, works for you, works during development and QA, but blows up in your most important customer's face"" --- Scott Meyers

Issue with CTRL+D on Windows Subsystem for Linux

I was working on an assignment using Windows Subsystem for Linux. Below is the C Code that was used to write a mini-shell for this assignment.
I ran into an interesting issue using WSL. On line 35, you can see that I call the read function to read in the buffer and it does a check for null. When pressing Ctrl+D, while using WSL, it will go into the if statement and prints the print message on line 36 infinitely and does not stop until I used Ctrl+C to exit. When running this program on a Linux machine, it behaves appropriately and prints once, and brings us to the top of the loop.
Any ideas as to what this bug could be?
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <errno.h>
#include <string.h>
#include <sys/types
#include <unistd.h>
#include <error.h>
char prompt[] = "$ ";
static int
Fork()
{
pid_t pid;
if ((pid = fork()) < 0)
error(EXIT_FAILURE, errno, "fork error");
return(pid);
}
int
main(void)
{
long MAX = sysconf(_SC_LINE_MAX);
char buf[MAX];
pid_t pid;
int status, n;
do {
write(STDOUT_FILENO, prompt, strlen(prompt));
fflush(NULL);
memset(buf, 0, MAX);
if((n = read(STDIN_FILENO, buf, MAX)) == 0) {
printf("use exit to exit shell\n");
continue;
}
buf[strlen(buf) - 1] = '\0'; // chomp '\n'
if (strncmp(buf, "exit", MAX) == 0) { // match
break;
}
pid = Fork();
if (pid == 0) { // child
execlp(buf, buf, (char *)NULL);
error(EXIT_FAILURE, errno, "exec failure");
}
// parent
if ((pid = waitpid(pid, &status, 0)) < 0)
error(EXIT_FAILURE, errno, "waitpid error");
} while(1);
exit(EXIT_SUCCESS);
}
The program is in C but there are no options available to insert C Code snippets.
Documentation on read() (Linux manpages v 3.54) does not specify that end of file (ctrl/D) causes read to return anything besides of 0. On the contrary, it says that return value zero indicates end of file. So you're relying upon undefined behavior.
Somehow on your Linux ctrl/D causes error, thus read() returns -1. Your program in this case exits the loop. Or, ctrl/D is read literally, then read() returns 1.
Different OSs use different keystrokes for EOF.

Child shell process bidirectional redirection to parent process

Hello stackoverflow I tried to create a program which execute a son shell process and redirect his I/O to a pipe in order to communicate with his father process.
I can execute command via the write pipe (wpipefd) but I can't get the response from the shell process on the read pipe (rpipefd).
I had 3 errors so far according to Strace : First the read function was blocking the program so I made ​​the read fd of the reading pipe non-blocking (rpipe[0]). Then I had an EAGAIN error with the read function... Finally I got an EPIPE error when I close the read fd from rpipe (close(rpipefd[0])) in the forked process just after the use of dup2() .
I don't understand what I did wrong. Here's what I did so far :
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#define BUF_SIZE 1024
int main(int argc, char **argv)
{
int rpipefd[2], wpipefd[2], pid;
pipe(rpipefd);
pipe(wpipefd);
char buffer[BUF_SIZE] = {0};
int flags = fcntl(rpipefd[0], F_GETFL, 0);
fcntl(rpipefd[0], F_SETFL, flags | O_NONBLOCK);
pid = fork();
if(pid == 0)
{
close(rpipefd[0]);
dup2(rpipefd[1],1);
dup2(rpipefd[1],2);
close(wpipefd[1]);
dup2(wpipefd[0],0);
close(rpipefd[1]);
close(wpipefd[0]);
execl("/bin/sh","/bin/sh",NULL);
}
close(wpipefd[0]);
write(wpipefd[1],"echo helloWorld",strlen("echo helloWorld"));
close(rpipefd[1]);
read(rpipefd[0],buffer,BUF_SIZE);
//perror("read()");
printf("%s",buffer);
exit(0);
}
Please help !
The main issue doesn't come from the code itself: the command passed to the shell is incomplete, you missed the final '\n' and thus the child process (your shell) is waiting for the rest of the command.
The non-blocking part is not a good idea (or at least, you should spin around you pipe in order to retrieve its content.)
Once you're done with your command, you should close the output pipe so the shell get the end-of-file on its input.
Other remarks: you should wait for the child termination (using wait(2)), you should leave after your execl in the child process (use with err(3) for the error message) to handle exec errors. And, seriously, calling strlen on string literal ? I know that gcc is replacing it at compile time, but …
Here is a modified version of your code:
#include <err.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#define BUF_SIZE 1024
int main(int argc, char **argv)
{
int rpipefd[2], wpipefd[2], pid;
pipe(rpipefd);
pipe(wpipefd);
char buffer[BUF_SIZE] = {0};
pid = fork();
if(pid == 0)
{
close(rpipefd[0]);
dup2(rpipefd[1],STDOUT_FILENO);
dup2(rpipefd[1],STDERR_FILENO);
close(wpipefd[1]);
dup2(wpipefd[0],STDIN_FILENO);
close(rpipefd[1]);
close(wpipefd[0]);
execl("/bin/sh","/bin/sh",NULL);
err(1, "execl()");
}
close(wpipefd[0]);
close(rpipefd[1]);
write(wpipefd[1], "echo helloWorld\n", 16);
close(wpipefd[1]); // we're done, say it to the shell
int r;
while ( (r = read(rpipefd[0],buffer,BUF_SIZE)) )
{
if (r == -1)
{
if (errno == EAGAIN || errno == EINTR) continue;
err(1, "read()");
}
write(STDOUT_FILENO, buffer, r);
}
wait(NULL);
return 0;
}

result of child process's exection of some system command can't send to the father process with pipe

Maybe this is not a compact title, I am very sorry about that:). I try redirecting stdin/stdout of a child process to its parent process with pipes. The child process execute a system command from the father process input and return the exec result to the father process with a pipe. Here I implemented "cat -n" and "tr /a-z/ /A-Z/", the former works fine, but later haven't return any results. What has caused this? Thank you.
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <assert.h>
#include <sys/sem.h>
#define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while( 0)
int main(int argc, char *argv[])
{
int chi_pipe[2], par_pipe[2];
if (pipe(chi_pipe) == -1 || pipe(par_pipe) == -1)
ERR_EXIT("pipe error");
/* Set O_NONBLOCK flag for the read end (pfd[0]) of the pipe. */
if (fcntl(chi_pipe[0], F_SETFL, O_NONBLOCK) == -1) {
fprintf(stderr, "Call to fcntl failed.\n"); exit(1);
}
/* Set O_NONBLOCK flag for the read end (pfd[0]) of the pipe. */
if (fcntl(chi_pipe[1], F_SETFL, O_NONBLOCK) == -1) {
fprintf(stderr, "Call to fcntl failed.\n"); exit(1);
}
pid_t pid;
pid = fork();
if (pid == -1)
ERR_EXIT("fork error");
if (pid == 0)
{
close(chi_pipe[0]); // I don't read in channel 1
close(par_pipe[1]); // I don't write in channel 2
dup2(chi_pipe[1], STDOUT_FILENO);
close(STDIN_FILENO);
dup2(par_pipe[0], STDIN_FILENO);
execlp("cat", "cat" , "-n", NULL);
//execlp("tr", "tr" , "/a-z/", "/A-Z/", NULL);
sleep(10);
close(chi_pipe[1]);
close(par_pipe[0]);
_exit(0);
}
close(par_pipe[0]);
close(chi_pipe[1]);
while(1) {
char input[1024];
memset(input, 0 , 1024);
fgets(input, 1024 ,stdin);
write(par_pipe[1], input, strlen(input));
char buf[3*1024];
int count = 0;
while (count <= 0)
count=read(chi_pipe[0], buf, 1024*3);
if (count >= 1)
{
printf("buf=%s", buf);
printf("\n");
}
}
close(par_pipe[1]);
close(chi_pipe[0]);
return 0;
}
A couple of points:
You are suffering from the need to perform non-blocking I/O. You are reading a line from a file, then writing it to a pipe. But there is no guarantee tr will conveniently write that line back translated. It might wait for the next line to come in. There is no line discipline in place. What you need to do is read from your file, write to tr (if the pipe is not full) and read from tr (if bytes are ready) at the same time. Or, more accurately, according to availability of data on the fd (to read) or the availability of space in the pipe (to write). Otherwise you will run into deadlock problems. tr isn't writing because it would rather read more first, and it hasn't got EOF. You aren't reading from tr because it hasn't written yet, so you aren't reading any more from the file either. To do this, you want to use select() (or poll()).
The only way execlp will return is if the exec fails; in that case you don't want to exit(0) as it's necessarily an error.

How to send a simple string between two programs using pipes?

I tried searching on the net, but there are hardly any resources. A small example would suffice.
EDIT
I mean, two different C programs communicating with each other. One program should send "Hi" and the other should receive it. Something like that.
A regular pipe can only connect two related processes. It is created by a process and will vanish when the last process closes it.
A named pipe, also called a FIFO for its behavior, can be used to connect two unrelated processes and exists independently of the processes; meaning it can exist even if no one is using it. A FIFO is created using the mkfifo() library function.
Example
writer.c
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
int fd;
char * myfifo = "/tmp/myfifo";
/* create the FIFO (named pipe) */
mkfifo(myfifo, 0666);
/* write "Hi" to the FIFO */
fd = open(myfifo, O_WRONLY);
write(fd, "Hi", sizeof("Hi"));
close(fd);
/* remove the FIFO */
unlink(myfifo);
return 0;
}
reader.c
#include <fcntl.h>
#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>
#define MAX_BUF 1024
int main()
{
int fd;
char * myfifo = "/tmp/myfifo";
char buf[MAX_BUF];
/* open, read, and display the message from the FIFO */
fd = open(myfifo, O_RDONLY);
read(fd, buf, MAX_BUF);
printf("Received: %s\n", buf);
close(fd);
return 0;
}
Note: Error checking was omitted from the above code for simplicity.
From Creating Pipes in C, this shows you how to fork a program to use a pipe. If you don't want to fork(), you can use named pipes.
In addition, you can get the effect of prog1 | prog2 by sending output of prog1 to stdout and reading from stdin in prog2. You can also read stdin by opening a file named /dev/stdin (but not sure of the portability of that).
/*****************************************************************************
Excerpt from "Linux Programmer's Guide - Chapter 6"
(C)opyright 1994-1995, Scott Burkett
*****************************************************************************
MODULE: pipe.c
*****************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
int main(void)
{
int fd[2], nbytes;
pid_t childpid;
char string[] = "Hello, world!\n";
char readbuffer[80];
pipe(fd);
if((childpid = fork()) == -1)
{
perror("fork");
exit(1);
}
if(childpid == 0)
{
/* Child process closes up input side of pipe */
close(fd[0]);
/* Send "string" through the output side of pipe */
write(fd[1], string, (strlen(string)+1));
exit(0);
}
else
{
/* Parent process closes up output side of pipe */
close(fd[1]);
/* Read in a string from the pipe */
nbytes = read(fd[0], readbuffer, sizeof(readbuffer));
printf("Received string: %s", readbuffer);
}
return(0);
}
dup2( STDIN_FILENO, newfd )
And read:
char reading[ 1025 ];
int fdin = 0, r_control;
if( dup2( STDIN_FILENO, fdin ) < 0 ){
perror( "dup2( )" );
exit( errno );
}
memset( reading, '\0', 1025 );
while( ( r_control = read( fdin, reading, 1024 ) ) > 0 ){
printf( "<%s>", reading );
memset( reading, '\0', 1025 );
}
if( r_control < 0 )
perror( "read( )" );
close( fdin );
But, I think that fcntl can be a better solution
echo "salut" | code
What one program writes to stdout can be read by another via stdin. So simply, using c, write prog1 to print something using printf() and prog2 to read something using scanf(). Then just run
./prog1 | ./prog2
Here's a sample:
int main()
{
char buff[1024] = {0};
FILE* cvt;
int status;
/* Launch converter and open a pipe through which the parent will write to it */
cvt = popen("converter", "w");
if (!cvt)
{
printf("couldn't open a pipe; quitting\n");
exit(1)
}
printf("enter Fahrenheit degrees: " );
fgets(buff, sizeof (buff), stdin); /*read user's input */
/* Send expression to converter for evaluation */
fprintf(cvt, "%s\n", buff);
fflush(cvt);
/* Close pipe to converter and wait for it to exit */
status=pclose(cvt);
/* Check the exit status of pclose() */
if (!WIFEXITED(status))
printf("error on closing the pipe\n");
return 0;
}
The important steps in this program are:
The popen() call which establishes the association between a child process and a pipe in the parent.
The fprintf() call that uses the pipe as an ordinary file to write to the child process's stdin or read from its stdout.
The pclose() call that closes the pipe and causes the child process to terminate.
This answer might be helpful for a future Googler.
#include <stdio.h>
#include <unistd.h>
int main(){
int p, f;
int rw_setup[2];
char message[20];
p = pipe(rw_setup);
if(p < 0){
printf("An error occured. Could not create the pipe.");
_exit(1);
}
f = fork();
if(f > 0){
write(rw_setup[1], "Hi from Parent", 15);
}
else if(f == 0){
read(rw_setup[0],message,15);
printf("%s %d\n", message, r_return);
}
else{
printf("Could not create the child process");
}
return 0;
}
You can find an advanced two-way pipe call example here.
First, have program 1 write the string to stdout (as if you'd like it to appear in screen). Then the second program should read a string from stdin, as if a user was typing from a keyboard. then you run:
$ program_1 | program_2

Resources