Commit 66de67ea authored by Rusty Russell's avatar Rusty Russell

pipecmd: new module.

Signed-off-by: default avatarRusty Russell <rusty@rustcorp.com.au>
parent 4a439212
../../licenses/CC0
\ No newline at end of file
#include "config.h"
#include <stdio.h>
#include <string.h>
/**
* 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 <rusty@rustcorp.com.au>
*
* Example:
* // Outputs HELLO WORLD
* #include <ccan/pipecmd/pipecmd.h>
* #include <ccan/err/err.h>
* #include <sys/types.h>
* #include <sys/wait.h>
* #include <unistd.h>
* #include <ctype.h>
*
* // 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;
}
/* CC0 license (public domain) - see LICENSE file for details */
#include <ccan/pipecmd/pipecmd.h>
#include <ccan/noerr/noerr.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
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;
}
/* CC0 license (public domain) - see LICENSE file for details */
#ifndef CCAN_PIPECMD_H
#define CCAN_PIPECMD_H
#include "config.h"
#include <sys/types.h>
#include <sys/wait.h>
#include <stdarg.h>
/**
* 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 */
#include <ccan/pipecmd/pipecmd.h>
/* Include the C files directly. */
#include <ccan/pipecmd/pipecmd.c>
#include <ccan/tap/tap.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
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();
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment