dirmngr: Add a background task framework.
authorWerner Koch <wk@gnupg.org>
Tue, 14 Nov 2017 12:42:18 +0000 (13:42 +0100)
committerWerner Koch <wk@gnupg.org>
Tue, 14 Nov 2017 12:42:18 +0000 (13:42 +0100)
* dirmngr/workqueue.c: New.
* dirmngr/Makefile.am (dirmngr_SOURCES): Add new file.
* dirmngr/server.c (server_local_s): New field session_id.
(cmd_wkd_get): Add a task.
(task_check_wkd_support): New stub function.
(cmd_getinfo): New sub-commands "session_id" and "workqueue".
(start_command_handler): Add arg session_id and store it in
SERVER_LOCAL.
(dirmngr_status_helpf): New.
* dirmngr/dirmngr.h (wqtask_t): New type.
* dirmngr/dirmngr.c (main): Pass 0 as session_id to
start_command_handler.
(start_connection_thread): Introduce a session_id and pass it to
start_command_handler.  Run post session tasks.
(housekeeping_thread): Run global workqueue tasks.
--

Signed-off-by: Werner Koch <wk@gnupg.org>
dirmngr/Makefile.am
dirmngr/dirmngr.c
dirmngr/dirmngr.h
dirmngr/server.c
dirmngr/workqueue.c [new file with mode: 0644]

index 421a325..43f59bd 100644 (file)
@@ -60,6 +60,7 @@ noinst_HEADERS = dirmngr.h crlcache.h crlfetch.h misc.h
 dirmngr_SOURCES = dirmngr.c dirmngr.h server.c crlcache.c crlfetch.c   \
        certcache.c certcache.h \
        domaininfo.c \
+       workqueue.c \
        loadswdb.c \
        cdb.h cdblib.c misc.c dirmngr-err.h  \
        ocsp.c ocsp.h validate.c validate.h  \
index 2b64655..9cb0203 100644 (file)
@@ -1134,7 +1134,7 @@ main (int argc, char **argv)
       cert_cache_init (hkp_cacert_filenames);
       crl_cache_init ();
       http_register_netactivity_cb (netactivity_action);
-      start_command_handler (ASSUAN_INVALID_FD);
+      start_command_handler (ASSUAN_INVALID_FD, 0);
       shutdown_reaper ();
     }
 #ifndef HAVE_W32_SYSTEM
@@ -1939,7 +1939,10 @@ housekeeping_thread (void *arg)
       network_activity_seen = 0;
       if (opt.allow_version_check)
         dirmngr_load_swdb (&ctrlbuf, 0);
+      workqueue_run_global_tasks (&ctrlbuf, 1);
     }
+  else
+    workqueue_run_global_tasks (&ctrlbuf, 0);
 
   dirmngr_deinit_default_ctrl (&ctrlbuf);
 
