How to get the error stream pipe of the child process? - c

I'm trying to execute an external program via libpipeline , but I can't get the error stream for the child process. code show as below:
#include <pipeline.h>
#include <stdlib.h>
#include <stdio.h>
#include <pwd.h>
#include <unistd.h>
#include <string.h>
int main() {
pipeline *cmd;
const char *line;
// get user info
struct passwd *uinfo = getpwuid(geteuid());
if (!uinfo) {
perror(NULL);
return EXIT_FAILURE;
}
printf("login shell: %s\n", uinfo->pw_shell);
// Create the pipeline of the external application, NULL indicates the end of parameter input.
cmd = pipeline_new_command_args(uinfo->pw_shell, "-c", "echo 'Hello World' 1>&2", NULL);
pipeline_want_out(cmd, -1);
pipeline_start(cmd);
line = pipeline_peekline(cmd);
if (!strstr(line, "coding: UTF-8")) printf("Unicode text follows:0\n");
while ((line = pipeline_readline(cmd))) printf("stdout: %s", line);
printf("exit code: %d\n", pipeline_wait(cmd));
return EXIT_SUCCESS;
}
How can I read the error stream of the child process?
The environmental information is as follows:
operating system: Linux, 5.15.60-1-MANJARO
gcc version: 12.1.1 20220730 (GCC)
shell: zsh 5.9

Here is a complete application using only the C library.
I based off this tutorial
#include <iostream>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
// Child process executes this
void child( int out[2], int err[2] ) {
// Duplicates stdout and stderr in the child, taking care to wait
while ((dup2(out[1], STDOUT_FILENO) == -1) && (errno == EINTR)) {}
while ((dup2(err[1], STDERR_FILENO) == -1) && (errno == EINTR)) {}
// Child does not need to access any pipe ends as it is using
// stdout/stderr
::close(out[1]);
::close(err[1]);
::close(out[0]);
::close(err[0]);
// Print something nice to papa
std::cerr << "Hello World" << std::endl;
}
// This is the parent executing
void parent( pid_t pid, int out[2], int err[2] ) {
// Parent does not need the write end of the child pipes (only read)
::close(out[1]);
::close(err[1]);
// Read stderr
while ( true ) {
char buf[4096];
ssize_t nb = ::read( err[0], buf, sizeof(buf));
if ( nb>0 ) {
std::cerr << "Read " << nb << " bytes" << std::endl;
std::cerr << " [" << std::string(buf,nb) << "]" << std::endl;
} else if ( nb==0 ) {
// pipe broke or was signaled
break;
}
else if ( nb<0 ) {
std::cerr << "Error " << strerror(errno) << std::endl;
break;
}
}
// wait for the child to end clean
while( true ) {
int pstatus = 0;
pid_t res = ::waitpid(pid, &pstatus, 0);
if ( res==-1 ) {
perror("waitpid");
exit(EXIT_FAILURE);
}
if ( res==pid ) {
std::cout << "Process exited with status "
<< WEXITSTATUS(pstatus) << std::endl;
break;
}
if(WIFEXITED(pstatus)) break;
}
}
int main() {
// We dont want signals
signal(SIGPIPE, SIG_IGN);
// Create the pipe between child and parent
int err[2];
int out[2];
::pipe(err);
::pipe(out);
// fork
pid_t pid = fork();
if ( pid==0 ) {
child(out,err);
return 69;
}
else {
parent(pid, out, err);
return 0;
}
}
It produces
Program stdout
Process exited with status 69
Program stderr
Read 11 bytes
[Hello World]
Read 1 bytes
[
]
Complete code: https://godbolt.org/z/hjaf8fYE6

How can I read the error stream of the child process?
As far as I can determine, libpipeline does not perform any stderr redirection, except as requested on a per-command basis via function pipecmd_discard_err(), or as you manually inject with the help of pipecmd_pre_exec() or pipeline_install_post_fork(). There is no built-in facility for reading the standard error output of any of the commands in the pipeline.
But it looks like you indeed can use some of the aforementioned mechanisms to do what you ask. Supposing that you want to capture the stderr output of all of the commands in the pipeline, you should be able to do this:
write a function to redirect stderr to the write end of a pipe. Something like this, maybe:
void redirect_stderr(void *pipe) {
int *pipe_fds = pipe;
// Hope that the following doesn't fail, because there aren't any
// particularly good choices for how to handle that in this
// context.
dup2(pipe_fds[1], STDERR_FILENO);
close(pipe_fds[1]);
}
Before you set up the pipeline, create a pipe for stderr redirection:
int err_pipe_fds[2];
int status;
status = pipe(pipe_fds);
// handle any error ...
Use the above to set up a pre-exec handler on each command in your pipeline:
void pipecmd_pre_exec(command1, redirect_stderr, NULL, err_pipe_fds);
void pipecmd_pre_exec(command2, redirect_stderr, NULL, err_pipe_fds);
// ...
(A pipeline-wide post-fork handler is not a good fit for this, because those do not accept arguments.)
Then, once the pipeline is running, you should be able to read the error output of all the commands from the read end of the pipe (file descriptor err_pipe_fds[0]). If you prefer stream I/O for that, then use fdopen() to wrap the file descriptor in a stream.
HOWEVER, do note that you should set up a separate thread to consume the stderr data, and you must start that thread before starting the pipeline. Otherwise, there is a risk that the pipeline will deadlock on account of the stderr pipe's buffer filling.

