return arr;
}
-pid_t pipecmdv(int *fd_fromchild, int *fd_tochild, const char *cmd, va_list ap)
+pid_t pipecmdv(int *fd_fromchild, int *fd_tochild, int *fd_errfromchild,
+ const char *cmd, va_list ap)
{
char **arr = gather_args(cmd, ap);
pid_t ret;
errno = ENOMEM;
return -1;
}
- ret = pipecmdarr(fd_fromchild, fd_tochild, arr);
+ ret = pipecmdarr(fd_fromchild, fd_tochild, fd_errfromchild, arr);
free_noerr(arr);
return ret;
}
-pid_t pipecmdarr(int *fd_fromchild, int *fd_tochild, char *const *arr)
+pid_t pipecmdarr(int *fd_fromchild, int *fd_tochild, int *fd_errfromchild,
+ char *const *arr)
{
- int tochild[2], fromchild[2], execfail[2];
+ int tochild[2], fromchild[2], errfromchild[2], execfail[2];
pid_t childpid;
int err;
if (fromchild[1] < 0)
goto close_tochild_fail;
}
+ if (fd_errfromchild) {
+ if (fd_errfromchild == fd_fromchild) {
+ errfromchild[0] = fromchild[0];
+ errfromchild[1] = fromchild[1];
+ } else {
+ if (pipe(errfromchild) != 0)
+ goto close_fromchild_fail;
+ }
+ } else {
+ errfromchild[1] = open("/dev/null", O_WRONLY);
+ if (errfromchild[1] < 0)
+ goto close_fromchild_fail;
+ }
+
if (pipe(execfail) != 0)
- goto close_fromchild_fail;
+ goto close_errfromchild_fail;
if (fcntl(execfail[1], F_SETFD, fcntl(execfail[1], F_GETFD)
| FD_CLOEXEC) < 0)
close(tochild[1]);
if (fd_fromchild)
close(fromchild[0]);
+ if (fd_errfromchild && fd_errfromchild != fd_fromchild)
+ close(errfromchild[0]);
+
close(execfail[0]);
// Child runs command.
goto child_errno_fail;
close(fromchild[1]);
}
+ if (fd_errfromchild && fd_errfromchild == fd_fromchild) {
+ if (dup2(STDOUT_FILENO, STDERR_FILENO) == -1)
+ goto child_errno_fail;
+ } else if (errfromchild[1] != STDERR_FILENO) {
+ if (dup2(errfromchild[1], STDERR_FILENO) == -1)
+ goto child_errno_fail;
+ close(errfromchild[1]);
+ }
execvp(arr[0], arr);
child_errno_fail:
close(tochild[0]);
close(fromchild[1]);
+ close(errfromchild[1]);
close(execfail[1]);
/* Child will close this without writing on successful exec. */
if (read(execfail[0], &err, sizeof(err)) == sizeof(err)) {
*fd_tochild = tochild[1];
if (fd_fromchild)
*fd_fromchild = fromchild[0];
+ if (fd_errfromchild)
+ *fd_errfromchild = errfromchild[0];
return childpid;
close_execfail_fail:
close_noerr(execfail[0]);
close_noerr(execfail[1]);
+close_errfromchild_fail:
+ if (fd_errfromchild)
+ close_noerr(errfromchild[0]);
+ close_noerr(errfromchild[1]);
close_fromchild_fail:
if (fd_fromchild)
close_noerr(fromchild[0]);
return -1;
}
-pid_t pipecmd(int *fd_fromchild, int *fd_tochild, const char *cmd, ...)
+pid_t pipecmd(int *fd_fromchild, int *fd_tochild, int *fd_errfromchild,
+ const char *cmd, ...)
{
pid_t childpid;
va_list ap;
va_start(ap, cmd);
- childpid = pipecmdv(fd_fromchild, fd_tochild, cmd, ap);
+ childpid = pipecmdv(fd_fromchild, fd_tochild, fd_errfromchild, cmd, ap);
va_end(ap);
return childpid;
* pipecmd - run a command, optionally connect pipes.
* @infd: input fd to write to child (if non-NULL)
* @outfd: output fd to read from child (if non-NULL)
+ * @errfd: error-output fd to read from child (if non-NULL)
* @cmd...: NULL-terminate list of command and arguments.
*
* If @infd is NULL, the child's input is (read-only) /dev/null.
* If @outfd is NULL, the child's output is (write-only) /dev/null.
+ * If @errfd is NULL, the child's stderr is (write-only) /dev/null.
+ *
+ * If @errfd == @outfd (and non-NULL) they will be shared.
*
* The return value is the pid of the child, or -1.
*/
-pid_t pipecmd(int *infd, int *outfd, const char *cmd, ...);
+pid_t pipecmd(int *infd, int *outfd, int *errfd, const char *cmd, ...);
/**
* pipecmdv - run a command, optionally connect pipes (stdarg version)
* @infd: input fd to write to child (if non-NULL)
* @outfd: output fd to read from child (if non-NULL)
+ * @errfd: error-output fd to read from child (if non-NULL)
* @cmd: command to run.
* @ap: argument list for arguments.
*/
-pid_t pipecmdv(int *infd, int *outfd, const char *cmd, va_list ap);
+pid_t pipecmdv(int *infd, int *outfd, int *errfd, const char *cmd, va_list ap);
/**
* pipecmdarr - run a command, optionally connect pipes (char arry version)
* @infd: input fd to write to child (if non-NULL)
* @outfd: output fd to read from child (if non-NULL)
+ * @errfd: error-output fd to read from child (if non-NULL)
* @arr: NULL-terminated array for arguments (first is program to run).
*/
-pid_t pipecmdarr(int *infd, int *outfd, char *const *arr);
+pid_t pipecmdarr(int *infd, int *outfd, int *errfd, char *const *arr);
#endif /* CCAN_PIPECMD_H */
int main(int argc, char *argv[])
{
pid_t child;
- int infd, outfd, status;
+ int infd, outfd, errfd, status;
char buf[5] = "test";
/* We call ourselves, to test pipe. */
buf[0]++;
if (write(STDOUT_FILENO, buf, sizeof(buf)) != sizeof(buf))
exit(1);
+ } else if (strcmp(argv[1], "err") == 0) {
+ if (write(STDERR_FILENO, buf, sizeof(buf)) != sizeof(buf))
+ exit(1);
} else
abort();
exit(0);
}
+ /* We assume no fd leaks, so close them now. */
+ close(3);
+ close(4);
+ close(5);
+ close(6);
+ close(7);
+ close(8);
+ close(9);
+ close(10);
+
/* This is how many tests you plan to run */
- plan_tests(28);
- child = pipecmd(&outfd, &infd, argv[0], "inout", NULL);
+ plan_tests(67);
+ child = pipecmd(&outfd, &infd, &errfd, argv[0], "inout", NULL);
if (!ok1(child > 0))
exit(1);
ok1(write(infd, buf, sizeof(buf)) == sizeof(buf));
ok1(read(outfd, buf, sizeof(buf)) == sizeof(buf));
+ ok1(read(errfd, buf, sizeof(buf)) == 0);
+ ok1(close(infd) == 0);
+ ok1(close(outfd) == 0);
+ ok1(close(errfd) == 0);
buf[0]--;
ok1(memcmp(buf, "test", sizeof(buf)) == 0);
ok1(waitpid(child, &status, 0) == child);
ok1(WIFEXITED(status));
ok1(WEXITSTATUS(status) == 0);
- child = pipecmd(NULL, &infd, argv[0], "in", NULL);
+ child = pipecmd(NULL, &infd, NULL, argv[0], "in", NULL);
if (!ok1(child > 0))
exit(1);
ok1(write(infd, buf, sizeof(buf)) == sizeof(buf));
+ ok1(close(infd) == 0);
+ ok1(waitpid(child, &status, 0) == child);
+ ok1(WIFEXITED(status));
+ ok1(WEXITSTATUS(status) == 0);
+
+ child = pipecmd(&outfd, NULL, NULL, argv[0], "out", NULL);
+ if (!ok1(child > 0))
+ exit(1);
+ ok1(read(outfd, buf, sizeof(buf)) == sizeof(buf));
+ ok1(close(outfd) == 0);
+ ok1(memcmp(buf, "test", sizeof(buf)) == 0);
+ ok1(waitpid(child, &status, 0) == child);
+ ok1(WIFEXITED(status));
+ ok1(WEXITSTATUS(status) == 0);
+
+ /* Errfd only should be fine. */
+ child = pipecmd(NULL, NULL, &errfd, argv[0], "err", NULL);
+ if (!ok1(child > 0))
+ exit(1);
+ ok1(read(errfd, buf, sizeof(buf)) == sizeof(buf));
+ ok1(close(errfd) == 0);
+ ok1(memcmp(buf, "test", sizeof(buf)) == 0);
+ ok1(waitpid(child, &status, 0) == child);
+ ok1(WIFEXITED(status));
+ ok1(WEXITSTATUS(status) == 0);
+
+ /* errfd == outfd should work with both. */
+ child = pipecmd(&errfd, NULL, &errfd, argv[0], "err", NULL);
+ if (!ok1(child > 0))
+ exit(1);
+ ok1(read(errfd, buf, sizeof(buf)) == sizeof(buf));
+ ok1(close(errfd) == 0);
+ ok1(memcmp(buf, "test", sizeof(buf)) == 0);
ok1(waitpid(child, &status, 0) == child);
ok1(WIFEXITED(status));
ok1(WEXITSTATUS(status) == 0);
- child = pipecmd(&outfd, NULL, argv[0], "out", NULL);
+ child = pipecmd(&outfd, NULL, &outfd, argv[0], "out", NULL);
if (!ok1(child > 0))
exit(1);
ok1(read(outfd, buf, sizeof(buf)) == sizeof(buf));
+ ok1(close(outfd) == 0);
ok1(memcmp(buf, "test", sizeof(buf)) == 0);
ok1(waitpid(child, &status, 0) == child);
ok1(WIFEXITED(status));
ok1(WEXITSTATUS(status) == 0);
// Writing to /dev/null should be fine.
- child = pipecmd(NULL, NULL, argv[0], "out", NULL);
+ child = pipecmd(NULL, NULL, NULL, argv[0], "out", NULL);
if (!ok1(child > 0))
exit(1);
ok1(waitpid(child, &status, 0) == child);
ok1(WEXITSTATUS(status) == 0);
// Reading should fail.
- child = pipecmd(NULL, NULL, argv[0], "in", NULL);
+ child = pipecmd(NULL, NULL, NULL, argv[0], "in", NULL);
if (!ok1(child > 0))
exit(1);
ok1(waitpid(child, &status, 0) == child);
ok1(WIFEXITED(status));
ok1(WEXITSTATUS(status) == 1);
+ child = pipecmd(NULL, NULL, NULL, argv[0], "err", NULL);
+ if (!ok1(child > 0))
+ exit(1);
+ ok1(waitpid(child, &status, 0) == child);
+ ok1(WIFEXITED(status));
+ ok1(WEXITSTATUS(status) == 0);
+
// Can't run non-existent file, but errno set correctly.
- child = pipecmd(NULL, NULL, "/doesnotexist", "in", NULL);
+ child = pipecmd(NULL, NULL, NULL, "/doesnotexist", "in", NULL);
ok1(errno == ENOENT);
ok1(child < 0);
+ /* No fd leaks! */
+ ok1(close(3) == -1 && errno == EBADF);
+ ok1(close(4) == -1 && errno == EBADF);
+ ok1(close(5) == -1 && errno == EBADF);
+ ok1(close(6) == -1 && errno == EBADF);
+ ok1(close(7) == -1 && errno == EBADF);
+ ok1(close(8) == -1 && errno == EBADF);
+ ok1(close(9) == -1 && errno == EBADF);
+ ok1(close(10) == -1 && errno == EBADF);
+
/* This exits depending on whether all tests passed */
return exit_status();
}