Search code examples
cforkfile-descriptordup2

Comunication to from child process hanging on read in C


I'm trying to communicate with an external program which, if executed, will run a terminal interface. Normally I'll have to provide some inputs (e.g. "1+1") and then read the output of the program (e.g. "2"). Since I need a two-way communication I wasn't able to use popen().

My problem is the following:

Whenever I have a part of the code that asks for inputs, for example containing std::cin >> input I run into the same issue, the read command never exits.

Here I wrote a minimal example, all the child process does is reading the input and repeating it.

When I try to run this code what happens is that I see the first print "Parent says:" and I can provide the input and send it using write. However, when I try to call again the read() function the see the outcome, it never exit.

I've noticed that if I close the pipe that goes from the parent to the child (fd_p2c[1]), then I can read successfully. This is clearly not what I want, since in my application I'd like to keep both communications open.

Any suggestions on what could be done to fix this problem?

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

int main() {
  int status, buf_length;

  // Input and output
  char buf[256];
  char msg[256];
  char child_read[256];

  int fd_c2p[2]; // file descriptor pipe child -> parent
  int fd_p2c[2]; // file descriptor pipe parent -> child

  pipe(fd_c2p);
  pipe(fd_p2c);

  // Spawn a new process with pid
  pid_t pid = fork(); // Fork process

  if (pid == 0) {
    // Child

    // Close the unused end of the pipe
    if (close(fd_p2c[1]) != 0 || close(fd_c2p[0]) != 0) {
      fprintf(stderr, "Faild to close unused end of pipe\n");
      exit(1);
    }

    // Set the comunication
    if (dup2(fd_p2c[0], STDIN_FILENO) != 0 ||
        dup2(fd_c2p[1], STDOUT_FILENO) != 1 ||
        dup2(fd_c2p[1], STDERR_FILENO) != 2) {
      fprintf(stderr, "Faild to duplicate the end of the pipes\n");
      exit(1);
    }

    // These two pipe ends are not needed anymore
    if (close(fd_p2c[0]) != 0 || close(fd_c2p[1]) != 0) {
      fprintf(stderr, "Faild to close unused end of pipe\n");
      exit(1);
    }

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

    // Moch program
    while (1) {
      fprintf(stdout, "Parent says: ");
      fflush(stdout);
      scanf("%s", child_read);
      fprintf(stdout, " >> Child repeat: %s\n", child_read);
      fflush(stdout);
    }
    exit(1);
  } else {
    // Parent

    // These two pipe ends are not needed anymore
    if (close(fd_p2c[0]) != 0 || close(fd_c2p[1]) != 0) {
      fprintf(stderr, "Faild to close unused end of pipe\n");
      exit(1);
    }
  }

  // Read output and send input
  while (1) {
    // Read from child
    while (buf_length = read(fd_c2p[0], buf, sizeof(buf) - 1)) {
      buf[buf_length] = '\0';
      printf("%s", buf);
    }

    // Enter message to send
    scanf("%s", msg);
    if (strcmp(msg, "exit") == 0)
      break;

    // Send to child
    write(fd_p2c[1], msg, strlen(msg));
    //close(fd_p2c[1]);
  }

  printf("KILL");
  kill(pid, SIGKILL); // send SIGKILL signal to the child process
  waitpid(pid, &status, 0);
}

Solution

  • One problem is in the child-process with:

    scanf("%s", child_read);
    

    With the %s format there's only three things that will stop scanf from waiting for more input:

    1. Error
    2. End of file
    3. Space

    Assuming nothing goes wrong, there will be no errors. And since the parent process keeps the pipe open there will be no end of file. And since the parent process writes only what it itself reads with scanf("%s", ...) there will be no spaces in the data sent.

    All in all, the child process will wait indefinitely for scanf to return, which it never will.