From 2a94645726f5fa8e1dfb4f2d33d63f2e97529515 Mon Sep 17 00:00:00 2001
From: Simo Sorce <simo@redhat.com>
Date: Tue, 12 Jan 2016 20:07:59 -0500
Subject: [PATCH 1/3] Util: Add watchdog helper

The watchdog uses a kernel timer to issue a signal to the process.
It checks if the ticker is not being reset by the main event loop, which
would indicate that the process got stuck.
At the same time it sets a tevent timer to clear the watchdog ticker, so
that the watchdog handler is kept happy.

If the watchdog detects that the timer event failed to reset the watchdog for
three times in a row then the process is killed.
Normally the monitor will detect the child terminated and will rescheduled it.

Related:
https://fedorahosted.org/sssd/ticket/2921
---
 Makefile.am              |   1 +
 src/util/util.h          |   4 ++
 src/util/util_watchdog.c | 142 +++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 147 insertions(+)
 create mode 100644 src/util/util_watchdog.c

diff --git a/Makefile.am b/Makefile.am
index a9d3f25d3775f6ac824b9f9b85dd0412417c33d3..407053b1a6dcd0255be76ae7f9252a671b965ea2 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -858,6 +858,7 @@ libsss_util_la_SOURCES = \
     src/util/well_known_sids.c \
     src/util/string_utils.c \
     src/util/become_user.c \
+    src/util/util_watchdog.c \
     $(NULL)
 libsss_util_la_CFLAGS = \
     $(AM_CFLAGS) \
diff --git a/src/util/util.h b/src/util/util.h
index e1245bb0fbab742dd58522f1892c66d08a45b59c..00a06635e2449d86006b65f05a11b0a48628ca3d 100644
--- a/src/util/util.h
+++ b/src/util/util.h
@@ -698,4 +698,8 @@ int sss_unique_file(TALLOC_CTX *owner,
  */
 int sss_unique_filename(TALLOC_CTX *owner, char *path_tmpl);
 
+/* from util_watchdog.c */
+int setup_watchdog(struct tevent_context *ev, int interval);
+void teardown_watchdog(void);
+
 #endif /* __SSSD_UTIL_H__ */
diff --git a/src/util/util_watchdog.c b/src/util/util_watchdog.c
new file mode 100644
index 0000000000000000000000000000000000000000..64faaaf03213319d3127fde3946124ee26c7794a
--- /dev/null
+++ b/src/util/util_watchdog.c
@@ -0,0 +1,142 @@
+/*
+   SSSD
+
+   Timer Watchdog routines
+
+   Copyright (C) Simo Sorce             2016
+
+   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 "util/util.h"
+
+#define WATCHDOG_DEF_INTERVAL 10
+
+/* this is intentionally a global variable */
+struct watchdog_ctx {
+    timer_t timerid;
+    struct timeval interval;
+    struct tevent_timer *te;
+    volatile int ticks;
+} watchdog_ctx;
+
+/* the watchdog is purposefully *not* handled by the tevent
+ * signal handler as it is meant to check if the daemon is
+ * still processing the event queue itself. A stuck process
+ * may not handle the event queue at all and thus not handle
+ * signals either */
+static void watchdog_handler(int sig)
+{
+    /* if 3 ticks passed by kills itself */
+
+    if (__sync_add_and_fetch(&watchdog_ctx.ticks, 1) > 3) {
+        DEBUG(SSSDBG_FATAL_FAILURE,
+              "Watchdog timer overflow, killing process!\n");
+        orderly_shutdown(1);
+    }
+}
+
+static void watchdog_reset(void)
+{
+    __sync_and_and_fetch(&watchdog_ctx.ticks, 0);
+}
+
+static void watchdog_event_handler(struct tevent_context *ev,
+                                   struct tevent_timer *te,
+                                   struct timeval current_time,
+                                   void *private_data)
+{
+    /* first thing reset the watchdog ticks */
+    watchdog_reset();
+
+    /* then set a new watchodg event */
+    watchdog_ctx.te = tevent_add_timer(ev, ev,
+        tevent_timeval_current_ofs(watchdog_ctx.interval.tv_sec, 0),
+        watchdog_event_handler, NULL);
+    /* if the function fails the watchdog will kill the
+     * process soon enough, so we just warn */
+    if (!watchdog_ctx.te) {
+        DEBUG(SSSDBG_FATAL_FAILURE,
+              "Failed to create a watchdog timer event!\n");
+    }
+}
+
+int setup_watchdog(struct tevent_context *ev, int interval)
+{
+    struct sigevent sev;
+    struct itimerspec its;
+    int signum = SIGRTMIN;
+    int ret;
+
+    CatchSignal(signum, watchdog_handler);
+
+    sev.sigev_notify = SIGEV_SIGNAL;
+    sev.sigev_signo = signum;
+    sev.sigev_value.sival_ptr = &watchdog_ctx.timerid;
+    errno = 0;
+    ret = timer_create(CLOCK_MONOTONIC, &sev, &watchdog_ctx.timerid);
+    if (ret == -1) {
+        ret = errno;
+        DEBUG(SSSDBG_FATAL_FAILURE,
+              "Failed to create watchdog timer (%d) [%s]\n",
+              ret, strerror(ret));
+        return ret;
+    }
+
+    if (interval == 0) {
+        interval = WATCHDOG_DEF_INTERVAL;
+    }
+    watchdog_ctx.interval.tv_sec = interval;
+    watchdog_ctx.interval.tv_usec = 0;
+
+    /* Start the timer */
+    /* we give 1 second head start to the watchdog event */
+    its.it_value.tv_sec = interval + 1;
+    its.it_value.tv_nsec = 0;
+    its.it_interval.tv_sec = interval;
+    its.it_interval.tv_nsec = 0;
+    errno = 0;
+    ret = timer_settime(watchdog_ctx.timerid, 0, &its, NULL);
+    if (ret == -1) {
+        ret = errno;
+        DEBUG(SSSDBG_FATAL_FAILURE,
+              "Failed to create watchdog timer (%d) [%s]\n",
+              ret, strerror(ret));
+        return ret;
+    }
+
+    /* Add the watchdog event and make it fire as fast as the timer */
+    watchdog_event_handler(ev, NULL, tevent_timeval_zero(), NULL);
+
+    return EOK;
+}
+
+void teardown_watchdog(void)
+{
+    int ret;
+
+    /* Disarm the timer */
+    errno = 0;
+    ret = timer_delete(watchdog_ctx.timerid);
+    if (ret == -1) {
+        ret = errno;
+        DEBUG(SSSDBG_FATAL_FAILURE,
+              "Failed to destroy watchdog timer (%d) [%s]\n",
+             ret, strerror(ret));
+    }
+
+    /* and kill the watchdog event */
+    talloc_free(watchdog_ctx.te);
+}
+
-- 
2.5.0

