summaryrefslogtreecommitdiffstats
path: root/src/providers/child_common.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/providers/child_common.c')
-rw-r--r--src/providers/child_common.c416
1 files changed, 416 insertions, 0 deletions
diff --git a/src/providers/child_common.c b/src/providers/child_common.c
new file mode 100644
index 000000000..2ad0f04e3
--- /dev/null
+++ b/src/providers/child_common.c
@@ -0,0 +1,416 @@
+/*
+ SSSD
+
+ Common helper functions to be used in child processes
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2009 Red Hat
+
+ 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 3 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, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <sys/types.h>
+#include <fcntl.h>
+#include <tevent.h>
+#include <sys/wait.h>
+#include <errno.h>
+
+#include "util/util.h"
+#include "util/find_uid.h"
+#include "db/sysdb.h"
+#include "providers/child_common.h"
+
+/* Async communication with the child process via a pipe */
+
+struct write_pipe_state {
+ int fd;
+ uint8_t *buf;
+ size_t len;
+ size_t written;
+};
+
+static void write_pipe_handler(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags, void *pvt);
+
+struct tevent_req *write_pipe_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ uint8_t *buf, size_t len, int fd)
+{
+ struct tevent_req *req;
+ struct write_pipe_state *state;
+ struct tevent_fd *fde;
+
+ req = tevent_req_create(mem_ctx, &state, struct write_pipe_state);
+ if (req == NULL) return NULL;
+
+ state->fd = fd;
+ state->buf = buf;
+ state->len = len;
+ state->written = 0;
+
+ fde = tevent_add_fd(ev, state, fd, TEVENT_FD_WRITE,
+ write_pipe_handler, req);
+ if (fde == NULL) {
+ DEBUG(1, ("tevent_add_fd failed.\n"));
+ goto fail;
+ }
+
+ return req;
+
+fail:
+ talloc_zfree(req);
+ return NULL;
+}
+
+static void write_pipe_handler(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags, void *pvt)
+{
+ struct tevent_req *req = talloc_get_type(pvt, struct tevent_req);
+ struct write_pipe_state *state = tevent_req_data(req,
+ struct write_pipe_state);
+ ssize_t size;
+
+ if (flags & TEVENT_FD_READ) {
+ DEBUG(1, ("write_pipe_done called with TEVENT_FD_READ,"
+ " this should not happen.\n"));
+ tevent_req_error(req, EINVAL);
+ return;
+ }
+
+ size = write(state->fd,
+ state->buf + state->written,
+ state->len - state->written);
+ if (size == -1) {
+ if (errno == EAGAIN || errno == EINTR) return;
+ DEBUG(1, ("write failed [%d][%s].\n", errno, strerror(errno)));
+ tevent_req_error(req, errno);
+ return;
+
+ } else if (size >= 0) {
+ state->written += size;
+ if (state->written > state->len) {
+ DEBUG(1, ("write to much, this should never happen.\n"));
+ tevent_req_error(req, EINVAL);
+ return;
+ }
+ } else {
+ DEBUG(1, ("unexpected return value of write [%d].\n", size));
+ tevent_req_error(req, EINVAL);
+ return;
+ }
+
+ if (state->len == state->written) {
+ DEBUG(6, ("All data has been sent!\n"));
+ tevent_req_done(req);
+ return;
+ }
+}
+
+int write_pipe_recv(struct tevent_req *req)
+{
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ return EOK;
+}
+
+struct read_pipe_state {
+ int fd;
+ uint8_t *buf;
+ size_t len;
+};
+
+static void read_pipe_handler(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags, void *pvt);
+
+struct tevent_req *read_pipe_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev, int fd)
+{
+ struct tevent_req *req;
+ struct read_pipe_state *state;
+ struct tevent_fd *fde;
+
+ req = tevent_req_create(mem_ctx, &state, struct read_pipe_state);
+ if (req == NULL) return NULL;
+
+ state->fd = fd;
+ state->buf = talloc_array(state, uint8_t, MAX_CHILD_MSG_SIZE);
+ state->len = 0;
+ if (state->buf == NULL) goto fail;
+
+ fde = tevent_add_fd(ev, state, fd, TEVENT_FD_READ,
+ read_pipe_handler, req);
+ if (fde == NULL) {
+ DEBUG(1, ("tevent_add_fd failed.\n"));
+ goto fail;
+ }
+
+ return req;
+
+fail:
+ talloc_zfree(req);
+ return NULL;
+}
+
+static void read_pipe_handler(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags, void *pvt)
+{
+ struct tevent_req *req = talloc_get_type(pvt, struct tevent_req);
+ struct read_pipe_state *state = tevent_req_data(req,
+ struct read_pipe_state);
+ ssize_t size;
+ errno_t err;
+
+ if (flags & TEVENT_FD_WRITE) {
+ DEBUG(1, ("read_pipe_done called with TEVENT_FD_WRITE,"
+ " this should not happen.\n"));
+ tevent_req_error(req, EINVAL);
+ return;
+ }
+
+ size = read(state->fd,
+ state->buf + state->len,
+ MAX_CHILD_MSG_SIZE - state->len);
+ if (size == -1) {
+ err = errno;
+ if (err == EAGAIN || err == EINTR) {
+ return;
+ }
+
+ DEBUG(1, ("read failed [%d][%s].\n", err, strerror(err)));
+ tevent_req_error(req, err);
+ return;
+
+ } else if (size > 0) {
+ state->len += size;
+ if (state->len > MAX_CHILD_MSG_SIZE) {
+ DEBUG(1, ("read to much, this should never happen.\n"));
+ tevent_req_error(req, EINVAL);
+ return;
+ }
+
+ } else if (size == 0) {
+ DEBUG(6, ("EOF received, client finished\n"));
+ tevent_req_done(req);
+ return;
+
+ } else {
+ DEBUG(1, ("unexpected return value of read [%d].\n", size));
+ tevent_req_error(req, EINVAL);
+ return;
+ }
+}
+
+int read_pipe_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ uint8_t **buf, ssize_t *len)
+{
+ struct read_pipe_state *state;
+ state = tevent_req_data(req, struct read_pipe_state);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ *buf = talloc_steal(mem_ctx, state->buf);
+ *len = state->len;
+
+ return EOK;
+}
+
+/* The pipes to communicate with the child must be nonblocking */
+void fd_nonblocking(int fd)
+{
+ int flags;
+ int ret;
+
+ flags = fcntl(fd, F_GETFL, 0);
+ if (flags == -1) {
+ ret = errno;
+ DEBUG(1, ("F_GETFL failed [%d][%s].\n", ret, strerror(ret)));
+ return;
+ }
+
+ if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
+ ret = errno;
+ DEBUG(1, ("F_SETFL failed [%d][%s].\n", ret, strerror(ret)));
+ }
+
+ return;
+}
+
+void child_sig_handler(struct tevent_context *ev,
+ struct tevent_signal *sige, int signum,
+ int count, void *__siginfo, void *pvt)
+{
+ int ret;
+ int child_status;
+
+ DEBUG(7, ("Waiting for [%d] childeren.\n", count));
+ do {
+ errno = 0;
+ ret = waitpid(-1, &child_status, WNOHANG);
+
+ if (ret == -1) {
+ DEBUG(1, ("waitpid failed [%d][%s].\n", errno, strerror(errno)));
+ } else if (ret == 0) {
+ DEBUG(1, ("waitpid did not found a child with changed status.\n"));
+ } else {
+ if (WEXITSTATUS(child_status) != 0) {
+ DEBUG(1, ("child [%d] failed with status [%d].\n", ret,
+ child_status));
+ } else {
+ DEBUG(4, ("child [%d] finished successful.\n", ret));
+ }
+ }
+
+ --count;
+ } while (count < 0);
+
+ return;
+}
+
+static errno_t prepare_child_argv(TALLOC_CTX *mem_ctx,
+ int child_debug_fd,
+ const char *binary,
+ char ***_argv)
+{
+ uint_t argc = 3; /* program name, debug_level and NULL */
+ char ** argv;
+ errno_t ret = EINVAL;
+
+ /* Save the current state in case an interrupt changes it */
+ bool child_debug_to_file = debug_to_file;
+ bool child_debug_timestamps = debug_timestamps;
+
+ if (child_debug_to_file) argc++;
+ if (!child_debug_timestamps) argc++;
+
+ /* program name, debug_level,
+ * debug_to_file, debug_timestamps
+ * and NULL */
+ argv = talloc_array(mem_ctx, char *, argc);
+ if (argv == NULL) {
+ DEBUG(1, ("talloc_array failed.\n"));
+ return ENOMEM;
+ }
+
+ argv[--argc] = NULL;
+
+ argv[--argc] = talloc_asprintf(argv, "--debug-level=%d",
+ debug_level);
+ if (argv[argc] == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ if (child_debug_to_file) {
+ argv[--argc] = talloc_asprintf(argv, "--debug-fd=%d",
+ child_debug_fd);
+ if (argv[argc] == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ }
+
+ if (!child_debug_timestamps) {
+ argv[--argc] = talloc_strdup(argv, "--debug-timestamps=0");
+ if (argv[argc] == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ }
+
+ argv[--argc] = talloc_strdup(argv, binary);
+ if (argv[argc] == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ if (argc != 0) {
+ ret = EINVAL;
+ goto fail;
+ }
+
+ *_argv = argv;
+
+ return EOK;
+
+fail:
+ talloc_free(argv);
+ return ret;
+}
+
+errno_t exec_child(TALLOC_CTX *mem_ctx,
+ int *pipefd_to_child, int *pipefd_from_child,
+ const char *binary, int debug_fd)
+{
+ int ret;
+ errno_t err;
+ char **argv;
+
+ close(pipefd_to_child[1]);
+ ret = dup2(pipefd_to_child[0], STDIN_FILENO);
+ if (ret == -1) {
+ err = errno;
+ DEBUG(1, ("dup2 failed [%d][%s].\n", err, strerror(err)));
+ return err;
+ }
+
+ close(pipefd_from_child[0]);
+ ret = dup2(pipefd_from_child[1], STDOUT_FILENO);
+ if (ret == -1) {
+ err = errno;
+ DEBUG(1, ("dup2 failed [%d][%s].\n", err, strerror(err)));
+ return err;
+ }
+
+ ret = prepare_child_argv(mem_ctx, debug_fd,
+ binary, &argv);
+ if (ret != EOK) {
+ DEBUG(1, ("prepare_child_argv.\n"));
+ return ret;
+ }
+
+ ret = execv(binary, argv);
+ if (ret == -1) {
+ err = errno;
+ DEBUG(1, ("execv failed [%d][%s].\n", err, strerror(err)));
+ return err;
+ }
+
+ return EOK;
+}
+
+void child_cleanup(int readfd, int writefd)
+{
+ int ret;
+
+ if (readfd != -1) {
+ ret = close(readfd);
+ if (ret != EOK) {
+ ret = errno;
+ DEBUG(1, ("close failed [%d][%s].\n", errno, strerror(errno)));
+ }
+ }
+ if (writefd != -1) {
+ ret = close(writefd);
+ if (ret != EOK) {
+ ret = errno;
+ DEBUG(1, ("close failed [%d][%s].\n", errno, strerror(errno)));
+ }
+ }
+}