41

Is it possible to read and write to a file descriptor returned by popen. I have an interactive process I'd like to control through C. If this isn't possible with popen, is there any way around it?

Lee
  • 411
  • 1
  • 4
  • 3

8 Answers8

42

As already answered, popen works in one direction. If you need to read and write, You can create a pipe with pipe(), span a new process by fork() and exec functions and then redirect its input and outputs with dup2(). Anyway I prefer exec over popen, as it gives you better control over the process (e.g. you know its pid)

EDITED:

As comments suggested, a pipe can be used in one direction only. Therefore you have to create separate pipes for reading and writing. Since the example posted before was wrong, I deleted it and created a new, correct one:

#include<unistd.h>
#include<sys/wait.h>
#include<sys/prctl.h>
#include<signal.h>
#include<stdlib.h>
#include<string.h>
#include<stdio.h>

int main(int argc, char** argv)
{
  pid_t pid = 0;
  int inpipefd[2];
  int outpipefd[2];
  char buf[256];
  char msg[256];
  int status;

  pipe(inpipefd);
  pipe(outpipefd);
  pid = fork();
  if (pid == 0)
  {
    // Child
    dup2(outpipefd[0], STDIN_FILENO);
    dup2(inpipefd[1], STDOUT_FILENO);
    dup2(inpipefd[1], STDERR_FILENO);

    //ask kernel to deliver SIGTERM in case the parent dies
    prctl(PR_SET_PDEATHSIG, SIGTERM);

    //close unused pipe ends
    close(outpipefd[1]);
    close(inpipefd[0]);

    //replace tee with your process
    execl("/usr/bin/tee", "tee", (char*) NULL);
    // Nothing below this line should be executed by child process. If so, 
    // it means that the execl function wasn't successfull, so lets exit:
    exit(1);
  }
  // The code below will be executed only by parent. You can write and read
  // from the child using pipefd descriptors, and you can send signals to 
  // the process using its pid by kill() function. If the child process will
  // exit unexpectedly, the parent process will obtain SIGCHLD signal that
  // can be handled (e.g. you can respawn the child process).

  //close unused pipe ends
  close(outpipefd[0]);
  close(inpipefd[1]);

  // Now, you can write to outpipefd[1] and read from inpipefd[0] :  
  while(1)
  {
    printf("Enter message to send\n");
    scanf("%s", msg);
    if(strcmp(msg, "exit") == 0) break;

    write(outpipefd[1], msg, strlen(msg));
    read(inpipefd[0], buf, 256);

    printf("Received answer: %s\n", buf);
  }

  kill(pid, SIGKILL); //send SIGKILL signal to the child process
  waitpid(pid, &status, 0);
}
FliegendeWurst
  • 176
  • 4
  • 9
Patryk
  • 1,421
  • 8
  • 21
  • 8
    Doesn't this make the process' stdout write to the process stdin? Potentially causing an infinite loop? I'm not an expert but I was looking into this and it seems as if you would need 2 pipes. – Aktau Apr 14 '13 at 11:05
  • 2
    write and read are inverted, write(pipefd[1]), read(pipefd[0]) – LemonCool Mar 16 '15 at 00:40
  • 2
    pipe() creates a unidirectional data channel. Therefore, you will have to create two pipes or read and written data may mix up. – Meixner Mar 31 '17 at 09:43
  • 1
    This code is certainly wrong. You cannot read from pipefd[1] and you cannot write into pipefd[0]. Pipe is a SINGLE channel with two ends. You need two pipes to implement this. – user2808671 Nov 21 '17 at 09:14
  • prctl call is useless in this case because the execl call (front-end of execve) clears the PR_SET_PDEATHSIG flag – jap jap Oct 26 '21 at 10:28
7

You want something often called popen2. Here's a basic implementation without error checking (found by a web search, not my code):

// http://media.unpythonic.net/emergent-files/01108826729/popen2.c

#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>

#include "popen2.h"

int popen2(const char *cmdline, struct popen2 *childinfo) {
    pid_t p;
    int pipe_stdin[2], pipe_stdout[2];

    if(pipe(pipe_stdin)) return -1;
    if(pipe(pipe_stdout)) return -1;

    //printf("pipe_stdin[0] = %d, pipe_stdin[1] = %d\n", pipe_stdin[0], pipe_stdin[1]);
    //printf("pipe_stdout[0] = %d, pipe_stdout[1] = %d\n", pipe_stdout[0], pipe_stdout[1]);

    p = fork();
    if(p < 0) return p; /* Fork failed */
    if(p == 0) { /* child */
        close(pipe_stdin[1]);
        dup2(pipe_stdin[0], 0);
        close(pipe_stdout[0]);
        dup2(pipe_stdout[1], 1);
        execl("/bin/sh", "sh", "-c", cmdline, NULL);
        perror("execl"); exit(99);
    }
    childinfo->child_pid = p;
    childinfo->to_child = pipe_stdin[1];
    childinfo->from_child = pipe_stdout[0];
    close(pipe_stdin[0]);
    close(pipe_stdout[1]);
    return 0; 
}

