1 /* workqueue.c - Maintain a queue of background tasks
2  * Copyright (C) 2017 Werner Koch
3  *
4  * This file is part of GnuPG.
5  *
6  * GnuPG is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * GnuPG is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, see <https://www.gnu.org/licenses/>.
18  *
19  * SPDX-License-Identifier: GPL-3.0+
20  */
21 
22 #include <config.h>
23 #include <stdlib.h>
24 #include <string.h>
25 
26 #include "dirmngr.h"
27 
28 
29 /* An object for one item in the workqueue.  */
30 struct wqitem_s
31 {
32   struct wqitem_s *next;
33 
34   /* This flag is set if the task requires network access.  */
35   unsigned int need_network:1;
36 
37   /* The id of the session which created this task.  If this is 0 the
38    * task is not associated with a specific session.  */
39   unsigned int session_id;
40 
41   /* The function to perform the background task.  */
42   wqtask_t func;
43 
44   /* A string with the string argument for that task.  */
45   char args[1];
46 };
47 typedef struct wqitem_s *wqitem_t;
48 
49 
50 /* The workque is a simple linked list.  */
51 static wqitem_t workqueue;
52 
53 
54 /* Dump the queue using Assuan status comments.  */
55 void
workqueue_dump_queue(ctrl_t ctrl)56 workqueue_dump_queue (ctrl_t ctrl)
57 {
58   wqitem_t saved_workqueue;
59   wqitem_t item;
60   unsigned int count;
61 
62   /* Temporarily detach the entiere workqueue so that other threads don't
63    * get into our way.  */
64   saved_workqueue = workqueue;
65   workqueue = NULL;
66 
67   for (count=0, item = saved_workqueue; item; item = item->next)
68     count++;
69 
70   dirmngr_status_helpf (ctrl, "wq: number of entries: %u", count);
71   for (item = saved_workqueue; item; item = item->next)
72     dirmngr_status_helpf (ctrl, "wq: sess=%u net=%d %s(\"%.100s%s\")",
73                           item->session_id, item->need_network,
74                           item->func? item->func (NULL, NULL): "nop",
75                           item->args, strlen (item->args) > 100? "[...]":"");
76 
77   /* Restore then workqueue.  Actually we append the saved queue do a
78    * possibly updated workqueue.  */
79   if (!(item=workqueue))
80     workqueue = saved_workqueue;
81   else
82     {
83       while (item->next)
84         item = item->next;
85       item->next = saved_workqueue;
86     }
87 }
88 
89 
90 /* Append the task (FUNC,ARGS) to the work queue.  FUNC shall return
91  * its name when called with (NULL, NULL).  */
92 gpg_error_t
workqueue_add_task(wqtask_t func,const char * args,unsigned int session_id,int need_network)93 workqueue_add_task (wqtask_t func, const char *args, unsigned int session_id,
94                     int need_network)
95 {
96   wqitem_t item, wi;
97 
98   item = xtrycalloc (1, sizeof *item + strlen (args));
99   if (!item)
100     return gpg_error_from_syserror ();
101   strcpy (item->args, args);
102   item->func = func;
103   item->session_id = session_id;
104   item->need_network = !!need_network;
105 
106   if (!(wi=workqueue))
107     workqueue = item;
108   else
109     {
110       while (wi->next)
111         wi = wi->next;
112       wi->next = item;
113     }
114   return 0;
115 }
116 
117 
118 /* Run the task described by ITEM.  ITEM must have been detached from
119  * the workqueue; its ownership is transferred to this function.  */
120 static void
run_a_task(ctrl_t ctrl,wqitem_t item)121 run_a_task (ctrl_t ctrl, wqitem_t item)
122 {
123   log_assert (!item->next);
124 
125   if (opt.verbose)
126     log_info ("session %u: running %s(\"%s%s\")\n",
127               item->session_id,
128               item->func? item->func (NULL, NULL): "nop",
129               item->args, strlen (item->args) > 100? "[...]":"");
130   if (item->func)
131     item->func (ctrl, item->args);
132 
133   xfree (item);
134 }
135 
136 
137 /* Run tasks not associated with a session.  This is called from the
138  * ticker every few minutes.  If WITH_NETWORK is not set tasks which
139  * require the network are not run.  */
140 void
workqueue_run_global_tasks(ctrl_t ctrl,int with_network)141 workqueue_run_global_tasks (ctrl_t ctrl, int with_network)
142 {
143   wqitem_t item, prev;
144 
145   with_network = !!with_network;
146 
147   if (opt.verbose)
148     log_info ("running scheduled tasks%s\n", with_network?" (with network)":"");
149 
150   for (;;)
151     {
152       prev = NULL;
153       for (item = workqueue; item; prev = item, item = item->next)
154         if (!item->session_id
155             && (!item->need_network || (item->need_network && with_network)))
156           break;
157       if (!item)
158         break;  /* No more tasks to run.  */
159 
160       /* Detach that item from the workqueue.  */
161       if (!prev)
162         workqueue = item->next;
163       else
164         prev->next = item->next;
165       item->next = NULL;
166 
167       /* Run the task.  */
168       run_a_task (ctrl, item);
169     }
170 }
171 
172 
173 /* Run tasks scheduled for running after a session.  Those tasks are
174  * identified by the SESSION_ID.  */
175 void
workqueue_run_post_session_tasks(unsigned int session_id)176 workqueue_run_post_session_tasks (unsigned int session_id)
177 {
178   struct server_control_s ctrlbuf;
179   ctrl_t ctrl = NULL;
180   wqitem_t item, prev;
181 
182   if (!session_id)
183     return;
184 
185   for (;;)
186     {
187       prev = NULL;
188       for (item = workqueue; item; prev = item, item = item->next)
189         if (item->session_id == session_id)
190           break;
191       if (!item)
192         break;  /* No more tasks for this session.  */
193 
194       /* Detach that item from the workqueue.  */
195       if (!prev)
196         workqueue = item->next;
197       else
198         prev->next = item->next;
199       item->next = NULL;
200 
201       /* Create a CTRL object the first time we need it.  */
202       if (!ctrl)
203         {
204           memset (&ctrlbuf, 0, sizeof ctrlbuf);
205           ctrl = &ctrlbuf;
206           dirmngr_init_default_ctrl (ctrl);
207         }
208 
209       /* Run the task.  */
210       run_a_task (ctrl, item);
211     }
212 
213   dirmngr_deinit_default_ctrl (ctrl);
214 }
215