Related

How can I get my C Shell to recognize that this is a command?

I am very new at C but am currently working on creating a C program to serve as a shell interface. It is supposed to accept commands and then execute each command in a separate process. I am currently stuck trying to get C to recognize that it is a command. I am unsure how to do this, and can't seem to find any useful examples.
Here is my code, it is saying that everything is not a valid command ("no cmd"). Does anyone have any idea why this would be occurring? Is C not able to recognize it is a command in the execvp() function or do I need to implement something for that specific purpose?
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#define MAX_LINE 80
/* 80 chars per line per command */
int main(void) {
//char *args[MAX_LINE/2 + 1];
char *args = calloc(MAX_LINE, (MAX_LINE/2 +1));
const size_t sz = MAX_LINE;
pid_t pid;
/* command line (of 80) has max of 40 arguments*/
int should_run = 1;
while (should_run) {
printf("osh>"); //beginning of command line
fgets(args, sz, stdin); //gets the stdin
char *token = strtok(args, " \n"); //supposed to break str if it has a space or line and recognize there are 2 commands
printf("%s\n", token);
token = strtok(NULL," \n");
printf("%s\n", token);
pid_t parent = getpid();
pid = fork(); //forking child
if(pid == 0){ //if forking occurred
int status = execvp(&args[0], &args); //status of input, is it a command?
printf("%d", status);
printf("forked!");
if(status == -1) { //if cmd err, print
printf("no cmd");
return 1;
} else {
printf("line will be printed");
}
return 0;
}
fflush(stdout); //flush
/*
* After reading user input, the steps are :
* 1: fork a child process
* 2: the child process will invoke execvp()
* 3: parent process waits for the child to exit before
continuing
*/
}
exit(0);
/**
return to the operating system:
-exit this process
-flush all
*/
}
If you look at the documentation for the exec family of functions, you'll note that the functions only return if the exec failed. That's because exec, when successful, completely replaces the calling process with the invoked program.
What you need to do is, from the parent process (i.e., the one that got a positive value returned from fork), wait on the child process via waitpid.
pid_t pid;
pid = fork();
if ( pid < 0 ) {
// Handle the error.
}
else if ( pid == 0 ) {
execvp(&args[0], &args);
// The fact that we've reached this line means that execvp failed.
exit(1);
}
else {
int status;
while ( waitpid(pid, &status, 0) != pid ) {} // We need this loop in case waitpid gets interrupted by a signal.
// status doesn't equal the return value of the child process. We need to extract that with macros.
if ( WIFEXITED(status) ) {
printf("Child process exited with code %i\n", WEXITSTATUS(status));
}
else {
printf("Child process was terminated by signal number %i\n", WTERMSIG(status));
}
}

UNIX: running processes in a loop with fork() and dup2() ends loop early

