Browse Source

pipecmd: new module.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
Rusty Russell 10 years ago
parent
commit
66de67ea48
5 changed files with 303 additions and 0 deletions
  1. 1 0
      ccan/pipecmd/LICENSE
  2. 58 0
      ccan/pipecmd/_info
  3. 130 0
      ccan/pipecmd/pipecmd.c
  4. 30 0
      ccan/pipecmd/pipecmd.h
  5. 84 0
      ccan/pipecmd/test/run.c

+ 1 - 0
ccan/pipecmd/LICENSE

@@ -0,0 +1 @@
+../../licenses/CC0

+ 58 - 0
ccan/pipecmd/_info

@@ -0,0 +1,58 @@
+#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;
+}

+ 130 - 0
ccan/pipecmd/pipecmd.c

@@ -0,0 +1,130 @@
+/* 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;
+}

+ 30 - 0
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 <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 */

+ 84 - 0
ccan/pipecmd/test/run.c

@@ -0,0 +1,84 @@
+#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();
+}