dup2(2)
キモになるのは
- 複数のサービスをまとめて面倒を見るにあたって、それぞれのサービス用のリスニングソケットを select(2) で多重化する
- 接続があると fork して子を作り、その子を exec して本体のプログラム(daytime.pl や echo.pl に相当するもの) を実行する
- exec する前にソケットディスクリプタを 0, 1, 2 に dup2(2) する
というところです。特に最後のところですね。exec でプログラムを切り替えてもファイルディスクリプタはそのまま継承するので、あらかじめ dup でソケットと標準入出力をつなげておいて exec することで、exec したあとのプログラムの標準入出力が接続ソケットとのやりとり相当になるという。なるほどー。
inetd に限らず、shell のリダイレクションもそのような実装だし、pipe(2) を組み合わせれば同じく shell のパイプや Perl でいうところの `cmd` も実現できる、というのは UNIX 野郎にはある意味当然の話ではある。
というわけで、C++ で、コマンドの出力結果を取得する例とか。
#include <cstdio> #include <string> #include <stdexcept> #include <unistd.h> #include <sys/types.h> #include <sys/wait.h> class SystemException : public std::exception { public: SystemException(const std::string& msg) : message(msg) { }; ~SystemException() throw() { } const char* what() const throw() { return message.c_str(); } private: const std::string message; }; static std::string getcmdout(const char *cmd) { int pfd[2]; pid_t pid; if (pipe(pfd) == -1) { throw SystemException("pipe"); } pid = fork(); if (pid == -1) { throw SystemException("fork"); } if (pid > 0) { /* parent process */ char buffer[1024]; int n; int status; close(pfd[1]); std::string cmdout; while ((n = read(pfd[0], buffer, sizeof buffer)) > 0) { cmdout.append(buffer, n); } close(pfd[0]); waitpid(pid, &status, 0); if (status != 0) { throw SystemException("error occured in child process"); } return cmdout; } else { close(pfd[0]); if (dup2(pfd[1], STDOUT_FILENO) == -1) { perror("dup2"); _exit(EXIT_FAILURE); } if (execlp(cmd, cmd, NULL) == -1) { perror("execlp"); _exit(EXIT_FAILURE); } } } int main() { try { std::string cmdout = getcmdout("ls"); puts(cmdout.c_str()); return 0; } catch (SystemException& ex) { fprintf(stderr, "%s\n", ex.what()); exit(EXIT_FAILURE); } }