cprocesspipeio-redirectiondup2

Problem with redirecting output and input streams between processes


I'm writing a pipe analogue in c. It crashes when there is more than one '|' and the other processes have to read the output of the previous one.

#define _GNU_SOURCE
#include <stdint.h>
#include <string.h>
#include <malloc.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <fcntl.h>

enum {
  MAX_ARGS_COUNT = 256,
  MAX_CHAIN_LINKS_COUNT = 256
};

pid_t pid_arr[MAX_CHAIN_LINKS_COUNT];

typedef struct {
  char *command;
  uint64_t argc;
  char *argv[MAX_ARGS_COUNT];
} chain_link_t;

typedef struct {
  uint64_t chain_links_count;
  chain_link_t chain_links[MAX_CHAIN_LINKS_COUNT];
} chain_t;

void parse_commands(char *command, chain_link_t *link) {
  char *tmp = command;
  char *tmp2 = command;
  link->argc = 1;
  while ((tmp = strchr(tmp, ' '))) {
    link->argv[link->argc - 1] = strndup(tmp2, tmp - tmp2);
    link->argc++;
    tmp2 = ++tmp;
  }
  if (strlen(tmp2) > 0) {
    link->argv[link->argc - 1] = strdup(tmp2);
  }
  link->command = link->argv[0];
}

void create_chain(char *command, chain_t *chain) {
  char *tmp = command;
  char *tmp2 = command;
  chain->chain_links_count = 0;
  while ((tmp = strchr(tmp, '|'))) {
    if (tmp[0] == ' ') {
      ++tmp;
    }
    parse_commands(strndup(tmp2, tmp - tmp2 - 1), &(chain->chain_links[chain->chain_links_count]));
    tmp = tmp2 = tmp + 2;
    ++chain->chain_links_count;
  }
  if (strlen(tmp2) > 0) {
    if (tmp2[0] == ' ') {
      ++tmp2;
    }
    parse_commands(strdup(tmp2), &(chain->chain_links[chain->chain_links_count]));
    ++chain->chain_links_count;
  }
}

void run_chain(chain_t *chain) {
  size_t size = chain->chain_links_count - 1;
  if (chain->chain_links_count == 0) {
    fprintf(stderr, "No commands provided.\n");
    return;
  }
  int pipes[chain->chain_links_count][2];

  for (int i = 0; i < chain->chain_links_count - 1; ++i) {
    if (pipe(pipes[i]) == -1) {
      perror("pipe");
      exit(EXIT_FAILURE);
    }
  }

  for (int i = 0; i < chain->chain_links_count; ++i) {
    pid_t pid = fork();
    if (pid == -1) {
      perror("fork");
      exit(EXIT_FAILURE);
    } else if (pid == 0) {  // Child process
      if (i > 0) {
        dup2(pipes[i - 1][0], STDIN_FILENO);
        close(pipes[i - 1][0]);
        close(pipes[i - 1][1]);
      }
      if (i < chain->chain_links_count - 1) {
        dup2(pipes[i][1], STDOUT_FILENO);
        close(pipes[i][0]);
        close(pipes[i][1]);
      }
      execvp(chain->chain_links[i].command, chain->chain_links[i].argv);
      perror("execvp");
      exit(EXIT_FAILURE);
    }
  }
  for (int i = 0; i < chain->chain_links_count - 1; ++i) {
    close(pipes[i][0]);
    close(pipes[i][1]);
  }
  for (int i = 0; i < chain->chain_links_count; ++i) {
    wait(NULL);
  }
}

int main(int argc, char *argv[]) {
  chain_t chain;
  create_chain(argv[1], &chain);
  run_chain(&chain);
  return 0;
}

That is Tests of such type as "pwd", "ls", "ls -l", "ls -la", "ls -la | wc -c", "pwd | cut -d/ -f2", "ls -la | wc | echo "1" | echo "1" | echo "1" | echo "1" | echo "1" | echo "1" | echo "1"" it works successfully, but in the test "ls -la | wc | wc -c" it hangs on the last process. I assume that the input for the last wc is empty, that's why it hangs. What could be the problem?


Solution

  • The problem I see is each and every end of the pipes are closed before execution. The code works fine for a single pipe because it writes and reads from standart io fds. As you can see in this [chart][1], some ends should remain open to be read from.

        (...)
        else if (!pid)
        {
            close(pipes[i - 1][1]);
            close(pipes[i][0]);
            dup2(pipes[i][1], STDOUT_FILENO);
            //exec
        }
        else
        {
            //revert the upper one
        }
    

    And I'm not sure of creating all the pipeline at once. You may need to close all the other pipe ends than the two ends you need, since they all are open. [1]: https://i.sstatic.net/u17DR.png