From b3f4fe27d5c8494b9aed58883a1740102526628c Mon Sep 17 00:00:00 2001 From: David Zeuthen Date: Sun, 11 Nov 2007 16:32:22 -0500 Subject: [PATCH] actually include the files with the spawn functions --- src/kit/kit-spawn.c | 607 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/kit/kit-spawn.h | 49 +++++ 2 files changed, 656 insertions(+) create mode 100644 src/kit/kit-spawn.c create mode 100644 src/kit/kit-spawn.h diff --git a/src/kit/kit-spawn.c b/src/kit/kit-spawn.c new file mode 100644 index 0000000..fb48a14 --- /dev/null +++ b/src/kit/kit-spawn.c @@ -0,0 +1,607 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */ +/*************************************************************************** + * + * kit-spawn.c : Spawn utilities + * + * Copyright (C) 2007 David Zeuthen, + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + **************************************************************************/ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "kit-test.h" + + +/** + * SECTION:kit-spawn + * @title: Spawn utilities + * @short_description: Spawn utilities + * + * Various spawn utilities. + **/ + +static kit_bool_t +set_close_on_exec (int fd, void *data) +{ + if (fd >= (int) data) { + if (fcntl (fd, F_SETFD, FD_CLOEXEC) != 0 && errno != EBADF) { + return FALSE; + } + } + + return TRUE; +} + +static kit_bool_t +_fdwalk (kit_bool_t (*callback)(int fd, void *user_data), void *user_data) +{ + int fd; + int max_fd; + + kit_return_val_if_fail (callback != NULL, FALSE); + + max_fd = sysconf (_SC_OPEN_MAX); + for (fd = 0; fd < max_fd; fd++) { + if (!callback (fd, user_data)) + return FALSE; + } + + return TRUE; +} + +static int +_sane_dup2 (int fd1, int fd2) +{ + int ret; + +again: + ret = dup2 (fd1, fd2); + if (ret < 0 && errno == EINTR) + goto again; + + return ret; +} + +static ssize_t +_read_from (int fd, char **out_string) +{ + char buf[4096]; + ssize_t num_read; + +again: + num_read = read (fd, buf, sizeof (buf) - 1); + if (num_read == -1) { + if (errno == EINTR) + goto again; + else + goto out; + } + + if (num_read > 0) { + char *s; + + buf[num_read] = '\0'; + + s = kit_str_append (*out_string, buf); + if (s == NULL) { + errno = ENOMEM; + num_read = -1; + goto out; + } + *out_string = s; + + //kit_debug ("fd=%d read %d bytes: '%s'", fd, num_read, buf); + } + +out: + return num_read; +} + +static ssize_t +_write_to (int fd, char *str) +{ + ssize_t num_written; + +again: + num_written = write (fd, str, strlen (str)); + if (num_written == -1) { + if (errno == EINTR) + goto again; + else + goto out; + } + + kit_debug ("Wrote %d bytes from '%s'", num_written, str); + +out: + return num_written; +} + +/** + * kit_spawn_sync: + * @working_directory: Working directory for child or #NULL to inherit parents + * @argv: #NULL terminated argument vector + * @envp: #NULL terminated environment or #NULL to inherit parents; + * @stdin: String to write to stdin of child or #NULL + * @stdout: Return location for stdout from child or #NULL. Free with kit_free(). + * @stderr: Return location for stderr from child or #NULL. Free with kit_free(). + * @out_exit_status: Return location for exit status + * + * Executes a child process and waits for the child process to exit + * before returning. The exit status of the child is stored in + * @out_exit_status as it would be returned by waitpid(); standard + * UNIX macros such as WIFEXITED() and WEXITSTATUS() must be used to + * evaluate the exit status. + * + * Returns: #TRUE if the child was executed; #FALSE if an error + * occured and errno will be set. + */ +kit_bool_t +kit_spawn_sync (const char *working_directory, + char **argv, + char **envp, + char *stdin, + char **stdout, + char **stderr, + int *out_exit_status) +{ + kit_bool_t ret; + pid_t pid; + char **envp_to_use; + int stdin_pipe[2] = {-1, -1}; + int stdout_pipe[2] = {-1, -1}; + int stderr_pipe[2] = {-1, -1}; + + ret = FALSE; + pid = -1; + + kit_return_val_if_fail (argv != NULL, FALSE); + kit_return_val_if_fail (out_exit_status != NULL, FALSE); + + if (stdout != NULL) + *stdout = NULL; + if (stderr != NULL) + *stderr = NULL; + + if (envp != NULL) + envp_to_use = envp; + else + envp_to_use = environ; + + if (stdin != NULL) { + if (pipe (stdin_pipe) != 0) + goto out; + } + + if (stdout != NULL) { + if (pipe (stdout_pipe) != 0) + goto out; + } + + if (stderr != NULL) { + if (pipe (stderr_pipe) != 0) + goto out; + } + + pid = fork (); + if (pid == -1) + goto out; + + if (pid == 0) { + /* child */ + + signal (SIGPIPE, SIG_DFL); + + /* close unused ends */ + if (stdin_pipe[1] != -1) { + close (stdin_pipe[1]); + } + if (stdout_pipe[0] != -1) { + close (stdout_pipe[0]); + } + if (stderr_pipe[0] != -1) { + close (stderr_pipe[0]); + } + + /* close all open file descriptors of child except stdin, stdout, stderr */ + _fdwalk (set_close_on_exec, (void *) 3); + + /* change working directory */ + if (working_directory != NULL) { + if (chdir (working_directory) != 0) { + exit (128 + errno); + } + } + + /* set stdin, stdout and stderr */ + + if (stdin == NULL) { + int fd_null; + fd_null = open ("/dev/null", O_RDONLY); + if (fd_null < 0) { + exit (128 + errno); + } + if (_sane_dup2 (fd_null, 0) < 0) { + exit (128 + errno); + } + } else { + if (_sane_dup2 (stdin_pipe[0], 0) < 0) { + exit (128 + errno); + } + } + if (stdout != NULL) { + if (_sane_dup2 (stdout_pipe[1], 1) < 0) { + exit (128 + errno); + } + } + + if (stderr != NULL) { + if (_sane_dup2 (stderr_pipe[1], 2) < 0) { + exit (128 + errno); + } + } + + /* finally, execute the child */ + if (execve (argv[0], argv, envp_to_use) == -1) { + exit (128 + errno); + } + + } else { + char *wp; + + /* parent */ + + /* closed unused ends */ + if (stdin_pipe[0] != -1) { + close (stdin_pipe[0]); + } + if (stdout_pipe[1] != -1) { + close (stdout_pipe[1]); + } + if (stderr_pipe[1] != -1) { + close (stderr_pipe[1]); + } + + wp = stdin; + + while (stdin_pipe[1] != -1 || stdout_pipe[0] != -1 || stderr_pipe[0] != -1) { + int ret; + ssize_t num_read; + ssize_t num_written; + int max; + fd_set read_fds; + fd_set write_fds; + + FD_ZERO (&read_fds); + FD_ZERO (&write_fds); + if (stdin_pipe[1] != -1) { + FD_SET (stdin_pipe[1], &write_fds); + } + if (stdout_pipe[0] != -1) { + FD_SET (stdout_pipe[0], &read_fds); + } + if (stderr_pipe[0] != -1) { + FD_SET (stderr_pipe[0], &read_fds); + } + + max = stdin_pipe[1]; + if (stdout_pipe[0] > max) + max = stdout_pipe[0]; + if (stderr_pipe[0] > max) + max = stderr_pipe[0]; + + ret = select (max + 1, + &read_fds, + &write_fds, + NULL, + NULL); + + if (ret < 0 && errno != EINTR) { + goto out; + } + + if (stdin_pipe[1] != -1) { + num_written = _write_to (stdin_pipe[1], wp); + + if (num_written == -1) { + goto out; + } + + wp += num_written; + if (*wp == '\0') { + close (stdin_pipe[1]); + stdin_pipe[1] = -1; + } + } + + if (stdout_pipe[0] != -1) { + num_read = _read_from (stdout_pipe[0], stdout); + if (num_read == 0) { + close (stdout_pipe[0]); + stdout_pipe[0] = -1; + } else if (num_read == -1) { + goto out; + } + } + + if (stderr_pipe[0] != -1) { + num_read = _read_from (stderr_pipe[0], stderr); + if (num_read == 0) { + close (stderr_pipe[0]); + stderr_pipe[0] = -1; + } else if (num_read == -1) { + goto out; + } + } + } + + if (waitpid (pid, out_exit_status, 0) == -1) { + goto out; + } + pid = -1; + } + + //kit_debug ("exit %d", WEXITSTATUS (*out_exit_status)); + + if (WEXITSTATUS (*out_exit_status) < 128) { + ret = TRUE; + } else { + ret = FALSE; + errno = WEXITSTATUS (*out_exit_status) - 128; + } + +out: + if (pid != -1) { + kill (pid, SIGKILL); + waitpid (pid, out_exit_status, 0); + } + + if (stdin_pipe[1] != -1) + close (stdin_pipe[1]); + if (stdout_pipe[0] != -1) + close (stdout_pipe[0]); + if (stderr_pipe[0] != -1) + close (stderr_pipe[0]); + + if (!ret) { + if (stdout != NULL) { + kit_free (*stdout); + *stdout = NULL; + } + if (stderr != NULL) { + kit_free (*stderr); + *stderr = NULL; + } + } + + return ret; + +} + + +#ifdef KIT_BUILD_TESTS + +static kit_bool_t +_run_test (void) +{ + char path[] = "/tmp/kit-spawn-test"; + char *script1 = + "#!/bin/sh" "\n" + "echo \"Hello World\"" "\n" + "echo \"Goodbye World\" 1>&2" "\n" + "exit 42" "\n"; + char *script2 = + "#!/bin/sh" "\n" + "exit 43" "\n"; + char *script3 = + "#!/bin/sh" "\n" + "echo -n \"$KIT_TEST_VAR\"" "\n" + "exit 0" "\n"; + char *script4 = + "#!/bin/sh" "\n" + "if [ \"x$KIT_TEST_VAR\" = \"x\" ] ; then" "\n" + " exit 0" "\n" + "fi" "\n" + "exit 1" "\n"; + char *script5 = + "#!/bin/sh" "\n" + "pwd" "\n" + "exit 0" "\n"; + char *script6 = + "#!/bin/sh" "\n" + "read value" "\n" + "echo -n \"$value\"" "\n" + "echo -n \" \"" "\n" + "read value" "\n" + "echo -n \"$value\"" "\n" + "exit 0" "\n"; + char *argv[] = {"/tmp/kit-spawn-test", NULL}; + char *stdout; + char *stderr; + int exit_status; + struct stat statbuf; + + /* script echoing to stdout and stderr */ + if (kit_file_set_contents (path, 0700, script1, strlen (script1))) { + if (kit_spawn_sync ("/", + argv, + NULL, + NULL, + &stdout, + &stderr, + &exit_status)) { + kit_assert (WEXITSTATUS (exit_status) == 42); + kit_assert (stdout != NULL && strcmp (stdout, "Hello World\n") == 0); + kit_assert (stderr != NULL && strcmp (stderr, "Goodbye World\n") == 0); + kit_free (stdout); + kit_free (stderr); + } + + if (kit_spawn_sync ("/", + argv, + NULL, + NULL, + NULL, + NULL, + &exit_status)) { + kit_assert (WEXITSTATUS (exit_status) == 42); + } + + kit_assert (unlink (path) == 0); + } + + /* silent script */ + if (kit_file_set_contents (path, 0700, script2, strlen (script2))) { + if (kit_spawn_sync ("/", + argv, + NULL, + NULL, + &stdout, + &stderr, + &exit_status)) { + kit_assert (WEXITSTATUS (exit_status) == 43); + kit_assert (stdout == NULL); + kit_assert (stderr == NULL); + } + + kit_assert (unlink (path) == 0); + } + + /* check environment is set */ + if (kit_file_set_contents (path, 0700, script3, strlen (script3))) { + char *envp[] = {"KIT_TEST_VAR=some_value", NULL}; + + if (kit_spawn_sync ("/", + argv, + envp, + NULL, + &stdout, + NULL, + &exit_status)) { + kit_assert (WEXITSTATUS (exit_status) == 0); + kit_assert (stdout != NULL && strcmp (stdout, "some_value") == 0); + kit_free (stdout); + } + + kit_assert (unlink (path) == 0); + } + + /* check environment is replaced */ + if (kit_file_set_contents (path, 0700, script4, strlen (script4))) { + char *envp[] = {NULL}; + + kit_assert (setenv ("KIT_TEST_VAR", "foobar", 1) == 0); + + if (kit_spawn_sync ("/", + argv, + envp, + NULL, + NULL, + NULL, + &exit_status)) { + kit_assert (WEXITSTATUS (exit_status) == 0); + } + + kit_assert (unlink (path) == 0); + kit_assert (unsetenv ("KIT_TEST_VAR") == 0); + } + + /* check working directory */ + if (kit_file_set_contents (path, 0700, script5, strlen (script5))) { + kit_assert (stat ("/tmp", &statbuf) == 0 && S_ISDIR (statbuf.st_mode)); + if (kit_spawn_sync ("/tmp", + argv, + NULL, + NULL, + &stdout, + NULL, + &exit_status)) { + kit_assert (WEXITSTATUS (exit_status) == 0); + kit_assert (stdout != NULL && strcmp (stdout, "/tmp\n") == 0); + kit_free (stdout); + } + + kit_assert (stat ("/usr", &statbuf) == 0 && S_ISDIR (statbuf.st_mode)); + if (kit_spawn_sync ("/usr", + argv, + NULL, + NULL, + &stdout, + NULL, + &exit_status)) { + kit_assert (WEXITSTATUS (exit_status) == 0); + kit_assert (stdout != NULL && strcmp (stdout, "/usr\n") == 0); + kit_free (stdout); + } + + kit_assert (unlink (path) == 0); + } + + /* check bogus working directory */ + kit_assert (stat ("/org/freedesktop/PolicyKit/bogus-fs-path", &statbuf) != 0); + kit_assert (kit_spawn_sync ("/org/freedesktop/PolicyKit/bogus-fs-path", + argv, + NULL, + NULL, + NULL, + NULL, + &exit_status) == FALSE && + (errno == ENOENT || errno == ENOMEM)); + + /* check for writing to stdin */ + if (kit_file_set_contents (path, 0700, script6, strlen (script6))) { + if (kit_spawn_sync (NULL, + argv, + NULL, + "foobar0\nfoobar1", + &stdout, + NULL, + &exit_status)) { + kit_assert (WEXITSTATUS (exit_status) == 0); + kit_assert (stdout != NULL && strcmp (stdout, "foobar0 foobar1") == 0); + kit_free (stdout); + } + + kit_assert (unlink (path) == 0); + } + + return TRUE; +} + +KitTest _test_spawn = { + "kit_spawn", + NULL, + NULL, + _run_test +}; + +#endif /* KIT_BUILD_TESTS */ diff --git a/src/kit/kit-spawn.h b/src/kit/kit-spawn.h new file mode 100644 index 0000000..72b76f7 --- /dev/null +++ b/src/kit/kit-spawn.h @@ -0,0 +1,49 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */ +/*************************************************************************** + * + * kit-spawn.h : Spawn utilities + * + * Copyright (C) 2007 David Zeuthen, + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + **************************************************************************/ + +#if !defined (KIT_COMPILATION) && !defined(_KIT_INSIDE_KIT_H) +#error "Only can be included directly, this file may disappear or change contents." +#endif + +#ifndef KIT_SPAWN_H +#define KIT_SPAWN_H + +#include + +KIT_BEGIN_DECLS + +kit_bool_t kit_spawn_sync (const char *working_directory, + char **argv, + char **envp, + char *stdin, + char **stdout, + char **stderr, + int *out_exit_status); + +KIT_END_DECLS + +#endif /* KIT_SPAWN_H */ + + -- 2.7.4