From: Rusty Russell Date: Fri, 20 Nov 2015 05:39:48 +0000 (+1030) Subject: pipecmd: new module. X-Git-Url: http://git.ozlabs.org/?p=ccan;a=commitdiff_plain;h=66de67ea48009a086f940288eacf9928515f3aec pipecmd: new module. Signed-off-by: Rusty Russell --- diff --git a/ccan/pipecmd/LICENSE b/ccan/pipecmd/LICENSE new file mode 120000 index 00000000..b7951dab --- /dev/null +++ b/ccan/pipecmd/LICENSE @@ -0,0 +1 @@ +../../licenses/CC0 \ No newline at end of file diff --git a/ccan/pipecmd/_info b/ccan/pipecmd/_info new file mode 100644 index 00000000..1af377bf --- /dev/null +++ b/ccan/pipecmd/_info @@ -0,0 +1,58 @@ +#include "config.h" +#include +#include + +/** + * pipecmd - code to fork and run a command in a pipe. + * + * This code is a classic example of how to run a command in a child, while + * handling the case where the exec fails. + * + * License: CC0 (Public domain) + * Author: Rusty Russell + * + * Example: + * // Outputs HELLO WORLD + * #include + * #include + * #include + * #include + * #include + * #include + * + * // Runs ourselves with an argument, upcases output. + * int main(int argc, char **argv) + * { + * pid_t child; + * int outputfd, i, status; + * char input[12]; + * + * if (argc == 2) { + * write(STDOUT_FILENO, "hello world\n", 12); + * exit(0); + * } + * child = pipecmd(&outputfd, NULL, argv[0], "ignoredarg", NULL); + * if (child < 0) + * err(1, "Creating child"); + * if (read(outputfd, input, sizeof(input)) != sizeof(input)) + * err(1, "Reading input"); + * if (waitpid(child, &status, 0) != child) + * err(1, "Waiting for child"); + * for (i = 0; i < sizeof(input); i++) + * printf("%c", toupper(input[i])); + * exit(0); + * } + */ +int main(int argc, char *argv[]) +{ + /* Expect exactly one argument */ + if (argc != 2) + return 1; + + if (strcmp(argv[1], "depends") == 0) { + printf("ccan/noerr\n"); + return 0; + } + + return 1; +} diff --git a/ccan/pipecmd/pipecmd.c b/ccan/pipecmd/pipecmd.c new file mode 100644 index 00000000..352677fa --- /dev/null +++ b/ccan/pipecmd/pipecmd.c @@ -0,0 +1,130 @@ +/* CC0 license (public domain) - see LICENSE file for details */ +#include +#include +#include +#include +#include +#include + +static char **gather_args(const char *arg0, va_list ap) +{ + size_t n = 1; + char **arr = calloc(sizeof(char *), n + 1); + + if (!arr) + return NULL; + arr[0] = (char *)arg0; + + while ((arr[n++] = va_arg(ap, char *)) != NULL) { + arr = realloc(arr, sizeof(char *) * (n + 1)); + if (!arr) + return NULL; + } + return arr; +} + +pid_t pipecmdv(int *fd_fromchild, int *fd_tochild, const char *cmd, va_list ap) +{ + int tochild[2], fromchild[2], execfail[2]; + pid_t childpid; + int err; + + if (fd_tochild) { + if (pipe(tochild) != 0) + goto fail; + } else { + tochild[0] = open("/dev/null", O_RDONLY); + if (tochild[0] < 0) + goto fail; + } + if (fd_fromchild) { + if (pipe(fromchild) != 0) + goto close_tochild_fail; + } else { + fromchild[1] = open("/dev/null", O_WRONLY); + if (fromchild[1] < 0) + goto close_tochild_fail; + } + if (pipe(execfail) != 0) + goto close_fromchild_fail; + + if (fcntl(execfail[1], F_SETFD, fcntl(execfail[1], F_GETFD) + | FD_CLOEXEC) < 0) + goto close_execfail_fail; + + childpid = fork(); + if (childpid < 0) + goto close_execfail_fail; + + if (childpid == 0) { + char **args = gather_args(cmd, ap); + + if (fd_tochild) + close(tochild[1]); + if (fd_fromchild) + close(fromchild[0]); + close(execfail[0]); + + // Child runs command. + if (!args) + err = ENOMEM; + else { + if (tochild[0] != STDIN_FILENO) { + if (dup2(tochild[0], STDIN_FILENO) == -1) + goto child_errno_fail; + close(tochild[0]); + } + if (fromchild[1] != STDOUT_FILENO) { + if (dup2(fromchild[1], STDOUT_FILENO) == -1) + goto child_errno_fail; + close(fromchild[1]); + } + execvp(cmd, args); + child_errno_fail: + err = errno; + } + write(execfail[1], &err, sizeof(err)); + exit(127); + } + + close(tochild[0]); + close(fromchild[1]); + close(execfail[1]); + /* Child will close this without writing on successful exec. */ + if (read(execfail[0], &err, sizeof(err)) == sizeof(err)) { + waitpid(childpid, NULL, 0); + errno = err; + return -1; + } + if (fd_tochild) + *fd_tochild = tochild[1]; + if (fd_fromchild) + *fd_fromchild = fromchild[0]; + return childpid; + +close_execfail_fail: + close_noerr(execfail[0]); + close_noerr(execfail[1]); +close_fromchild_fail: + if (fd_fromchild) + close_noerr(fromchild[0]); + close_noerr(fromchild[1]); +close_tochild_fail: + close_noerr(tochild[0]); + if (fd_tochild) + close_noerr(tochild[1]); +fail: + return -1; +} + +pid_t pipecmd(int *fd_fromchild, int *fd_tochild, const char *cmd, ...) +{ + pid_t childpid; + + va_list ap; + va_start(ap, cmd); + childpid = pipecmdv(fd_fromchild, fd_tochild, cmd, ap); + va_end(ap); + + return childpid; +} diff --git a/ccan/pipecmd/pipecmd.h b/ccan/pipecmd/pipecmd.h new file mode 100644 index 00000000..3adfb541 --- /dev/null +++ b/ccan/pipecmd/pipecmd.h @@ -0,0 +1,30 @@ +/* CC0 license (public domain) - see LICENSE file for details */ +#ifndef CCAN_PIPECMD_H +#define CCAN_PIPECMD_H +#include "config.h" +#include +#include +#include + +/** + * 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) + * @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. + * + * The return value is the pid of the child, or -1. + */ +pid_t pipecmd(int *infd, int *outfd, 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) + * @cmd: command to run. + * @ap: argument list for arguments. + */ +pid_t pipecmdv(int *infd, int *outfd, const char *cmd, va_list ap); +#endif /* CCAN_PIPECMD_H */ diff --git a/ccan/pipecmd/test/run.c b/ccan/pipecmd/test/run.c new file mode 100644 index 00000000..b08d194d --- /dev/null +++ b/ccan/pipecmd/test/run.c @@ -0,0 +1,84 @@ +#include +/* Include the C files directly. */ +#include +#include +#include +#include +#include + +int main(int argc, char *argv[]) +{ + pid_t child; + int infd, outfd, status; + char buf[5] = "test"; + + /* We call ourselves, to test pipe. */ + if (argc == 2) { + if (strcmp(argv[1], "out") == 0) { + if (write(STDOUT_FILENO, buf, sizeof(buf)) != sizeof(buf)) + exit(1); + } else if (strcmp(argv[1], "in") == 0) { + if (read(STDIN_FILENO, buf, sizeof(buf)) != sizeof(buf)) + exit(1); + if (memcmp(buf, "test", sizeof(buf)) != 0) + exit(1); + } else if (strcmp(argv[1], "inout") == 0) { + if (read(STDIN_FILENO, buf, sizeof(buf)) != sizeof(buf)) + exit(1); + buf[0]++; + if (write(STDOUT_FILENO, buf, sizeof(buf)) != sizeof(buf)) + exit(1); + } else + abort(); + exit(0); + } + + /* This is how many tests you plan to run */ + plan_tests(26); + child = pipecmd(&outfd, &infd, 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)); + 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); + if (!ok1(child > 0)) + exit(1); + ok1(write(infd, buf, sizeof(buf)) == sizeof(buf)); + ok1(waitpid(child, &status, 0) == child); + ok1(WIFEXITED(status)); + ok1(WEXITSTATUS(status) == 0); + + child = pipecmd(&outfd, NULL, argv[0], "out", NULL); + if (!ok1(child > 0)) + exit(1); + ok1(read(outfd, buf, sizeof(buf)) == sizeof(buf)); + 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); + if (!ok1(child > 0)) + exit(1); + ok1(waitpid(child, &status, 0) == child); + ok1(WIFEXITED(status)); + ok1(WEXITSTATUS(status) == 0); + + // Reading should fail. + child = pipecmd(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); + + /* This exits depending on whether all tests passed */ + return exit_status(); +}