#include #include #include #include #include #include #include #include #include #include #include #include #include #define FDPAIR_INITIALIZER { NULL, 0, { -1, -1 }, { -1, -1 } } struct fdpair { struct fdpair *next; int direction; int pipe[2]; int fd[2]; }; static int plumbfdpairs(struct fdpair *); static ssize_t strtofdpairs(const char *, struct fdpair **); static ssize_t strtofdpair(const char *, struct fdpair *); static ssize_t strtofd(const char *, int *); int main(int argc, char *argv[]) { const char *wargv[] = { "sh", "-c", NULL, NULL }; const char *wrapper = "exec cat"; struct fdpair *pairs = NULL; struct fdpair *p; ssize_t r; pid_t pid; int nullfd; int c; if (strtofdpairs("stdin<-stdout,stdout->stdin", &pairs) < 0) err(EX_OSERR, "strtofdpairs"); while ((c = getopt(argc, argv, "f:w:")) != -1) switch (c) { case 'w': wrapper = optarg; break; case 'f': r = strtofdpairs(optarg, &pairs); if (r >= 0 && optarg[r] != '\0') { errno = EINVAL; r = -1; } if (r < 0) err(EX_USAGE, "%s", optarg); break; default: goto usage; } argc -= optind; argv += optind; wargv[2] = wrapper; if ((nullfd = open("/dev/null", O_RDWR)) < 0) err(EX_OSERR, "/dev/null"); /* * Reserve the desired descriptors so * that socketpair(2) does not take them. */ for (p = pairs; p != NULL; p = p->next) if ((p->fd[0] != STDIN_FILENO && p->fd[0] != STDOUT_FILENO && p->fd[0] != STDERR_FILENO && dup2(nullfd, p->fd[0]) < 0) || (p->fd[1] != STDIN_FILENO && p->fd[1] != STDOUT_FILENO && p->fd[1] != STDERR_FILENO && dup2(nullfd, p->fd[1]) < 0)) err(EX_OSERR, "dup2"); (void)close(nullfd); for (p = pairs; p != NULL; p = p->next) if (socketpair(PF_LOCAL, SOCK_STREAM, 0, p->pipe) != 0) err(EX_OSERR, "pipe"); switch (pid = fork()) { case -1: err(EX_OSERR, "fork"); case 0: for (p = pairs; p != NULL; p = p->next) if (p->direction) { if (close(p->pipe[1]) != 0) err(EX_OSERR, "close"); if (dup2(p->pipe[0], p->fd[1]) < 0) err(EX_OSERR, "dup2"); if (close(p->pipe[0]) != 0) err(EX_OSERR, "close"); } else { if (close(p->pipe[0]) != 0) err(EX_OSERR, "close"); if (dup2(p->pipe[1], p->fd[1]) < 0) err(EX_OSERR, "dup2"); if (close(p->pipe[1]) != 0) err(EX_OSERR, "close"); } (void)execvp("sh", (char * const *)wargv); err(EX_SOFTWARE, "execvp: sh -c %s", wrapper); default: for (p = pairs; p != NULL; p = p->next) if (p->direction) { if (close(p->pipe[0]) != 0) err(EX_OSERR, "close"); if (dup2(p->pipe[1], p->fd[0]) < 0) err(EX_OSERR, "dup2"); if (close(p->pipe[1]) != 0) err(EX_OSERR, "close"); } else { if (close(p->pipe[1]) != 0) err(EX_OSERR, "close"); if (dup2(p->pipe[0], p->fd[0]) < 0) err(EX_OSERR, "dup2"); if (close(p->pipe[0]) != 0) err(EX_OSERR, "close"); } (void)execvp(*argv, argv); err(EX_SOFTWARE, "execvp: %s ...", *argv); } abort(); /* unreachable */ usage: (void)fprintf(stderr, "usage: fdwrap [-f fd-pairs] [-w wrapper-command] " "command [argument ...]\n"); return (EX_USAGE); } ssize_t strtofdpairs(const char *s, struct fdpair **fdp) { struct fdpair *p; struct fdpair *n; struct fdpair t = FDPAIR_INITIALIZER; ssize_t r; size_t i; for (i = 0; s[i] != '\0'; i += r + (s[i + r] == ',')) { if ((r = strtofdpair(s + i, &t)) < 0) return (-1); for (p = NULL, n = *fdp; n != NULL; p = n, n = n == NULL ? *fdp : n->next) if (n->fd[0] == t.fd[0] || n->fd[1] == t.fd[1]) { if (p == NULL) *fdp = n->next; else p->next = n->next; free(n); n = p; } if ((n = malloc(sizeof(*n))) == NULL) return (-1); *n = t; if (p == NULL) *fdp = n; else p->next = n; } if (i == 0) { errno = EINVAL; return (-1); } return (i); } ssize_t strtofdpair(const char *s, struct fdpair *fdp) { int fd[2]; ssize_t r; size_t i; int d = -1; if ((r = strtofd(s, &fd[0])) < 0) return (-1); i = r; if (s[i] == '-' && s[i + 1] == '>') { d = 1; i += 2; } else if (s[i] == '<' && s[i + 1] == '-') { d = 0; i += 2; } else if (s[i] == '>') { d = 1; i++; } else if (s[i] == '<') { d = 0; i++; } if (d == -1) { errno = EINVAL; return (-1); } if ((r = strtofd(s + i, &fd[1])) < 0) return (-1); i += r; if (fdp != NULL) { fdp->direction = d; fdp->fd[0] = fd[0]; fdp->fd[1] = fd[1]; } return (i); } ssize_t strtofd(const char *s, int *fdp) { size_t i; int fd = 0; if (strncasecmp(s, "stdin", 5) == 0) { i = 5; fd = STDIN_FILENO; } else if (strncasecmp(s, "stdout", 6) == 0) { i = 6; fd = STDOUT_FILENO; } else if (strncasecmp(s, "stderr", 6) == 0) { i = 6; fd = STDERR_FILENO; } else { for (i = 0; s[i] >= '0' && s[i] <= '9'; fd = fd * 10 + (s[i] - '0'), i++) if (fd > (INT_MAX - (s[i] - '0')) / 10) { errno = ERANGE; return (-1); } } if (i == 0) { errno = EINVAL; return (-1); } if (fdp != NULL) *fdp = fd; return (i); }