/* 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 . * * SPDX-License-Identifier: GPL-3.0+ */ #include #include #include #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 background 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; /* Temporarily detach the entire 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 the workqueue. Actually we append the saved queue to * handle 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 function. */ 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); }