LinuxのC言語でCLIの子プロセスを起動して標準入出力を使う

なんだかCLIの便利なプログラムがあって、でも一般人にシリアルコンソールをつかわせるわけにはいかないのでGUIのWrapperを作る必要が出てきた。こういうことよくありますよね。ないですか?

で、そんなのすぐ作れるはずだろと思って調べたんだけど出てこなかったんですよね。まず似たようなものに、標準Cライブラリにpopenという関数があって、こいつは子プロセスを実行して、その結果をFILE *で読み取れる超便利なやつ。手元にMacがあったので、「LinuxでもMacOSでも一緒だろ」と思ってMacOSで調べたら、popenに"r+"を指定すればFILE*に書き込めると。

macOSのpopenのman

macOSのpopenのman。"‘r+’ for reading and writing"とある

 

でもLinuxのpopenにはこれがなかったんです。

いろいろ調べて試行錯誤した結果、dup2とpipe2をつかってふにふにすればできるようでした。

 

void popen2(char *cmd, char *arg1, FILE **infp, FILE **outfp)
{
  static int infd[2];
  static int outfd[2];
  *infp = NULL;
  *outfp = NULL;

  if (pipe2(infd, O_NONBLOCK) == -1) {
    return; // pipe failed
  }
  if (pipe2(outfd, 0) == -1) {
    return; // pipe failed
  }
  int fork_result = fork();

  if (fork_result == -1) {
    printf("fork error %s\n", errno);
    return;
  } else if (fork_result == 0) { // 新しい方のプロセス
    // 入力される側(こちらにとってはSTDOUT)をつなぐ
    close(infd[0]);
    close(STDOUT_FILENO);
    dup2(infd[1], STDOUT_FILENO);
    // 出力されてくる側(こちらにとってはSTDIN)をつなぐ
    close(outfd[1]);
    close(STDIN_FILENO);
    dup2(outfd[0], STDIN_FILENO);

    // コマンドを起動する
    execl(cmd, cmd, arg1, (char*) NULL);
  } else { // 元のプロセス
    close(infd[1]);
    *infp = fdopen(infd[0], "r");

    close(outfd[0]);
    *outfp = fdopen(outfd[1], "a");
  }
}

 

こういうのは、学生時代にシステムプログラミングの講義の内容に含まれていたはずで、でも僕はその講義をサボって全然授業を聞いてなかったんですよね…もっと真面目に勉強しておいたらよかったなあと思ったのでした。