From bfe098b42fb2e04d6e2d00302e9a7c77db5ad9f3 Mon Sep 17 00:00:00 2001
From: Pavel Zuna <pzuna@redhat.com>
Date: Thu, 20 Oct 2011 13:26:18 -0400
Subject: [PATCH] Add common SIGCHLD handling for providers.

---
 Makefile.am                      |   11 ++-
 src/providers/child_common.c     |  218 ++++++++++++++++++++++++++++++++++++-
 src/providers/child_common.h     |   25 +++++
 src/providers/data_provider_be.c |    9 ++
 src/providers/dp_backend.h       |    2 +
 5 files changed, 257 insertions(+), 8 deletions(-)

diff --git a/Makefile.am b/Makefile.am
index 8d75b8767bd30532f9a241ca1518545d602beffb..fe62a3eba6caff340e92b521a930406e603b593a 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -436,6 +436,7 @@ sssd_pam_LDADD = \
     libsss_util.la
 
 sssd_be_SOURCES = \
+    src/providers/child_common.c \
     src/providers/data_provider_be.c \
     src/providers/data_provider_fo.c \
     src/providers/data_provider_opts.c \
@@ -984,7 +985,9 @@ krb5_child_SOURCES = \
     src/providers/child_common.c \
     src/providers/dp_pam_data_util.c \
     src/util/user_info_msg.c \
-    src/util/sss_krb5.c
+    src/util/sss_krb5.c \
+    src/util/util.c \
+    src/util/signal.c
 krb5_child_CFLAGS = \
     $(AM_CFLAGS) \
     $(POPT_CFLAGS) \
@@ -994,12 +997,15 @@ krb5_child_LDADD = \
     $(TALLOC_LIBS) \
     $(TEVENT_LIBS) \
     $(POPT_LIBS) \
+    $(DHASH_LIBS) \
     $(KRB5_LIBS)
 
 ldap_child_SOURCES = \
     src/providers/ldap/ldap_child.c \
     src/providers/child_common.c \
-    src/util/sss_krb5.c
+    src/util/sss_krb5.c \
+    src/util/util.c \
+    src/util/signal.c
 ldap_child_CFLAGS = \
     $(AM_CFLAGS) \
     $(POPT_CFLAGS) \
@@ -1010,6 +1016,7 @@ ldap_child_LDADD = \
     $(TEVENT_LIBS) \
     $(POPT_LIBS) \
     $(OPENLDAP_LIBS) \
+    $(DHASH_LIBS) \
     $(KRB5_LIBS)
 
 proxy_child_SOURCES = \
diff --git a/src/providers/child_common.c b/src/providers/child_common.c
index 5920ebc7423ddce81dbf4d8f2767cab201852236..1ecde9d4b5c62a705a49154ff584b7324dad607b 100644
--- a/src/providers/child_common.c
+++ b/src/providers/child_common.c
@@ -33,7 +33,213 @@
 #include "db/sysdb.h"
 #include "providers/child_common.h"
 