@@ -2034,6 +2037,8 @@ check_nonce (assuan_fd_t fd, assuan_sock_nonce_t *nonce)
 static void *
 start_connection_thread (void *arg)
 {
+  static unsigned int last_session_id;
+  unsigned int session_id;
   union int_and_ptr_u argval;
   gnupg_fd_t fd;
 
@@ -2055,12 +2060,17 @@ start_connection_thread (void *arg)
   if (opt.verbose)
     log_info (_("handler for fd %d started\n"), FD2INT (fd));
 
-  start_command_handler (fd);
+  session_id = ++last_session_id;
+  if (!session_id)
+    session_id = ++last_session_id;
+  start_command_handler (fd, session_id);
 
   if (opt.verbose)
     log_info (_("handler for fd %d terminated\n"), FD2INT (fd));
   active_connections--;
 
+  workqueue_run_post_session_tasks (session_id);
+
 #ifndef HAVE_W32_SYSTEM
   argval.afd = ASSUAN_INVALID_FD;
   npth_setspecific (my_tlskey_current_fd, argval.aptr);
index b08e4fe..5189f93 100644 (file)
@@ -228,9 +228,11 @@ ksba_cert_t get_cert_local_ski (ctrl_t ctrl,
 gpg_error_t get_istrusted_from_client (ctrl_t ctrl, const char *hexfpr);
 int dirmngr_assuan_log_monitor (assuan_context_t ctx, unsigned int cat,
                                 const char *msg);
-void start_command_handler (gnupg_fd_t fd);
+void start_command_handler (gnupg_fd_t fd, unsigned int session_id);
 gpg_error_t dirmngr_status (ctrl_t ctrl, const char *keyword, ...);
 gpg_error_t dirmngr_status_help (ctrl_t ctrl, const char *text);
+gpg_error_t dirmngr_status_helpf (ctrl_t ctrl, const char *format,
+                                  ...) GPGRT_ATTR_PRINTF(2,3);
 gpg_error_t dirmngr_status_printf (ctrl_t ctrl, const char *keyword,
                                    const char *format,
                                    ...) GPGRT_ATTR_PRINTF(3,4);
@@ -258,6 +260,15 @@ void domaininfo_set_wkd_supported (const char *domain);
 void domaininfo_set_wkd_not_supported (const char *domain);
 void domaininfo_set_wkd_not_found (const char *domain);
 
+/*-- workqueue.c --*/
+typedef const char *(*wqtask_t)(ctrl_t ctrl, const char *args);
+
+void workqueue_dump_queue (ctrl_t ctrl);
+gpg_error_t workqueue_add_task (wqtask_t func, const char *args,
+                                unsigned int session_id, int need_network);
+void workqueue_run_global_tasks (ctrl_t ctrl, int with_network);
+void workqueue_run_post_session_tasks (unsigned int session_id);
+
 
 
 #endif /*DIRMNGR_H*/
index 18a5f72..1fbd007 100644 (file)
@@ -90,6 +90,9 @@ struct server_local_s
   /* Data used to associate an Assuan context with local server data */
   assuan_context_t assuan_ctx;
 
+  /* The session id (a counter).  */
+  unsigned int session_id;
+
   /* Per-session LDAP servers.  */
   ldap_server_t ldapservers;
 
@@ -125,6 +128,9 @@ static es_cookie_io_functions_t data_line_cookie_functions =
   };
 
 
+/* Local prototypes */
+static const char *task_check_wkd_support (ctrl_t ctrl, const char *domain);
+
 
 
 \f
@@ -992,8 +998,12 @@ cmd_wkd_get (assuan_context_t ctx, char *line)
             break;
 
           case GPG_ERR_NO_DATA:
-            if (is_wkd_query) /* Mark that - we will latter do a check.  */
-              domaininfo_set_wkd_not_found (domain_orig);
+            if (is_wkd_query) /* Mark that and schedule a check.  */
+              {
+                domaininfo_set_wkd_not_found (domain_orig);
+                workqueue_add_task (task_check_wkd_support, domain_orig,
+                                    ctrl->server_local->session_id, 1);
+              }
             else if (opt_policy_flags) /* No policy file - no support.  */
               domaininfo_set_wkd_not_supported (domain_orig);
             break;
@@ -1014,6 +1024,20 @@ cmd_wkd_get (assuan_context_t ctx, char *line)
 }
 
 
+/* A task to check whether DOMAIN supports WKD.  This is done by
+ * checking whether the policy flags file can be read.  */
+static const char *
+task_check_wkd_support (ctrl_t ctrl, const char *domain)
+{
+  if (!ctrl || !domain)
+    return "check_wkd_support";
+
+  log_debug ("FIXME: Implement %s\n", __func__);
+
+  return NULL;
+}
+
+
 \f
 static const char hlp_ldapserver[] =
   "LDAPSERVER <data>\n"
@@ -2428,12 +2452,15 @@ static const char hlp_getinfo[] =
   "pid         - Return the process id of the server.\n"
   "tor         - Return OK if running in Tor mode\n"
   "dnsinfo     - Return info about the DNS resolver\n"
-  "socket_name - Return the name of the socket.\n";
+  "socket_name - Return the name of the socket.\n"
+  "session_id  - Return the current session_id.\n"
+  "workqueue   - Inspect the work queue\n";
 static gpg_error_t
 cmd_getinfo (assuan_context_t ctx, char *line)
 {
   ctrl_t ctrl = assuan_get_pointer (ctx);
   gpg_error_t err;
+  char numbuf[50];
 
   if (!strcmp (line, "version"))
     {
@@ -2442,8 +2469,6 @@ cmd_getinfo (assuan_context_t ctx, char *line)
     }
   else if (!strcmp (line, "pid"))
     {
-      char numbuf[50];
-
       snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ());
       err = assuan_send_data (ctx, numbuf, strlen (numbuf));
     }
@@ -2452,6 +2477,11 @@ cmd_getinfo (assuan_context_t ctx, char *line)
       const char *s = dirmngr_get_current_socket_name ();
       err = assuan_send_data (ctx, s, strlen (s));
     }