I am beginning to code a shell in UNIX to practice with API calls such as fork() dup2(), read(), and wait(). Currently, my shell opens and runs fine. When I type a command to run, such as ls -a, it parses this command properly, and executes it. The problem is, the main loop terminates early, exiting the shell after a single command. I need the loop to continue to run until 'exit' is read from stdin. Here is my current code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <unistd.h>
#include <signal.h>
#include <unistd.h>
static const char prompt[] = "myshell> ";
static const char sep[] = " \t\n\r";
int main()
{
int ac; // arg count
char *av[10]; //argument vector
int tty = open("/dev/tty", O_RDWR); // open tty for read/write
int pid; // process id
int status; // child process exit status
int w;
void (*istat)(int), (*qstat)(int);
if (tty == -1)
{
fprintf(stderr, "can't open /dev/tty\n");
exit(EXIT_FAILURE);
}
while (1)
{
char *arg, line[256]; // buffer to hold line of input
int i;
// prompt and read
write(tty, prompt, sizeof(prompt) - 1);
i = read(tty, line, sizeof(line));
if (i <= 0)
break;
line[i] = '\0';
// tokenize the line into av[]
ac = 0;
for (arg = strtok(line, sep); arg && ac < 10; arg = strtok(NULL, sep))
av[ac++] = arg;
if (ac > 0 && strcmp(av[0], "exit") == 0)
break;
if ((pid = fork()) == 0)
{
// this is the forked child process that is a copy of the running program
dup2(tty, 0); // stdin from tty
dup2(tty, 1); // stdout to tty
dup2(tty, 2); // stderr to tty
close(tty);
// last argument must be NULL for execvp()
av[ac] = NULL;
// execute program av[0] with arguments av[0]... replacing this program
execvp(av[0], av);
fprintf(stderr, "can't execute %s\n", av[0]);
exit(EXIT_FAILURE);
}
close(tty);
// disable interrupt (^C and kill -TERM) and kill -QUIT
istat = signal(SIGINT, SIG_IGN);
qstat = signal(SIGQUIT, SIG_IGN);
// wait until forked child process terminated, get its exit status
while ((w = wait(&status)) != pid && w != -1)
continue;
if (w == -1)
status = -1;
}
// restore interrupt and quit signals
signal(SIGINT, istat);
signal(SIGQUIT, qstat);
exit(EXIT_SUCCESS);
}
I tried moving these lines right above exit success (so they're outside of the loop and inside main)
close(tty);
// disable interrupt (^C and kill -TERM) and kill -QUIT
istat = signal(SIGINT, SIG_IGN);
qstat = signal(SIGQUIT, SIG_IGN);
// wait until forked child process terminated, get its exit status
while ((w = wait(&status)) != pid && w != -1)
continue;
if (w == -1)
status = -1;
}
// restore interrupt and quit signals
signal(SIGINT, istat);
signal(SIGQUIT, qstat);
exit(EXIT_SUCCESS);
}
What this did is loop the shell properly, but printed the prompt over top the last line of the output from the executed command, because the program wasn't waiting for the child process to end before printing the prompt and waiting for input. I tried moving some of the lines around, but every time it just ends up terminating the shell after one command is executed
You do not want to call close(tty) within your main loop. Doing so causes your next read(tty,... to fail, exiting the shell.
Additionally, if you really want to disable SIGQUIT/SIGQUIT, you should symmetrically restore them within the loop.
the following posted code:
skips the handling of the signals, you may want to add that back in.
cleanly compiles
properly checks for and documents any errors
performs the desired functionality, except for the disabling of certain signals
continues to execute the loop until the user enters 'exit'
and now, the proposed code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
// added next statement for 'wait()' and 'waitpid()'
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
//#include <signal.h>
#include <unistd.h>
static const char prompt[] = "myshell> ";
static const char sep[] = " \t\n\r";
int main( void )
{
int ac; // arg count
char *av[10]; //argument vector
pid_t pid; // process id
int status; // child process exit status
int tty = open("/dev/tty", O_RDWR); // open tty for read/write
if (tty == -1)
{
perror( "open for /dev/tty failed");
exit(EXIT_FAILURE);
}
while (1)
{
char *arg;
char line[256]; // buffer to hold line of input
// prompt and read
write(tty, prompt, sizeof(prompt) - 1);
ssize_t i = read(tty, line, sizeof(line));
if (i == 0)
break;
if (i < 0)
{
perror( "read failed" );
exit( EXIT_FAILURE );
}
line[i] = '\0';
// tokenize the line into av[]
ac = 0;
for (arg = strtok(line, sep); arg && ac < 10; arg = strtok(NULL, sep))
av[ac++] = arg;
// last argument must be NULL for execvp()
av[ac] = NULL
if (ac > 0 && strcmp( av[0], "exit" ) == 0)
break;
pid = fork();
switch( pid )
{
case -1:
perror( "fork failed" );
exit( EXIT_FAILURE );
break;
case 0: // child process
// this is the forked child process that is a copy of the running program
dup2(tty, 0); // stdin from tty
dup2(tty, 1); // stdout to tty
dup2(tty, 2); // stderr to tty
close(tty);
;
// execute program av[0] with arguments av[0]... replacing this program
execvp(av[0], av);
perror( "execvp failed" );
exit(EXIT_FAILURE);
break;
default: // parent process
// wait until forked child process terminated, get its exit status
waitpid( pid, &status, 0 );
break;
}
}
return 0;
}

c - spawned a bash shell. Shell died but pipe not broken?

Problem
I'm trying to pipe contents from the main routine to a execvp'd bash shell. I'm encountering a problem where when I write "exit" into the subshell, it doesn't tell me that the pipe is really broken. It should be though - right? The process died and thus the pipe fd should also return an EOF or a SIGPIPE. It doesn't, however, and just keeps on reading/writing like normal.
Code
The code is attached here:
/************************************************************
* Includes:
* ioctl - useless(?)
* termios, tcsetattr, tcgetattr - are for setting the
* noncanonical, character-at-a-time terminal.
* fork, exec - creating the child process for part 2.
* pthread, pipe - creating the pipe process to communicate
* with the child shell.
* kill - to exit the process
* atexit - does some cleanups. Used in termios, tcsetattr,
* tcgetattr.
************************************************************/
#include <sys/ioctl.h> // ioctl
#include <termios.h> // termios, tcsetattr, tcgetattr
#include <unistd.h> // fork, exec, pipe
#include <sys/wait.h> // waitpid
#include <pthread.h> // pthread
#include <signal.h> // kill
#include <stdlib.h> // atexit
#include <stdio.h> // fprintf and other utility functions
#include <getopt.h> // getopt
/**********************
* GLOBALS
**********************/
pid_t pid;
/**********************
* CONSTANTS
**********************/
static const int BUFFER_SIZE = 16;
static const int STDIN_FD = 0;
static const int STDOUT_FD = 1;
static const int STDERR_FD = 2;
// these attributes are reverted to later
struct termios saved_attributes;
// to revert the saved attributes
void
reset_input_mode (void) {
tcsetattr (STDIN_FILENO, TCSANOW, &saved_attributes);
}
// to set the input mode to correct non-canonical mode.
void
set_input_mode (void) {
struct termios tattr;
/* Make sure stdin is a terminal. */
if (!isatty (STDIN_FILENO))
{
fprintf (stderr, "Not a terminal.\n");
exit (EXIT_FAILURE);
}
/* Save the terminal attributes so we can restore them later. */
tcgetattr (STDIN_FILENO, &saved_attributes);
atexit (reset_input_mode);
/* Set the funny terminal modes. */
tcgetattr (STDIN_FILENO, &tattr);
tattr.c_lflag &= ~(ICANON|ECHO); /* Clear ICANON and ECHO. */
tattr.c_cc[VMIN] = 1;
tattr.c_cc[VTIME] = 0;
tcsetattr (STDIN_FILENO, TCSAFLUSH, &tattr);
}
// pthread 1 will read from pipe_fd[0], which
// is really the child's pipe_fd[1](stdout).
// It then prints out the contents.
void* thread_read(void* arg){
int* pipe_fd = ((int *) arg);
int read_fd = pipe_fd[0];
int write_fd = pipe_fd[1];
char c;
while(1){
int bytes_read = read(read_fd, &c, 1);
if(bytes_read > 0){
putchar(c);
}
else{
close(read_fd);
close(write_fd);
fprintf(stdout, "The read broke.");
fflush(stdout);
break;
}
}
}
// pthread 2 will write to child_pipe_fd[1], which
// is really the child's stdin.
// but in addition to writing to child_pipe_fd[1],
// we must also print to stdout what our
// argument was into the terminal. (so pthread 2
// does extra).
void* thread_write(void* arg){
set_input_mode();
int* pipe_args = ((int *) arg);
int child_read_fd = pipe_args[0];
int child_write_fd = pipe_args[1];
int parent_read_fd = pipe_args[2];
int parent_write_fd = pipe_args[3];
char c;
while(1) {
int bytes_read = read(STDIN_FD, &c, 1);
write(child_write_fd, &c, bytes_read);
putchar(c);
if(c == 0x04){
// If an EOF has been detected, then
// we need to close the pipes.
close(child_write_fd);
close(child_read_fd);
close(parent_write_fd);
close(parent_read_fd);
kill(pid, SIGHUP);
break;
}
}
}
int main(int argc, char* argv[]) {
/***************************
* Getopt process here for --shell
**************************/
int child_pipe_fd[2];
int parent_pipe_fd[2];
pipe(child_pipe_fd);
pipe(parent_pipe_fd);
// We need to spawn a subshell.
pid = fork();
if(pid < 0){
perror("Forking was unsuccessful. Exiting");
exit(EXIT_FAILURE);
}
else if(pid == 0){ // is the child.
// We dup the fd and close the pipe.
close(0); // close stdin. child's pipe should read.
dup(child_pipe_fd[0]); // pipe_fd[0] is the read. Make read the stdin.
close(child_pipe_fd[0]);
close(1); // close stdout
dup(parent_pipe_fd[1]); // pipe_fd[1] is the write. Make write the stdout.
close(parent_pipe_fd[1]);
char* BASH[] = {"/bin/bash", NULL};
execvp(BASH[0], BASH);
}
else{ // is the parent
// We dup the fd and close the pipe.
//
// create 2 pthreads.
// pthread 1 will read from pipe_fd[0], which
// is really the child's pipe_fd[1](stdout).
// It then prints out the contents.
//
// pthread 2 will write to pipe_fd[1], which
// is really the child's pipe_fd[0](stdin)
// but in addition to writing to pipe_fd[1],
// we must also print to stdout what our
// argument was into the terminal. (so pthread 2
// does extra).
//
// We also need to take care of signal handling:
signal(SIGINT, sigint_handler);
/*signal(SIGPIPE, sigpipe_handler);*/
int write_args[] = {child_pipe_fd[0], child_pipe_fd[1],
parent_pipe_fd[0], parent_pipe_fd[1]};
pthread_t t[2];
pthread_create(t, NULL, thread_read, parent_pipe_fd);
pthread_create(t+1, NULL, thread_write, write_args);
pthread_join(t[0], NULL);
pthread_join(t[1], NULL);
int status;
if (waitpid(pid, &status, 0) == -1) {
perror("Waiting for child failed.");
exit(EXIT_FAILURE);
}
printf("Subshell exited with the error code %d", status);
exit(0);
}
return 0;
}
The program basically pipes inputs from the terminal into the subshell and tries to execute them and return the outputs. To write to the pipe, I have a pthread that writes the stdin inputs into the subshell. To read to the pipe, I have a pthread that reads the pipe to the parent. To detect the broken pipe via the subshell dying(calling exit), I detect the EOF character from the read thread.
My attempts
I added a check for the 0x04 character(EOF), I checked for read_bytes == 0 or read_bytes < 0. It seems that it never gets the memo unless I explicitly close the pipes on the writing end. It only meets the EOF character if I send the character ^D(which, in my code, handles via closing all pipes of the child & parent).
Any comments would be appreciated! Thank you.
Your parent process is holding copies of the child's file descriptors. Thus, even after the child has exited, those FDs are still open -- so the other ends of those pipelines remain open as well, preventing any SIGPIPE.
Modify your code as follows:
else {
// pid >0; this is the parent
close(child_pipe_fd[0]); // ADD THIS LINE
close(parent_pipe_fd[1]); // ADD THIS LINE

Communicate with child process stdout/stdin

I am trying to communicate with a process (that itself writes to stdin and stdout to interact in a terminal with a user) and read it's stdin and write to it's stdout in C.
Hence I try to substitute a shell user programmatically. A methapohrical example: Imagine I want to use VIM in C for some reason. Then I also need to write commands (stdout) and read stuff from the editor (stdin).
Initially I thought this might be a trivial task, but it seems like there's no standard approach. int system(const char *command); just executes a command and sets the commands stdin/stdout to the one of the calling process.
Because this leads nowhere, I looked at FILE *popen(const char *command, const char *type); but the manual pages state that:
Since a pipe is by definition unidirectional, the type argument may specify only reading or writing, not both; the resulting stream is correspondingly read-only or write-only.
and its implication:
The return value from popen() is a normal standard I/O stream in all respects save that it must be closed with pclose() rather than fclose(3). Writing to such a stream writes to the standard input
of the command; the command's standard output is the same as that of the process that called popen(), unless this is altered by the command itself. Conversely, reading from a "popened" stream reads
the command's standard output, and the command's standard input is the same as that of the process that called popen().
Hence it wouldn't be completely impossible to use popen(), but it appears to me very inelegant, because I would have to parse the stdout of the calling process (the code that called popen()) in order to parse data sent from the popened command (when using popen type 'w').
Conversely, when popen is called with type 'r', I would need to write to the calling's process stdin, in order to write data to the popened command. It's not even clear to me whether both these processes receive the same data in the stdin in this case...
I just need to control stdin and stdout of a program. I mean can't there be a function like:
stdin_of_process, stdout_of_process = real_popen("/path/to/bin", "rw")
// write some data to the process stdin
write("hello", stdin_of_process)
// read the response of the process
read(stdout_of_process)
So my first question: What is the best way to implement the upper functionality?
Currently I am trying the following approach to communicate with another process:
Set up two pipes with int pipe(int fildes[2]);. One pipe to read the stdout of the process, the other pipe to write to the stdin of the process.
Fork.
Execute the process that I want to communicate with in the forked child process using int execvp(const char *file, char *const argv[]);.
Communicate with the child using the two pipes in the original process.
That's easy said bot not so trivially implemented (At least for me). I oddly managed to do so in one case, but when I tried to understand what I am doing with a simpler example, I fail. Here is my current problem:
I have two programs. The first just writes a incremented number every 100 ms to it's stdout:
#include <unistd.h>
#include <time.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
void sleepMs(uint32_t ms) {
struct timespec ts;
ts.tv_sec = 0 + (ms / 1000);
ts.tv_nsec = 1000 * 1000 * (ms % 1000);
nanosleep(&ts, NULL);
}
int main(int argc, char *argv[]) {
long int cnt = 0;
char buf[0x10] = {0};
while (1) {
sleepMs(100);
sprintf(buf, "%ld\n", ++cnt);
if (write(STDOUT_FILENO, buf, strlen(buf)) == -1)
perror("write");
}
}
Now the second program is supposed to read the stdout of the first program (Please keep in my mind that I eventually want to read AND write with a process, so a technical correct solution to use popen() for the upper use case might be right in this specific case, because I simplified my experiments to just capture the stdout of the bottom program). I expect from the bottom program that it reads whatever data the upper program writes to stdout. But it does not read anything. Where could be the reason? (second question).
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdint.h>
#include <time.h>
void sleepMs(uint32_t ms) {
struct timespec ts;
ts.tv_sec = 0 + (ms / 1000);
ts.tv_nsec = 1000 * 1000 * (ms % 1000);
nanosleep(&ts, NULL);
}
int main() {
int pipe_fds[2];
int n;
char buf[0x100] = {0};
pid_t pid;
pipe(pipe_fds);
char *cmd[] = {"/path/to/program/above", NULL};
if ((pid = fork()) == 0) { /* child */
dup2(pipe_fds[1], 1); // set stdout of the process to the write end of the pipe
execvp(cmd[0], cmd); // execute the program.
fflush(stdout);
perror(cmd[0]); // only reached in case of error
exit(0);
} else if (pid == -1) { /* failed */
perror("fork");
exit(1);
} else { /* parent */
while (1) {
sleepMs(500); // Wait a bit to let the child program run a little
printf("Trying to read\n");
if ((n = read(pipe_fds[0], buf, 0x100)) >= 0) { // Try to read stdout of the child process from the read end of the pipe
buf[n] = 0; /* terminate the string */
fprintf(stderr, "Got: %s", buf); // this should print "1 2 3 4 5 6 7 8 9 10 ..."
} else {
fprintf(stderr, "read failed\n");
perror("read");
}
}
}
}
Here is a (C++11-flavored) complete example:
//
// Example of communication with a subprocess via stdin/stdout
// Author: Konstantin Tretyakov
// License: MIT
//
#include <ext/stdio_filebuf.h> // NB: Specific to libstdc++
#include <sys/wait.h>
#include <unistd.h>
#include <iostream>
#include <memory>
#include <exception>
// Wrapping pipe in a class makes sure they are closed when we leave scope
class cpipe {
private:
int fd[2];
public:
const inline int read_fd() const { return fd[0]; }
const inline int write_fd() const { return fd[1]; }
cpipe() { if (pipe(fd)) throw std::runtime_error("Failed to create pipe"); }
void close() { ::close(fd[0]); ::close(fd[1]); }
~cpipe() { close(); }
};
//
// Usage:
// spawn s(argv)
// s.stdin << ...
// s.stdout >> ...
// s.send_eol()
// s.wait()
//
class spawn {
private:
cpipe write_pipe;
cpipe read_pipe;
public:
int child_pid = -1;
std::unique_ptr<__gnu_cxx::stdio_filebuf<char> > write_buf = NULL;
std::unique_ptr<__gnu_cxx::stdio_filebuf<char> > read_buf = NULL;
std::ostream stdin;
std::istream stdout;
spawn(const char* const argv[], bool with_path = false, const char* const envp[] = 0): stdin(NULL), stdout(NULL) {
child_pid = fork();
if (child_pid == -1) throw std::runtime_error("Failed to start child process");
if (child_pid == 0) { // In child process
dup2(write_pipe.read_fd(), STDIN_FILENO);
dup2(read_pipe.write_fd(), STDOUT_FILENO);
write_pipe.close(); read_pipe.close();
int result;
if (with_path) {
if (envp != 0) result = execvpe(argv[0], const_cast<char* const*>(argv), const_cast<char* const*>(envp));
else result = execvp(argv[0], const_cast<char* const*>(argv));
}
else {
if (envp != 0) result = execve(argv[0], const_cast<char* const*>(argv), const_cast<char* const*>(envp));
else result = execv(argv[0], const_cast<char* const*>(argv));
}
if (result == -1) {
// Note: no point writing to stdout here, it has been redirected
std::cerr << "Error: Failed to launch program" << std::endl;
exit(1);
}
}
else {
close(write_pipe.read_fd());
close(read_pipe.write_fd());
write_buf = std::unique_ptr<__gnu_cxx::stdio_filebuf<char> >(new __gnu_cxx::stdio_filebuf<char>(write_pipe.write_fd(), std::ios::out));
read_buf = std::unique_ptr<__gnu_cxx::stdio_filebuf<char> >(new __gnu_cxx::stdio_filebuf<char>(read_pipe.read_fd(), std::ios::in));
stdin.rdbuf(write_buf.get());
stdout.rdbuf(read_buf.get());
}
}
void send_eof() { write_buf->close(); }
int wait() {
int status;
waitpid(child_pid, &status, 0);
return status;
}
};
// ---------------- Usage example -------------------- //
#include <string>
using std::string;
using std::getline;
using std::cout;
using std::endl;
int main() {
const char* const argv[] = {"/bin/cat", (const char*)0};
spawn cat(argv);
cat.stdin << "Hello" << std::endl;
string s;
getline(cat.stdout, s);
cout << "Read from program: '" << s << "'" << endl;
cat.send_eof();
cout << "Waiting to terminate..." << endl;
cout << "Status: " << cat.wait() << endl;
return 0;
}
For many practical purposes, however, the Expect library could probably be a good choice (check out the code in the example subdirectory of its source distribution).
You've got the right idea, and I don't have time to analyze all of your code to point out the specific problem, but I do want to point out a few things that you may have overlooked on how programs and terminals work.
The idea of a terminal as a "file" is naivé. Programs like vi use a library (ncurses) to send special control characters (and change terminal device driver settings). For example, vi puts the terminal device driver itself into a mode where it can read a character at a time, among other things.
It is very non-trivial to "control" a program like vi this way.
On your simplified experiment...
Your buffer is one byte too small. Also, be aware IO is sometimes line buffered. So, you might try making sure the newline is getting transferred (use printf instead of sprintf/strlen/write...you hooked the stdout up to your pipe already), otherwise you might not see data until a newline is hit. I don't remember pipe being line buffered, but it is worth a shot.

How to pipe stdin to a child and execl cat in C

In the code below, I am simply trying to send a file via stdin to a child process which will exec the cat OS command. The code compiles fine. Here is how I call it from the command line:
$ ./uniquify < words.txt
However, when I run it I get a seg fault error. I am really having a hard time understanding how the flow if information is supposed to work through pipes to children. I am trying to make the code as simple as possible, so I can understand it, but it is not yet making sense. Any help would be appreciated.
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#define NUM_CHILDREN 2
int main(int argc, char *argv[])
{
pid_t catPid;
int writeFds[NUM_CHILDREN];
int catFds[2];
int c = 0;
FILE *writeToChildren[NUM_CHILDREN];
//create a pipe
(void) pipe(catFds);
if ((catPid = fork()) < 0) {
perror("cat fork failed");
exit(1);
}
//this is the child case
if (catPid == 0) {
//close the write end of the pipe
close(catFds[1]);
//close stdin?
close(0);
//duplicate the read side of the pipe
dup(catFds[0]);
//exec cat
execl("/bin/cat", "cat", (char *) 0);
perror("***** exec of cat failed");
exit(20);
}
else { //this is the parent case
//close the read end of the pipe
close(catFds[0]);
int p[2];
//create a pipe
pipe(p);
writeToChildren[c] = fdopen(p[1], "w");
} //only the the parent continues from here
//close file descriptor so the cat child can exit
close(catFds[1]);
char words[NUM_CHILDREN][50];
//read through the input file two words at a time
while (fscanf(stdin, "%s %s", words[0], words[1]) != EOF) {
//loop twice passing one of the words to each rev child
for (c = 0; c < NUM_CHILDREN; c++) {
fprintf(writeToChildren[c], "%s\n", words[c]);
}
}
//close all FILEs and fds by sending and EOF
for (c = 0; c < NUM_CHILDREN; c++) {
fclose(writeToChildren[c]);
close(writeFds[c]);
}
int status = 0;
//wait on all children
for (c = 0; c < (NUM_CHILDREN + 1); c++) {
wait(&status);
}
return 0;
}
Since your question seems to be about understanding how pipes and forks work, I hope below programs can help you. Please notice that this is for illustration only. It wouldn't qualify for commercial implementation, but I wanted to keep it short!
You can compile the two programs as follows:
cc pipechild.c -o pipechild
cc pipeparent.c -o pipeparent
Then execute with ./pipeparent
pipeparent.c source
/* pipeparent.c */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#define MESSAGE "HELLO!\n"
#define INBUFSIZE 80
#define RD 0 // Read end of pipe
#define WR 1 // Write end of pipe
int main(void)
{
int ptocpipe[2]; // Parent-to-child pipe
int ctoppipe[2]; // Chile-to-parent pipe
pid_t childpid; // Process ID of child
char inbuf[80]; // Input from child
int rd; // read() return
int rdup; // dup():ed stdin for child
int wdup; // dup():ed stdout for child
char *eol; // End of line
// Create pipe for writing to child
if (pipe(ptocpipe) < 0) {
fprintf(stderr, "pipe(ptocpipe) failed!\n");
return 2;
}
// Create pipe for writing back to parent
if (pipe(ctoppipe) < 0) {
fprintf(stderr, "pipe(ctoppipe) failed!\n");
return 2;
}
// Verify that one of the pipes are working by filling it first
// in one end and then reading it from the other. The OS will
// buffer the contents for us. Note, this is not at all necessary,
// it's just to illustrate how it works!
write(ptocpipe[WR], MESSAGE, strlen(MESSAGE));
read(ptocpipe[RD], inbuf, INBUFSIZE);
if (strlen(inbuf) != strlen(MESSAGE)) {
fprintf(stderr, "Failed to flush the toilet!\n");
return 6;
} else {
printf("Wrote to myself: %s", inbuf);
}
// Next, we want to launch some interactive program which
// replies with exactly one line to each line we send to it,
// until it gets tired and returns EOF to us.
// First, we must clone ourselves by using fork(). Then the
// child process must be replaced by the interactive program.
// Problem is: How do we cheat the program to read its stdin
// from us, and send its stdout back to us?
switch (childpid = fork()) {
case -1: // Error
fprintf(stderr, "Parent: fork() failed!\n");
return 3;
case 0: // Child process
// Close the ends we don't need. If not, we might
// write back to ourselves!
close(ptocpipe[WR]);
close(ctoppipe[RD]);
// Close stdin
close(0);
// Create a "new stdin", which WILL be 0 (zero)
if ((rdup = dup(ptocpipe[RD])) < 0) {
fprintf(stderr, "Failed dup(stdin)\n");
return 4;
}
// Close stdout
close(1);
// Create a "new stdout", which WILL be 1 (one)
if ((wdup = dup(ctoppipe[WR])) < 0) {
fprintf(stderr, "Failed dup(stdout)\n");
return 5;
}
// For debugging, verify stdin and stdout
fprintf(stderr, "rdup: %d, wdup %d\n", rdup, wdup);
// Overload current process by the interactive
// child process which we want to execute.
execlp("./pipechild", "pipechild", (char *) NULL);
// Getting here means we failed to launch the child
fprintf(stderr, "Parent: execl() failed!\n");
return 4;
}
// This code is executed by the parent only!
// Close the ends we don't need, to avoid writing back to ourself
close(ptocpipe[RD]);
close(ctoppipe[WR]);
// Write one line to the child and expect a reply, or EOF.
do {
write(ptocpipe[WR], MESSAGE, strlen(MESSAGE));
if ((rd = read(ctoppipe[RD], inbuf, INBUFSIZE)) > 0) {
// Chop off ending EOL
if ((eol = rindex(inbuf, '\n')) != NULL)
*eol = '\0';
printf("Parent: Read \"%s\" from child.\n", inbuf);
}
} while (rd > 0);
fprintf(stderr, "Parent: Child done!\n");
return 0;
}
pipechild.c source
/* pipechild.c
* Note - This is only for illustration purpose!
* To be stable, we should catch/ignore signals,
* and use select() to read.
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <strings.h>
#include <string.h>
#define MAXCOUNT 5 // Maximum input lines toread
#define INBUFSIZE 80 // Buffer size
int main(void)
{
char buff[INBUFSIZE];
int remains = MAXCOUNT;
pid_t mypid;
char *eol;
mypid = getpid(); // Process-ID
fprintf(stderr, "Child %d: Started!\n", mypid);
// For each line read, write one tostdout.
while (fgets(buff, INBUFSIZE, stdin) && remains--) {
// Chop off ending EOL
if ((eol = rindex(buff, '\n')) != NULL)
*eol = '\0';
// Debug to console
fprintf(stderr, "Child %d: I got %s. %d remains.\n",
mypid, buff, 1 + remains);
// Reply to parent
sprintf(buff, "Child %d: %d remains\n", mypid, 1 + remains);
write(1, buff, strlen(buff));
}
fprintf(stderr, "Child %d: I'm done!\n", mypid);
return 0;
}

Resources