document the change in prompt timeout handling
[vlock.git] / src / plugins.c
blob9c7654989a95ec0e2bfd9d238020400751dd0284
1 /* plugins.c -- plugins for vlock, the VT locking program for linux
3 * This program is copyright (C) 2007 Frank Benkstein, and is free
4 * software which is freely distributable under the terms of the
5 * GNU General Public License version 2, included as the file COPYING in this
6 * distribution. It is NOT public domain software, and any
7 * redistribution not permitted by the GNU General Public License is
8 * expressly forbidden without prior written permission from
9 * the author.
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <string.h>
16 #include <errno.h>
17 #include <assert.h>
19 #include <glib.h>
21 #include "plugins.h"
23 #include "tsort.h"
25 #include "plugin.h"
26 #include "module.h"
27 #include "script.h"
29 #include "util.h"
31 /* the list of plugins */
32 static GList *plugins = NULL;
34 /****************/
35 /* dependencies */
36 /****************/
38 #define SUCCEEDS 0
39 #define PRECEEDS 1
40 #define REQUIRES 2
41 #define NEEDS 3
42 #define DEPENDS 4
43 #define CONFLICTS 5
45 const char *dependency_names[nr_dependencies] = {
46 "succeeds",
47 "preceeds",
48 "requires",
49 "needs",
50 "depends",
51 "conflicts",
54 /*********/
55 /* hooks */
56 /*********/
58 static void handle_vlock_start(const char * hook_name);
59 static void handle_vlock_end(const char * hook_name);
60 static void handle_vlock_save(const char * hook_name);
61 static void handle_vlock_save_abort(const char * hook_name);
63 const struct hook hooks[nr_hooks] = {
64 { "vlock_start", handle_vlock_start },
65 { "vlock_end", handle_vlock_end },
66 { "vlock_save", handle_vlock_save },
67 { "vlock_save_abort", handle_vlock_save_abort },
70 /**********************/
71 /* exported functions */
72 /**********************/
74 /* helper declarations */
75 static VlockPlugin *__load_plugin(const char *name, GError **error);
76 static bool __resolve_depedencies(GError **error);
77 static bool sort_plugins(GError **error);
79 bool load_plugin(const char *name, GError **error)
81 return __load_plugin(name, error) != NULL;
84 bool resolve_dependencies(GError **error)
86 return __resolve_depedencies(error) && sort_plugins(error);
89 void unload_plugins(void)
91 while (plugins != NULL) {
92 g_object_unref(plugins->data);
93 plugins = g_list_delete_link(plugins, plugins);
97 void plugin_hook(const char *hook_name)
99 for (size_t i = 0; i < nr_hooks; i++)
100 /* Get the handler and call it. */
101 if (strcmp(hook_name, hooks[i].name) == 0) {
102 hooks[i].handler(hook_name);
103 break;
107 /********************/
108 /* helper functions */
109 /********************/
111 static gint plugin_name_compare(VlockPlugin *p, const char *name)
113 return strcmp(name, p->name);
116 static VlockPlugin *get_plugin(const char *name)
118 GList *item = g_list_find_custom(plugins,
119 name,
120 (GCompareFunc) plugin_name_compare);
122 if (item != NULL)
123 return item->data;
124 else
125 return NULL;
128 /* Load and return the named plugin. */
129 static VlockPlugin *__load_plugin(const char *name, GError **error)
131 VlockPlugin *p = get_plugin(name);
133 if (p != NULL)
134 return p;
136 GError *err = NULL;
138 /* Possible plugin types. */
139 GType plugin_types[] = { TYPE_VLOCK_MODULE, TYPE_VLOCK_SCRIPT, 0 };
141 for (size_t i = 0; plugin_types[i] != 0; i++) {
142 if (err == NULL || g_error_matches(err,
143 VLOCK_PLUGIN_ERROR,
144 VLOCK_PLUGIN_ERROR_NOT_FOUND))
145 /* Continue if the was no previous error or the error was "not found". */
146 g_clear_error(&err);
147 else
148 /* Bail out on real errors. */
149 break;
151 /* Create the plugin. */
152 p = g_object_new(plugin_types[i], "name", name, NULL);
154 /* Try to open the plugin. */
155 if (vlock_plugin_open(p, &err)) {
156 g_assert(err == NULL);
157 break;
158 } else {
159 g_assert(err != NULL);
160 g_object_unref(p);
161 p = NULL;
165 if (err != NULL) {
166 g_assert(p == NULL);
168 g_propagate_error(error, err);
170 return NULL;
171 } else {
172 g_assert(p != NULL);
174 plugins = g_list_append(plugins, p);
176 return p;
180 /* Resolve the dependencies of the plugins. */
181 static bool __resolve_depedencies(GError **error)
183 GList *required_plugins = NULL;
185 /* Load plugins that are required. This automagically takes care of plugins
186 * that are required by the plugins loaded here because they are appended to
187 * the end of the list. */
188 for (GList *plugin_item = plugins;
189 plugin_item != NULL;
190 plugin_item = g_list_next(plugin_item)) {
191 VlockPlugin *p = plugin_item->data;
193 for (GList *dependency_item = p->dependencies[REQUIRES];
194 dependency_item != NULL;
195 dependency_item = g_list_next(dependency_item)) {
196 const char *d = dependency_item->data;
197 VlockPlugin *q = __load_plugin(d, NULL);
199 if (q == NULL) {
200 g_set_error(
201 error,
202 VLOCK_PLUGIN_ERROR,
203 VLOCK_PLUGIN_ERROR_DEPENDENCY,
204 "'%s' requires '%s' which could not be loaded", p->name, d);
205 g_list_free(required_plugins);
206 return false;
209 required_plugins = g_list_append(required_plugins, p);
213 /* Fail if a plugins that is needed is not loaded. */
214 for (GList *plugin_item = plugins;
215 plugin_item != NULL;
216 plugin_item = g_list_next(plugin_item)) {
217 VlockPlugin *p = plugin_item->data;
219 for (GList *dependency_item = p->dependencies[NEEDS];
220 dependency_item != NULL;
221 dependency_item = g_list_next(dependency_item)) {
222 const char *d = dependency_item->data;
223 VlockPlugin *q = get_plugin(d);
225 if (q == NULL) {
226 g_set_error(
227 error,
228 VLOCK_PLUGIN_ERROR,
229 VLOCK_PLUGIN_ERROR_DEPENDENCY,
230 "'%s' needs '%s' which is not loaded", p->name, d);
231 g_list_free(required_plugins);
232 errno = 0;
233 return false;
236 required_plugins = g_list_append(required_plugins, q);
240 /* Unload plugins whose prerequisites are not present, fail if those plugins
241 * are required. */
242 for (GList *plugin_item = plugins;
243 plugin_item != NULL; ) {
244 VlockPlugin *p = plugin_item->data;
245 bool dependencies_loaded = true;
247 for (GList *dependency_item = p->dependencies[DEPENDS];
248 dependency_item != NULL;
249 dependency_item = g_list_next(dependency_item)) {
250 const char *d = dependency_item->data;
251 VlockPlugin *q = get_plugin(d);
253 if (q == NULL) {
254 dependencies_loaded = false;
256 /* Abort if dependencies not met and plugin is required. */
257 if (g_list_find(required_plugins, p) != NULL) {
258 g_set_error(
259 error,
260 VLOCK_PLUGIN_ERROR,
261 VLOCK_PLUGIN_ERROR_DEPENDENCY,
262 "'%s' is required by some other plugin but depends on '%s' which is not loaded",
263 p->name,
265 g_list_free(required_plugins);
266 errno = 0;
267 return false;
270 break;
274 GList *next_plugin_item = g_list_next(plugin_item);
276 if (!dependencies_loaded) {
277 g_object_unref(p);
278 plugins = g_list_delete_link(plugins, plugin_item);
281 plugin_item = next_plugin_item;
284 g_list_free(required_plugins);
286 /* Fail if conflicting plugins are loaded. */
287 for (GList *plugin_item = plugins;
288 plugin_item != NULL;
289 plugin_item = g_list_next(plugin_item)) {
290 VlockPlugin *p = plugin_item->data;
292 for (GList *dependency_item = p->dependencies[CONFLICTS];
293 dependency_item != NULL;
294 dependency_item = g_list_next(dependency_item)) {
295 const char *d = dependency_item->data;
296 if (get_plugin(d) != NULL) {
297 g_set_error(
298 error,
299 VLOCK_PLUGIN_ERROR,
300 VLOCK_PLUGIN_ERROR_DEPENDENCY,
301 "'%s' and '%s' cannot be loaded at the same time",
302 p->name,
304 errno = 0;
305 return false;
310 return true;
313 static GList *get_edges(void);
315 /* Sort the list of plugins according to their "preceeds" and "succeeds"
316 * dependencies. Fails if sorting is not possible because of circles. */
317 static bool sort_plugins(GError **error)
319 GList *edges = get_edges();
320 GList *sorted_plugins;
322 /* Topological sort. */
323 sorted_plugins = tsort(plugins, &edges);
325 bool tsort_successful = (edges == NULL);
327 if (tsort_successful) {
328 /* Switch the global list of plugins for the sorted list. The global list
329 * is static and cannot be freed. */
331 g_assert(edges == NULL);
332 g_assert(g_list_length(sorted_plugins) == g_list_length(plugins));
334 GList *tmp = plugins;
335 plugins = sorted_plugins;
337 g_list_free(tmp);
339 return true;
340 } else {
341 GString *error_message = g_string_new("circular dependencies detected:");
343 while (edges != NULL) {
344 struct edge *e = edges->data;
345 VlockPlugin *p = e->predecessor;
346 VlockPlugin *s = e->successor;
348 g_string_append_printf(error_message,
349 "\n\t'%s'\tmust come before\t'%s'",
350 p->name,
351 s->name);
352 g_free(e);
353 edges = g_list_delete_link(edges, edges);
356 g_set_error(error,
357 VLOCK_PLUGIN_ERROR,
358 VLOCK_PLUGIN_ERROR_DEPENDENCY,
359 "%s",
360 error_message->str);
362 g_string_free(error_message, true);
363 return false;
367 /* Get the edges of the plugin graph specified by each plugin's "preceeds" and
368 * "succeeds" dependencies. */
369 static GList *get_edges(void)
371 GList *edges = NULL;
373 for (GList *plugin_item = plugins;
374 plugin_item != NULL;
375 plugin_item = g_list_next(plugin_item)) {
376 VlockPlugin *p = plugin_item->data;
377 /* p must come after these */
378 for (GList *predecessor_item = p->dependencies[SUCCEEDS];
379 predecessor_item != NULL;
380 predecessor_item = g_list_next(predecessor_item)) {
381 VlockPlugin *q = get_plugin(predecessor_item->data);
383 if (q != NULL)
384 edges = g_list_append(edges, make_edge(q, p));
387 /* p must come before these */
388 for (GList *successor_item = p->dependencies[PRECEEDS];
389 successor_item != NULL;
390 successor_item = g_list_next(successor_item)) {
391 VlockPlugin *q = get_plugin(successor_item->data);
393 if (q != NULL)
394 edges = g_list_append(edges, make_edge(p, q));
398 return edges;
401 /************/
402 /* handlers */
403 /************/
405 /* Call the "vlock_start" hook of each plugin. Fails if the hook of one of the
406 * plugins fails. In this case the "vlock_end" hooks of all plugins that were
407 * called before are called in reverse order. */
408 void handle_vlock_start(const char *hook_name)
410 for (GList *plugin_item = plugins;
411 plugin_item != NULL;
412 plugin_item = g_list_next(plugin_item)) {
413 VlockPlugin *p = plugin_item->data;
415 if (!vlock_plugin_call_hook(p, hook_name)) {
416 int errsv = errno;
418 for (GList *reverse_item = g_list_previous(plugin_item);
419 reverse_item != NULL;
420 reverse_item = g_list_previous(reverse_item)) {
421 VlockPlugin *r = reverse_item->data;
422 (void) vlock_plugin_call_hook(r, "vlock_end");
425 if (errsv)
426 fprintf(stderr, "vlock: plugin '%s' failed: %s\n", p->name,
427 strerror(errsv));
429 exit(EXIT_FAILURE);
434 /* Call the "vlock_end" hook of each plugin in reverse order. Never fails. */
435 void handle_vlock_end(const char *hook_name)
437 for (GList *plugin_item = g_list_last(plugins);
438 plugin_item != NULL;
439 plugin_item = g_list_previous(plugin_item)) {
440 VlockPlugin *p = plugin_item->data;
441 (void) vlock_plugin_call_hook(p, hook_name);
445 /* Call the "vlock_save" hook of each plugin. Never fails. If the hook of a
446 * plugin fails its "vlock_save_abort" hook is called and both hooks are never
447 * called again afterwards. */
448 void handle_vlock_save(const char *hook_name)
450 for (GList *plugin_item = plugins;
451 plugin_item != NULL;
452 plugin_item = g_list_next(plugin_item)) {
453 VlockPlugin *p = plugin_item->data;
455 if (p->save_disabled)
456 continue;
458 if (!vlock_plugin_call_hook(p, hook_name)) {
459 p->save_disabled = true;
460 (void) vlock_plugin_call_hook(p, "vlock_save_abort");
465 /* Call the "vlock_save" hook of each plugin. Never fails. If the hook of a
466 * plugin fails both hooks "vlock_save" and "vlock_save_abort" are never called
467 * again afterwards. */
468 void handle_vlock_save_abort(const char *hook_name)
470 for (GList *plugin_item = g_list_last(plugins);
471 plugin_item != NULL;
472 plugin_item = g_list_previous(plugin_item)) {
473 VlockPlugin *p = plugin_item->data;
475 if (p->save_disabled)
476 continue;
478 if (!vlock_plugin_call_hook(p, hook_name))
479 p->save_disabled = true;