+  else if (!strcmp (line, "session_id"))
+    {
+      snprintf (numbuf, sizeof numbuf, "%u", ctrl->server_local->session_id);
+      err = assuan_send_data (ctx, numbuf, strlen (numbuf));
+    }
   else if (!strcmp (line, "tor"))
     {
       int use_tor;
@@ -2487,6 +2517,11 @@ cmd_getinfo (assuan_context_t ctx, char *line)
         }
       err = 0;
     }
+  else if (!strcmp (line, "workqueue"))
+    {
+      workqueue_dump_queue (ctrl);
+      err = 0;
+    }
   else
     err = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT");
 
@@ -2614,9 +2649,10 @@ dirmngr_assuan_log_monitor (assuan_context_t ctx, unsigned int cat,
 
 
 /* Startup the server and run the main command loop.  With FD = -1,
-   use stdin/stdout. */
+ * use stdin/stdout.  SESSION_ID is either 0 or a unique number
+ * identifying a session.  */
 void
-start_command_handler (assuan_fd_t fd)
+start_command_handler (assuan_fd_t fd, unsigned int session_id)
 {
   static const char hello[] = "Dirmngr " VERSION " at your service";
   static char *hello_line;
@@ -2693,6 +2729,8 @@ start_command_handler (assuan_fd_t fd)
   assuan_register_option_handler (ctx, option_handler);
   assuan_register_reset_notify (ctx, reset_notify);
 
+  ctrl->server_local->session_id = session_id;
+
   for (;;)
     {
       rc = assuan_accept (ctx);
@@ -2792,8 +2830,7 @@ dirmngr_status (ctrl_t ctrl, const char *keyword, ...)
 }
 
 
-/* Print a help status line.  TEXTLEN gives the length of the text
-   from TEXT to be printed.  The function splits text at LFs.  */
+/* Print a help status line.  The function splits text at LFs.  */
 gpg_error_t
 dirmngr_status_help (ctrl_t ctrl, const char *text)
 {
@@ -2823,6 +2860,26 @@ dirmngr_status_help (ctrl_t ctrl, const char *text)
 }
 
 
+/* Print a help status line using a printf like format.  The function
+ * splits text at LFs.  */
+gpg_error_t
+dirmngr_status_helpf (ctrl_t ctrl, const char *format, ...)
+{
+  va_list arg_ptr;
+  gpg_error_t err;
+  char *buf;
+
+  va_start (arg_ptr, format);
+  buf = es_vbsprintf (format, arg_ptr);
+  err = buf? 0 : gpg_error_from_syserror ();
+  va_end (arg_ptr);
+  if (!err)
+    err = dirmngr_status_help (ctrl, buf);
+  es_free (buf);
+  return err;
+}
+
+
 /* This function is similar to print_assuan_status but takes a CTRL
  * arg instead of an assuan context as first argument.  */
 gpg_error_t
diff --git a/dirmngr/workqueue.c b/dirmngr/workqueue.c
new file mode 100644 (file)
index 0000000..2cb8573
--- /dev/null
@@ -0,0 +1,214 @@
+/* workqueue.c - Maintain a queue of background tasks
+ * Copyright (C) 2017 Werner Koch
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG 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.
+ *
+ * GnuPG 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 <https://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0+
+ */
+
+#include <config.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "dirmngr.h"
+
+
+/* An object for one item in the workqueue.  */
+struct wqitem_s
+{
+  struct wqitem_s *next;
+
+  /* This flag is set if the task requires network access.  */
+  unsigned int need_network:1;
+
+  /* The id of the session which created this task.  If this is 0 the
+   * task is not associated with a specific session.  */
+  unsigned int session_id;
+
+  /* The function to perform the backgrount task.  */
+  wqtask_t func;
+
+  /* A string with the string argument for that task.  */
+  char args[1];
+};
+typedef struct wqitem_s *wqitem_t;
+
+
+/* The workque is a simple linked list.  */
+static wqitem_t workqueue;
+
+
+/* Dump the queue using Assuan status comments.  */
+void
+workqueue_dump_queue (ctrl_t ctrl)
+{
+  wqitem_t saved_workqueue;
+  wqitem_t item;
+  unsigned int count;
+
+  /* Temporay detach the entiere workqueue so that other threads don't
+   * get into our way.  */
+  saved_workqueue = workqueue;
+  workqueue = NULL;
+
+  for (count=0, item = saved_workqueue; item; item = item->next)
+    count++;
+
+  dirmngr_status_helpf (ctrl, "wq: number of entries: %u", count);
+  for (item = saved_workqueue; item; item = item->next)
+    dirmngr_status_helpf (ctrl, "wq: sess=%u net=%d %s(\"%.100s%s\")",
+                          item->session_id, item->need_network,
+                          item->func? item->func (NULL, NULL): "nop",
+                          item->args, strlen (item->args) > 100? "[...]":"");
+
+  /* Restore then workqueue.  Actually we append the saved queue do a
+   * possibly updated workqueue.  */
+  if (!(item=workqueue))
+    workqueue = saved_workqueue;
+  else
+    {
+      while (item->next)
+        item = item->next;
+      item->next = saved_workqueue;
+    }
+}
+
+
+/* Append the task (FUNC,ARGS) to the work queue.  FUNC shall return
+ * its name when called with (NULL, NULL).  */
+gpg_error_t
+workqueue_add_task (wqtask_t func, const char *args, unsigned int session_id,
+                    int need_network)
+{
+  wqitem_t item, wi;
+
+  item = xtrycalloc (1, sizeof *item + strlen (args));
+  if (!item)
+    return gpg_error_from_syserror ();
+  strcpy (item->args, args);
+  item->func = func;
+  item->session_id = session_id;
+  item->need_network = !!need_network;
+
+  if (!(wi=workqueue))
+    workqueue = item;
+  else
+    {
+      while (wi->next)
+        wi = wi->next;
+      wi->next = item;
+    }
+  return 0;
+}
+
+
+/* Run the task described by ITEM.  ITEM must have been detached from
+ * the workqueue; its ownership is transferred to this fucntion.  */
+static void
+run_a_task (ctrl_t ctrl, wqitem_t item)
+{
+  log_assert (!item->next);
+
+  if (opt.verbose)
+    log_info ("session %u: running %s(\"%s%s\")\n",
+              item->session_id,
+              item->func? item->func (NULL, NULL): "nop",
+              item->args, strlen (item->args) > 100? "[...]":"");
+  if (item->func)
+    item->func (ctrl, item->args);
+
+  xfree (item);
+}
+
+
+/* Run tasks not associated with a session.  This is called from the
+ * ticker every few minutes.  If WITH_NETWORK is not set tasks which
+ * require the network are not run.  */
+void
+workqueue_run_global_tasks (ctrl_t ctrl, int with_network)
+{
+  wqitem_t item, prev;
+
+  with_network = !!with_network;
+
+  if (opt.verbose)
+    log_info ("running scheduled tasks%s\n", with_network?" (with network)":"");
+
+  for (;;)
+    {
+      prev = NULL;
+      for (item = workqueue; item; prev = item, item = item->next)
+        if (!item->session_id
+            && (!item->need_network || (item->need_network && with_network)))
+          break;
+      if (!item)
+        break;  /* No more tasks to run.  */
+
+      /* Detach that item from the workqueue.  */
+      if (!prev)
+        workqueue = item->next;
+      else
+        prev->next = item->next;
+      item->next = NULL;
+
+      /* Run the task.  */
+      run_a_task (ctrl, item);
+    }
+}
+
+
+/* Run tasks scheduled for running after a session.  Those tasks are
+ * identified by the SESSION_ID.  */
+void
+workqueue_run_post_session_tasks (unsigned int session_id)
+{
+  struct server_control_s ctrlbuf;
+  ctrl_t ctrl = NULL;
+  wqitem_t item, prev;
+
+  if (!session_id)
+    return;
+
+  for (;;)
+    {
+      prev = NULL;
+      for (item = workqueue; item; prev = item, item = item->next)
+        if (item->session_id == session_id)
+          break;
+      if (!item)
+        break;  /* No more tasks for this session.  */
+
+      /* Detach that item from the workqueue.  */
+      if (!prev)
+        workqueue = item->next;
+      else
+        prev->next = item->next;
+      item->next = NULL;
+
+      /* Create a CTRL object the first time we need it.  */
+      if (!ctrl)
+        {
+          memset (&ctrlbuf, 0, sizeof ctrlbuf);
+          ctrl = &ctrlbuf;
+          dirmngr_init_default_ctrl (ctrl);
+        }
+
+      /* Run the task.  */
+      run_a_task (ctrl, item);
+    }
+
+  dirmngr_deinit_default_ctrl (ctrl);
+}