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