+struct sss_sigchild_ctx {
+    struct tevent_context *ev;
+    hash_table_t *children;
+    int options;
+};
+
 struct sss_child_ctx {
+    pid_t pid;
+    sss_child_fn_t cb;
+    void *pvt;
+    struct sss_sigchild_ctx *sigchld_ctx;
+};
+
+errno_t sss_sigchld_init(TALLOC_CTX *mem_ctx,
+                         struct tevent_context *ev,
+                         struct sss_sigchild_ctx **child_ctx)
+{
+    errno_t ret;
+    struct sss_sigchild_ctx *sigchld_ctx;
+    struct tevent_signal *tes;
+
+    sigchld_ctx = talloc_zero(mem_ctx, struct sss_sigchild_ctx);
+    if (!sigchld_ctx) {
+        DEBUG(0, ("fatal error initializing sss_sigchild_ctx\n"));
+        return ENOMEM;
+    }
+    sigchld_ctx->ev = ev;
+
+    ret = sss_hash_create(sigchld_ctx, 10, &sigchld_ctx->children);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_FATAL_FAILURE,
+              ("fatal error initializing children hash table: [%s]\n",
+               strerror(ret)));
+        talloc_free(sigchld_ctx);
+        return ret;
+    }
+
+    BlockSignals(false, SIGCHLD);
+    tes = tevent_add_signal(ev, sigchld_ctx, SIGCHLD, SA_SIGINFO,
+                            sss_child_handler, sigchld_ctx);
+    if (tes == NULL) {
+        talloc_free(sigchld_ctx);
+        return EIO;
+    }
+
+    *child_ctx = sigchld_ctx;
+    return EOK;
+}
+
+static int sss_child_destructor(void *ptr)
+{
+    struct sss_child_ctx *child_ctx;
+    hash_key_t key;
+    int error;
+
+    child_ctx = talloc_get_type(ptr, struct sss_child_ctx);
+    key.type = HASH_KEY_ULONG;
+    key.ul = child_ctx->pid;
+
+    error = hash_delete(child_ctx->sigchld_ctx->children, &key);
+    if (error != HASH_SUCCESS && error != HASH_ERROR_KEY_NOT_FOUND) {
+        DEBUG(SSSDBG_TRACE_INTERNAL,
+              ("failed to delete child_ctx from hash table [%d]: %s\n",
+               error, hash_error_string(error)));
+    }
+
+    return 0;
+}
+
+errno_t sss_child_register(TALLOC_CTX *mem_ctx,
+                           struct sss_sigchild_ctx *sigchld_ctx,
+                           pid_t pid,
+                           sss_child_fn_t cb,
+                           void *pvt,
+                           struct sss_child_ctx **child_ctx)
+{
+    struct sss_child_ctx *child;
+    hash_key_t key;
+    hash_value_t value;
+    int error;
+
+    child = talloc_zero(mem_ctx, struct sss_child_ctx);
+    if (child == NULL) {
+        return ENOMEM;
+    }
+
+    child->pid = pid;
+    child->cb = cb;
+    child->pvt = pvt;
+
+    key.type = HASH_KEY_ULONG;
+    key.ul = pid;
+
+    value.type = HASH_VALUE_PTR;
+    value.ptr = child;
+
+    error = hash_enter(sigchld_ctx->children, &key, &value);
+    if (error != HASH_SUCCESS) {
+        talloc_free(child);
+        return ENOMEM;
+    }
+
+    talloc_set_destructor((TALLOC_CTX *) child, sss_child_destructor);
+
+    *child_ctx = child;
+    return EOK;
+}
+
+struct sss_child_cb_pvt {
+    struct sss_child_ctx *child_ctx;
+    int wait_status;
+};
+
+static void sss_child_invoke_cb(struct tevent_context *ev,
+                                struct tevent_immediate *imm,
+                                void *pvt)
+{
+    struct sss_child_cb_pvt *cb_pvt;
+    struct sss_child_ctx *child_ctx;
+    hash_key_t key;
+    int error;
+
+    cb_pvt = talloc_get_type(pvt, struct sss_child_cb_pvt);
+    child_ctx = cb_pvt->child_ctx;
+
+    if (child_ctx->cb) {
+        child_ctx->cb(child_ctx->pid, cb_pvt->wait_status, child_ctx->pvt);
+    }
+
+    key.type = HASH_KEY_ULONG;
+    key.ul = child_ctx->pid;
+
+    error = hash_delete(child_ctx->sigchld_ctx->children, &key);
+    if (error != HASH_SUCCESS && error != HASH_ERROR_KEY_NOT_FOUND) {
+        DEBUG(SSSDBG_TRACE_INTERNAL,
+              ("failed to delete child_ctx from hash table [%d]: %s\n",
+               error, hash_error_string(error)));
+    }
+}
+
+void sss_child_handler(struct tevent_context *ev,
+                       struct tevent_signal *se,
+                       int signum,
+                       int count,
+                       void *siginfo,
+                       void *private_data)
+{
+    struct sss_sigchild_ctx *sigchld_ctx;
+    struct tevent_immediate *imm;
+    struct sss_child_cb_pvt *invoke_pvt;
+    struct sss_child_ctx *child_ctx;
+    hash_key_t key;
+    hash_value_t value;
+    int error;
+    int wait_status;
+    pid_t pid;
+
+    sigchld_ctx = talloc_get_type(private_data, struct sss_sigchild_ctx);
+    key.type = HASH_KEY_ULONG;
+
+    do {
+        do {
+            errno = 0;
+            pid = waitpid(-1, &wait_status, WNOHANG & sigchld_ctx->options);
+        } while (pid == -1 && errno == EINTR);
+
+        if (pid == -1) {
+            DEBUG(SSSDBG_TRACE_INTERNAL,
+                  ("waitpid failed [%d]: %s\n", errno, strerror(errno)));
+            return;
+        }
+
+        key.ul = pid;
+        error = hash_lookup(sigchld_ctx->children, &key, &value);
+        if (error == HASH_SUCCESS) {
+            child_ctx = talloc_get_type(value.ptr, struct sss_child_ctx);
+
+            imm = tevent_create_immediate(sigchld_ctx->ev);
+            if (imm == NULL) {
+                DEBUG(SSSDBG_CRIT_FAILURE,
+                      ("Out of memory invoking SIGCHLD callback\n"));
+                return;
+            }
+
+            invoke_pvt = talloc_zero(child_ctx, struct sss_child_cb_pvt);
+            if (invoke_pvt == NULL) {
+                DEBUG(SSSDBG_CRIT_FAILURE,
+                      ("out of memory invoking SIGCHLD callback\n"));
+                return;
+            }
+            invoke_pvt->child_ctx = child_ctx;
+            invoke_pvt->wait_status = wait_status;
+
+            tevent_schedule_immediate(imm, sigchld_ctx->ev,
+                                      sss_child_invoke_cb, invoke_pvt);
+        } else if (error != HASH_ERROR_KEY_NOT_FOUND) {
+            DEBUG(SSSDBG_CRIT_FAILURE,
+                  ("SIGCHLD hash table error [%d]: %s\n",
+                   error, hash_error_string(error)));
+            /* This is bad, but we should try to check for other
+             * children anyway, to avoid potential zombies.
+             */
+        }
+    } while (pid != 0);
+}
+
+struct sss_child_ctx_old {
     struct tevent_signal *sige;
     pid_t pid;
     int child_status;
@@ -44,11 +250,11 @@ struct sss_child_ctx {
 int child_handler_setup(struct tevent_context *ev, int pid,
                         sss_child_callback_t cb, void *pvt)
 {
-    struct sss_child_ctx *child_ctx;
+    struct sss_child_ctx_old *child_ctx;
 
     DEBUG(8, ("Setting up signal handler up for pid [%d]\n", pid));
 
-    child_ctx = talloc_zero(ev, struct sss_child_ctx);
+    child_ctx = talloc_zero(ev, struct sss_child_ctx_old);
     if (child_ctx == NULL) {
         return ENOMEM;
     }
@@ -300,7 +506,7 @@ void child_sig_handler(struct tevent_context *ev,
                        int count, void *__siginfo, void *pvt)
 {
     int ret, err;
-    struct sss_child_ctx *child_ctx;
+    struct sss_child_ctx_old *child_ctx;
     struct tevent_immediate *imm;
 
     if (count <= 0) {
@@ -308,7 +514,7 @@ void child_sig_handler(struct tevent_context *ev,
         return;
     }
 
-    child_ctx = talloc_get_type(pvt, struct sss_child_ctx);
+    child_ctx = talloc_get_type(pvt, struct sss_child_ctx_old);
     DEBUG(7, ("Waiting for child [%d].\n", child_ctx->pid));
 
     errno = 0;
@@ -363,8 +569,8 @@ static void child_invoke_callback(struct tevent_context *ev,
                                   struct tevent_immediate *imm,
                                   void *pvt)
 {
-    struct sss_child_ctx *child_ctx =
-            talloc_get_type(pvt, struct sss_child_ctx);
+    struct sss_child_ctx_old *child_ctx =
+            talloc_get_type(pvt, struct sss_child_ctx_old);
     if (child_ctx->cb) {
         child_ctx->cb(child_ctx->child_status, child_ctx->sige, child_ctx->pvt);
     }
diff --git a/src/providers/child_common.h b/src/providers/child_common.h
index 22a77dbbe28c385c7c0e49ce8a339a68028b3658..1e9f1b6c152686ba75d5207ba48f0f131a4a2fff 100644
--- a/src/providers/child_common.h
+++ b/src/providers/child_common.h
@@ -45,6 +45,31 @@ struct io_buffer {
     size_t size;
 };
 
+/* COMMON SIGCHLD HANDLING */
+typedef void (*sss_child_fn_t)(int pid, int wait_status, void *pvt);
+
+struct sss_sigchild_ctx;
+struct sss_child_ctx;
+
+/* Create a new child context to manage callbacks */
+errno_t sss_sigchld_init(TALLOC_CTX *mem_ctx,
+                         struct tevent_context *ev,
+                         struct sss_sigchild_ctx **child_ctx);
+
+errno_t sss_child_register(TALLOC_CTX *mem_ctx,
+                           struct sss_sigchild_ctx *sigchld_ctx,
+                           pid_t pid,
+                           sss_child_fn_t cb,
+                           void *pvt,
+                           struct sss_child_ctx **child_ctx);
+
+void sss_child_handler(struct tevent_context *ev,
+                       struct tevent_signal *se,
+                       int signum,
+                       int count,
+                       void *siginfo,
+                       void *private_data);
+
 /* Callback to be invoked when a sigchld handler is called.
  * The tevent_signal * associated with the handler will be
  * freed automatically when this function returns.
diff --git a/src/providers/data_provider_be.c b/src/providers/data_provider_be.c
index 6557f9bca25865e34afea5aebc71954587f50977..4910aa6fc50429bf2a6367ce9ffa90e5689d5c91 100644
--- a/src/providers/data_provider_be.c
+++ b/src/providers/data_provider_be.c
@@ -42,6 +42,7 @@
 #include "sbus/sssd_dbus.h"
 #include "providers/dp_backend.h"
 #include "providers/fail_over.h"
+#include "providers/child_common.h"
 #include "resolv/async_resolv.h"
 #include "monitor/monitor_interfaces.h"
 
@@ -1176,6 +1177,14 @@ int be_process_init(TALLOC_CTX *mem_ctx,
         return EIO;
     }
 
+    ret = sss_sigchld_init(ctx, ctx->ev, &ctx->sigchld_ctx);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_FATAL_FAILURE,
+              ("Could not initialize sigchld context: [%s]\n",
+               strerror(ret)));
+        return ret;
+    }
+
     return EOK;
 }
 
diff --git a/src/providers/dp_backend.h b/src/providers/dp_backend.h
index 3d5e40bae43b326c00ce9464abfca1e5918ced01..0c24b82c9687b75a69f1559a16647fb0fb86c00d 100644
--- a/src/providers/dp_backend.h
+++ b/src/providers/dp_backend.h
@@ -24,6 +24,7 @@
 
 #include "providers/data_provider.h"
 #include "providers/fail_over.h"
+#include "providers/child_common.h"
 #include "db/sysdb.h"
 
 /* a special token, if used in place of the hostname, denotes that real
@@ -93,6 +94,7 @@ struct be_ctx {
     const char *identity;
     const char *conf_path;
     struct be_failover_ctx *be_fo;
+    struct sss_sigchild_ctx *sigchld_ctx;
 
     /* Functions to be invoked when the
      * backend goes online or offline
-- 
1.7.7.3