//#define TESTING
#ifdef TESTING
int main(void) {
    char buf[1000];
    struct popen2 kid;
    popen2("tr a-z A-Z", &kid);
    write(kid.to_child, "testing\n", 8);
    close(kid.to_child);
    memset(buf, 0, 1000);
    read(kid.from_child, buf, 1000);
    printf("kill(%d, 0) -> %d\n", kid.child_pid, kill(kid.child_pid, 0)); 
    printf("from child: %s", buf); 
    printf("waitpid() -> %d\n", waitpid(kid.child_pid, NULL, 0));
    printf("kill(%d, 0) -> %d\n", kid.child_pid, kill(kid.child_pid, 0)); 
    return 0;
}
#endif
Mr Fooz
  • 109,094
  • 6
  • 73
  • 101
  • 2
    @ggorlen Thanks. I've tracked down another implementation and inlined it as well. – Mr Fooz May 04 '20 at 16:51
  • This link seems better: http://media.unpythonic.net/emergent-files/01108826729/popen2.c The implementation you copied/paste contains strange calls to `close(pipe_stdin[0]);` and `close(pipe_stdout[1]);` – Kiruahxh May 31 '21 at 10:09
  • It looks like close(pipe_stdin[0]); and close(pipe_stdout[1]); are necessary. – fchen Oct 17 '22 at 15:33
6

The reason popen() and friends don't offer bidirectional communication is that it would be deadlock-prone, due to buffering in the subprocess. All the makeshift pipework and socketpair() solutions discussed in the answers suffer from the same problem.

Under UNIX, most commands cannot be trusted to read one line and immediately process it and print it, except if their standard output is a tty. The reason is that stdio buffers output in userspace by default, and defers the write() system call until either the buffer is full or the stdio stream is closed (typically because the program or script is about to exit after having seen EOF on input). If you write to such a program's stdin through a pipe, and now wait for an answer from that program's stdout (without closing the ingress pipe), the answer is stuck in the stdio buffers and will never come out - This is a deadlock.

You can trick some line-oriented programs (eg grep) into not buffering by using a pseudo-tty to talk to them; take a look at libexpect(3). But in the general case, you would have to re-run a different subprocess for each message, allowing to use EOF to signal the end of each message and cause whatever buffers in the command (or pipeline of commands) to be flushed. Obviously not a good thing performance-wise.

See more info about this problem in the perlipc man page (it's for bi-directional pipes in Perl but the buffering considerations apply regardless of the language used for the main program).

DomQ
  • 4,184
  • 38
  • 37
  • If you `read()` and `write()` after checking the file descriptors with `poll()`, I don't see why you would deadlock. It's true that it requires some buffering on your side, as you are sometimes require to read even when you still have data to write, but that doesn't sound like a problem to me. – Pavel Šimerda Feb 23 '17 at 17:07
  • @PavelŠimerda you would deadlock because the data is in the subprocess' user space, not in some pipe buffer. – DomQ Feb 24 '17 at 17:34
  • 1
    That doesn't explain it and doesn't sound credible, see my answer. – Pavel Šimerda Feb 28 '17 at 12:01
  • @PavelŠimerda Have you taken the time to read the references I quote? – DomQ Mar 07 '17 at 09:02
4

popen() can only open the pipe in read or write mode, not both. Take a look at this thread for a workaround.

rid
  • 61,078
  • 31
  • 152
  • 193
3

In one of netresolve backends I'm talking to a script and therefore I need to write to its stdin and read from its stdout. The following function executes a command with stdin and stdout redirected to a pipe. You can use it and adapt it to your liking.

static bool
start_subprocess(char *const command[], int *pid, int *infd, int *outfd)
{
    int p1[2], p2[2];

    if (!pid || !infd || !outfd)
        return false;

    if (pipe(p1) == -1)
        goto err_pipe1;
    if (pipe(p2) == -1)
        goto err_pipe2;
    if ((*pid = fork()) == -1)
        goto err_fork;

    if (*pid) {
        /* Parent process. */
        *infd = p1[1];
        *outfd = p2[0];
        close(p1[0]);
        close(p2[1]);
        return true;
    } else {
        /* Child process. */
        dup2(p1[0], 0);
        dup2(p2[1], 1);
        close(p1[0]);
        close(p1[1]);
        close(p2[0]);
        close(p2[1]);
        execvp(*command, command);
        /* Error occured. */
        fprintf(stderr, "error running %s: %s", *command, strerror(errno));
        abort();
    }

err_fork:
    close(p2[1]);
    close(p2[0]);
err_pipe2:
    close(p1[1]);
    close(p1[0]);
err_pipe1:
    return false;
}

https://github.com/crossdistro/netresolve/blob/master/backends/exec.c#L46

(I used the same code in Can popen() make bidirectional pipes like pipe() + fork()?)

Community
  • 1
  • 1
Pavel Šimerda
  • 5,783
  • 1
  • 31
  • 31
1

Use forkpty (it's non-standard, but the API is very nice, and you can always drop in your own implementation if you don't have it) and exec the program you want to communicate with in the child process.

Alternatively, if tty semantics aren't to your liking, you could write something like forkpty but using two pipes, one for each direction of communication, or using socketpair to communicate with the external program over a unix socket.

R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711
0

You can't use popen to use two-way pipes.

In fact, some OSs don't support two-way pipes, in which case a socket-pair (socketpair) is the only way to do it.

C. K. Young
  • 219,335
  • 46
  • 382
  • 435
-1

popen works for me in both directions (read and write) I have been using a popen() pipe in both directions..

Reading and writing a child process stdin and stdout with the file descriptor returned by popen(command,"w")

It seems to work fine..

I assumed it would work before I knew better, and it does. According posts above this shouldn't work.. which worries me a little bit.

gcc on raspbian (raspbery pi debian)

Robert
  • 5,278
  • 43
  • 65
  • 115