>From 5f1a1b91a28f648a3bc8159139a069d9735ecfc1 Mon Sep 17 00:00:00 2001
From: Karel Klic <kklic@redhat.com>
Date: Wed, 16 Jun 2010 16:05:09 +0200
Subject: [PATCH] Cron code improvements

---
 src/Daemon/Daemon.cpp  |  165 +-------------------------
 src/Daemon/Makefile.am |    3 +-
 src/Daemon/cron.cpp    |  305 ++++++++++++++++++++++++++++++++++++++++++++++++
 src/Daemon/cron.h      |   31 +++++
 4 files changed, 343 insertions(+), 161 deletions(-)
 create mode 100644 src/Daemon/cron.cpp
 create mode 100644 src/Daemon/cron.h

diff --git a/src/Daemon/Daemon.cpp b/src/Daemon/Daemon.cpp
index 139f4fa..ad5ae84 100644
--- a/src/Daemon/Daemon.cpp
+++ b/src/Daemon/Daemon.cpp
@@ -42,10 +42,10 @@
 #include "DebugDump.h"
 #include "Daemon.h"
 #include "dumpsocket.h"
+#include "cron.h"
 
 using namespace std;
 
-
 /* Daemon initializes, then sits in glib main loop, waiting for events.
  * Events can be:
  * - inotify: something new appeared under /var/spool/abrt
@@ -89,25 +89,6 @@ using namespace std;
 #define VAR_RUN_LOCK_FILE   VAR_RUN"/abrtd.lock"
 #define VAR_RUN_PIDFILE     VAR_RUN"/abrtd.pid"
 
-
-//FIXME: add some struct to be able to join all threads!
-typedef struct cron_callback_data_t
-{
-    std::string m_sPluginName;
-    std::string m_sPluginArgs;
-    unsigned int m_nTimeout;
-
-    cron_callback_data_t(
-                      const std::string& pPluginName,
-                      const std::string& pPluginArgs,
-                      const unsigned int& pTimeout) :
-        m_sPluginName(pPluginName),
-        m_sPluginArgs(pPluginArgs),
-        m_nTimeout(pTimeout)
-    {}
-} cron_callback_data_t;
-
-
 static volatile sig_atomic_t s_sig_caught;
 static int s_signal_pipe[2];
 static int s_signal_pipe_write = -1;
@@ -116,49 +97,6 @@ static bool s_exiting;
 
 CCommLayerServer* g_pCommLayer;
 
-
-static void cron_delete_callback_data_cb(gpointer data)
-{
-    cron_callback_data_t* cronDeleteCallbackData = static_cast<cron_callback_data_t*>(data);
-    delete cronDeleteCallbackData;
-}
-
-static gboolean cron_activation_periodic_cb(gpointer data)
-{
-    cron_callback_data_t* cronPeriodicCallbackData = static_cast<cron_callback_data_t*>(data);
-    VERB1 log("Activating plugin: %s", cronPeriodicCallbackData->m_sPluginName.c_str());
-    RunAction(DEBUG_DUMPS_DIR,
-            cronPeriodicCallbackData->m_sPluginName.c_str(),
-            cronPeriodicCallbackData->m_sPluginArgs.c_str()
-    );
-    return TRUE;
-}
-static gboolean cron_activation_one_cb(gpointer data)
-{
-    cron_callback_data_t* cronOneCallbackData = static_cast<cron_callback_data_t*>(data);
-    VERB1 log("Activating plugin: %s", cronOneCallbackData->m_sPluginName.c_str());
-    RunAction(DEBUG_DUMPS_DIR,
-            cronOneCallbackData->m_sPluginName.c_str(),
-            cronOneCallbackData->m_sPluginArgs.c_str()
-    );
-    return FALSE;
-}
-static gboolean cron_activation_reshedule_cb(gpointer data)
-{
-    cron_callback_data_t* cronResheduleCallbackData = static_cast<cron_callback_data_t*>(data);
-    VERB1 log("Rescheduling plugin: %s", cronResheduleCallbackData->m_sPluginName.c_str());
-    cron_callback_data_t* cronPeriodicCallbackData = new cron_callback_data_t(cronResheduleCallbackData->m_sPluginName,
-                                                                              cronResheduleCallbackData->m_sPluginArgs,
-                                                                              cronResheduleCallbackData->m_nTimeout);
-    g_timeout_add_seconds_full(G_PRIORITY_DEFAULT,
-                               cronPeriodicCallbackData->m_nTimeout,
-                               cron_activation_periodic_cb,
-                               static_cast<gpointer>(cronPeriodicCallbackData),
-                               cron_delete_callback_data_cb
-    );
-    return FALSE;
-}
-
 static int SetUpMW()
 {
     set_string_t::iterator it_k = g_settings_setOpenGPGPublicKeys.begin();
@@ -185,102 +123,6 @@ static int SetUpMW()
     return 0;
 }
 
-static int SetUpCron()
-{
-    map_cron_t::iterator it_c = g_settings_mapCron.begin();
-    for (; it_c != g_settings_mapCron.end(); it_c++)
-    {
-        std::string::size_type pos = it_c->first.find(":");
-        int timeout = 0;
-        int nH = -1;
-        int nM = -1;
-        int nS = -1;
-
-//TODO: rewrite using good old sscanf?
-
-        if (pos != std::string::npos)
-        {
-            std::string sH;
-            std::string sM;
-
-            sH = it_c->first.substr(0, pos);
-            nH = xatou(sH.c_str());
-            nH = nH > 23 ? 23 : nH;
-            nH = nH < 0 ? 0 : nH;
-            timeout += nH * 60 * 60;
-            sM = it_c->first.substr(pos + 1);
-            nM = xatou(sM.c_str());
-            nM = nM > 59 ? 59 : nM;
-            nM = nM < 0 ? 0 : nM;
-            timeout += nM * 60;
-        }
-        else
-        {
-            std::string sS;
-
-            sS = it_c->first;
-            nS = xatou(sS.c_str());
-            nS = nS <= 0 ? 1 : nS;
-            timeout = nS;
-        }
-
-        if (nS != -1)
-        {
-            vector_pair_string_string_t::iterator it_ar = it_c->second.begin();
-            for (; it_ar != it_c->second.end(); it_ar++)
-            {
-                cron_callback_data_t* cronPeriodicCallbackData = new cron_callback_data_t(it_ar->first, it_ar->second, timeout);
-                g_timeout_add_seconds_full(G_PRIORITY_DEFAULT,
-                                           timeout,
-                                           cron_activation_periodic_cb,
-                                           static_cast<gpointer>(cronPeriodicCallbackData),
-                                           cron_delete_callback_data_cb);
-            }
-        }
-        else
-        {
-            time_t actTime = time(NULL);
-            struct tm locTime;
-            localtime_r(&actTime, &locTime);
-            locTime.tm_hour = nH;
-            locTime.tm_min = nM;
-            locTime.tm_sec = 0;
-            time_t nextTime = mktime(&locTime);
-            if (nextTime == ((time_t)-1))
-            {
-                /* paranoia */
-                perror_msg("Can't set up cron time");
-                return -1;
-            }
-            if (actTime > nextTime)
-            {
-                timeout = 24*60*60 + (nextTime - actTime);
-            }
-            else
-            {
-                timeout = nextTime - actTime;
-            }
-            vector_pair_string_string_t::iterator it_ar = it_c->second.begin();
-            for (; it_ar != it_c->second.end(); it_ar++)
-            {
-                cron_callback_data_t* cronOneCallbackData = new cron_callback_data_t(it_ar->first, it_ar->second, timeout);
-                g_timeout_add_seconds_full(G_PRIORITY_DEFAULT,
-                                           timeout,
-                                           cron_activation_one_cb,
-                                           static_cast<gpointer>(cronOneCallbackData),
-                                           cron_delete_callback_data_cb);
-                cron_callback_data_t* cronResheduleCallbackData = new cron_callback_data_t(it_ar->first, it_ar->second, 24 * 60 * 60);
-                g_timeout_add_seconds_full(G_PRIORITY_DEFAULT,
-                                           timeout,
-                                           cron_activation_reshedule_cb,
-                                           static_cast<gpointer>(cronResheduleCallbackData),
-                                           cron_delete_callback_data_cb);
-            }
-        }
-    }
-    return 0;
-}
-
 static void FindNewDumps(const char* pPath)
 {
     /* Get all debugdump directories in the pPath directory */
@@ -831,7 +673,7 @@ int main(int argc, char** argv)
 
         if (SetUpMW() != 0) /* logging is inside */
             throw 1;
-        if (SetUpCron() != 0)
+        if (cron_init() != 0)
             throw 1;
 
         VERB1 log("Adding inotify watch to glib main loop");
@@ -915,6 +757,7 @@ int main(int argc, char** argv)
      * Take care to not undo things we did not do.
      */
     dumpsocket_shutdown();
+
     if (pidfile_created)
         unlink(VAR_RUN_PIDFILE);
     if (lockfile_created)
@@ -929,6 +772,8 @@ int main(int argc, char** argv)
     if (channel_inotify)
         g_io_channel_unref(channel_inotify);
 
+    cron_shutdown();
+
     delete g_pCommLayer;
     if (g_pPluginManager)
     {
diff --git a/src/Daemon/Makefile.am b/src/Daemon/Makefile.am
index 5629215..a255a1f 100644
--- a/src/Daemon/Makefile.am
+++ b/src/Daemon/Makefile.am
@@ -11,7 +11,8 @@ abrtd_SOURCES = \
     CommLayerServerDBus.h CommLayerServerDBus.cpp \
     Daemon.h Daemon.cpp \
     Settings.h Settings.cpp \
-    dumpsocket.h dumpsocket.cpp
+    dumpsocket.h dumpsocket.cpp \
+    cron.h cron.cpp
 abrtd_CPPFLAGS = \
     -I$(srcdir)/../../inc \
     -I$(srcdir)/../../lib/Utils \
diff --git a/src/Daemon/cron.cpp b/src/Daemon/cron.cpp
new file mode 100644
index 0000000..ec9032e
--- /dev/null
+++ b/src/Daemon/cron.cpp
@@ -0,0 +1,305 @@
+/*
+    Copyright (C) 2009, 2010  Red Hat, Inc.
+
+    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 Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+#include "cron.h"
+#include <glib.h>
+#include "abrtlib.h"
+#include "MiddleWare.h"
+#include "Settings.h"
+
+typedef struct callback_data_t
+{
+    char *plugin_name;
+    char *plugin_args;
+    /** Used only by activation_daily_cb(). */
+    int hours;
+    /** Used only by activation_daily_cb(). */
+    int minutes;
+    /** GLib event source ID */
+    guint event_id;
+} callback_data_t;
+
+/**
+ * List of all currently active callback_data.
+ * Required to unregister events from GLib's main loop
+ * in cron_shutdown().
+ */
+GSList *callback_data_list = NULL;
+
+/**
+ * @todo
+ *  Releasing memory to plugin_name and plugin_args
+ *  should not be needed after Settings rewrite to C.
+ *  Those pointers can point directly to the settings.
+ */
+static void delete_callback_data_cb(gpointer data)
+{
+    VERB3 log("Deleting callback data for a cron timer.");
+    callback_data_t *cbdata = (callback_data_t*)data;
+    callback_data_list = g_slist_remove(callback_data_list, cbdata);
+    free(cbdata->plugin_name);
+    free(cbdata->plugin_args);
+    free(cbdata);
+}
+
+/**
+ * Registers a new Cron callback to the main loop.
+ * @param timeout
+ *  Number of seconds before callback is called.
+ * @param hours
+ * @param minutes
+ *  Time of day when the callback should be activated, every day.
+ *  Use with callback function activation_daily_cb().
+ * @param callback
+ *  Callback function 'gboolean (*)(gpointer data)'.
+ *  The data pointer points to callback_data_t structure
+ *  created by this function.
+ * @todo
+ *  Duplicating plugin_name and plugin_args
+ *  should not be needed after Settings rewrite to C.
+ *  Those pointers can point directly to the settings.
+ */
+static void register_cb(const char *plugin_name,
+                        const char *plugin_args,
+                        int hours,
+                        int minutes,
+                        int timeout,
+                        GSourceFunc callback)
+{
+    VERB3 log("Registering CRON callback %s (args=\"%s\" timeout=%d).",
+              plugin_name,
+              plugin_args,
+              timeout);
+
+    callback_data_t *data = (callback_data_t*)xmalloc(sizeof(callback_data_t));
+    data->plugin_name = xstrdup(plugin_name);
+    data->plugin_args = xstrdup(plugin_args);
+    data->hours = hours;
+    data->minutes = minutes;
+
+    /* Add data to the global list. */
+    callback_data_list = g_slist_prepend(callback_data_list, data);
+
+    data->event_id = g_timeout_add_seconds_full(G_PRIORITY_DEFAULT,
+                                                timeout,
+                                                callback,
+                                                (gpointer)data,
+                                                delete_callback_data_cb);
+}
+
+/**
+ * Callback for plugins activated every n seconds.
+ */
+static gboolean activation_periodic_cb(gpointer data)
+{
+    callback_data_t* cbdata = (callback_data_t*)data;
+    VERB1 log("Activating plugin: %s", cbdata->plugin_name);
+    RunAction(DEBUG_DUMPS_DIR,
+              cbdata->plugin_name,
+              cbdata->plugin_args);
+
+    return TRUE;
+}
+
+/**
+ * Gets the number of seconds until the nearest hh:mm
+ * time in future (today or tomorrow).
+ * @param timeout
+ *  Result, in seconds.
+ * @returns
+ *  0 when successful, -1 otherwise.
+ */
+static int timeout_from_now(int hours, int minutes, int *timeout)
+{
+    time_t acttime = time(NULL);
+    struct tm loctime;
+    localtime_r(&acttime, &loctime);
+    loctime.tm_hour = hours;
+    loctime.tm_min = minutes;
+    loctime.tm_sec = 0;
+    time_t nexttime = mktime(&loctime);
+    if (nexttime == ((time_t)-1))
+    {
+        /* paranoia */
+        perror_msg("Can't set up cron time");
+        return -1;
+    }
+
+    if (acttime > nexttime)
+        *timeout = 24 * 60 * 60 + (nexttime - acttime);
+    else
+        *timeout = nexttime - acttime;
+
+    return 0;
+}
+
+/**
+ * Callback for plugins activated every day at the same time.
+ */
+static gboolean activation_daily_cb(gpointer data)
+{
+    callback_data_t* cbdata = (callback_data_t*)data;
+    VERB1 log("Activating plugin: %s", cbdata->plugin_name);
+    RunAction(DEBUG_DUMPS_DIR,
+              cbdata->plugin_name,
+              cbdata->plugin_args);
+
+    /* Get the number of seconds until the same time tomorrow. */
+    int timeout;
+    if (timeout_from_now(cbdata->hours, cbdata->minutes, &timeout) != 0)
+        timeout = 24 * 60 * 60; /* a day */
+
+    VERB1 log("Rescheduling plugin: %s", cbdata->plugin_name);
+    register_cb(cbdata->plugin_name,
+                cbdata->plugin_args,
+                cbdata->hours,
+                cbdata->minutes,
+                timeout,
+                activation_daily_cb);
+
+    return FALSE;
+}
+
+/**
+ * Parses time in format "hh:mm", or "ss".
+ * @param hours
+ * @param minutes
+ * @param seconds
+ *  Output parameters. This function sets the parameter to -1 when
+ *  it is not present in the input.
+ * @returns
+ *  0 if the parsing was successful, -1 otherwise.
+ */
+static int parse_time(const char *time,
+                      int *hours,
+                      int *minutes,
+                      int *seconds)
+{
+    if (strchr(time, ':')) /* if time contains ':' */
+    {
+        if (sscanf(time, "%u:%u", hours, minutes) != 2 || (unsigned)*hours > 23 || (unsigned)*minutes > 59)
+        {
+            error_msg("Failed to parse CRON time \"%s\"", time);
+            return -1;
+        }
+
+        *seconds = -1;
+    }
+    else
+    {
+        *hours = -1;
+        *minutes = -1;
+        *seconds = MAX(1, xatou(time));
+    }
+    return 0;
+}
+
+/**
+ * Creates and starts a GLib timer for every plugin in action_plugins.
+ * The timers are activated every n seconds.
+ */
+static void register_cron_periodic(int seconds,
+                                   const vector_pair_string_string_t &action_plugins)
+{
+    vector_pair_string_string_t::const_iterator it = action_plugins.begin();
+    for (; it != action_plugins.end(); ++it)
+    {
+        register_cb(it->first.c_str(),
+                    it->second.c_str(),
+                    -1, /* hours not used */
+                    -1, /* minutes not used */
+                    seconds,
+                    activation_periodic_cb);
+    }
+}
+
+/**
+ * Creates and starts a GLib timer for every plugin in action_plugins.
+ * The timers are activated every day at the same hh:mm.
+ */
+static int register_cron_daily(int hours,
+                               int minutes,
+                               const vector_pair_string_string_t &action_plugins)
+{
+    int timeout;
+    if (timeout_from_now(hours, minutes, &timeout) != 0)
+        return -1;
+
+    vector_pair_string_string_t::const_iterator it = action_plugins.begin();
+    for (; it != action_plugins.end(); ++it)
+    {
+        register_cb(it->first.c_str(),
+                    it->second.c_str(),
+                    hours,
+                    minutes,
+                    timeout,
+                    activation_daily_cb);
+    }
+    return 0;
+}
+
+/**
+ * Handles time entry from settings such as "120 = KerneloopsScanner"
+ * (time == "120", action_plugins == (<"KerneloopsScanner", "">)).
+ * When time is in format hour:minute, the action plugin
+ * is run at that time every day. If time is just a number, the action
+ * plugin is run every `time' seconds.
+ */
+static int process_entry(const char *time,
+                         const vector_pair_string_string_t &action_plugins)
+{
+    int hours, minutes, seconds;
+    if (parse_time(time, &hours, &minutes, &seconds) != 0)
+        return -1;
+
+    if (seconds != -1)
+        register_cron_periodic(seconds, action_plugins);
+    else
+    {
+        if (register_cron_daily(hours, minutes, action_plugins) != 0)
+            return -1;
+    }
+
+    return 0;
+}
+
+int cron_init()
+{
+    map_cron_t::const_iterator it = g_settings_mapCron.begin();
+    for (; it != g_settings_mapCron.end(); ++it)
+    {
+        if (process_entry(it->first.c_str(), it->second) != 0)
+            return -1;
+    }
+    return 0;
+}
+
+void cron_shutdown()
+{
+    /* Take the first item from callback_data_list, and
+     * unregister the associated timer from the event loop.
+     * This action results in removing the item from
+     * callback_data_list (it happens in delete_callback_data_cb()).
+     * Remove all items from the list this way.
+     */
+    while (callback_data_list)
+    {
+        callback_data_t *data = (callback_data_t*)callback_data_list->data;
+        VERB3 log("Removing cron timer from event loop: %s.", data->plugin_name);
+        g_source_remove(data->event_id);
+    }
+}
diff --git a/src/Daemon/cron.h b/src/Daemon/cron.h
new file mode 100644
index 0000000..f07f02c
--- /dev/null
+++ b/src/Daemon/cron.h
@@ -0,0 +1,31 @@
+/*
+    Copyright (C) 2010  Red Hat, Inc.
+
+    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 Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+#ifndef ABRT_CRON_H
+#define ABRT_CRON_H
+
+/**
+ * Initializes cron infrastructure from settings.
+ */
+int cron_init();
+
+/**
+ * Releases all resources allocated by cron.
+ */
+void cron_shutdown();
+
+#endif
-- 
1.6.6.1

