From fb5995065ddc8fcb9c02702ee6bf312984a03b08 Mon Sep 17 00:00:00 2001 From: InterLinked1 <24227567+InterLinked1@users.noreply.github.com> Date: Sun, 22 Dec 2024 19:04:20 -0500 Subject: [PATCH] module.c: Rewrite autoloading using a linked list. This largely rewrites the module traversal logic of the module autoloading that occurs at BBS startup. Previously, this used a set of string lists to keep track of modules that should be preloaded, not loaded, required, etc. Over time, this became very clunky and inefficient, and didn't allow capturing relationships involving multi-layer dependencies at all. Instead of this, we now use a single (doubly) linked list so that the modules can be ordered correctly prior to actually autoloading them. This also reduces the number of traversals order all and is more efficient. Some logging has also been cleaned up to be less cluttered and more usable for the user. LBBS-2 #close --- bbs/menu.c | 2 +- bbs/module.c | 525 +++++++++++------- configs/modules.conf | 31 +- include/dlinkedlists.h | 1164 ++++++++++++++++++++++++++++++++++++++++ include/module.h | 2 +- modules/mod_chanserv.c | 2 +- modules/mod_mysql.c | 7 +- modules/mod_sysop.c | 6 +- 8 files changed, 1531 insertions(+), 208 deletions(-) create mode 100755 include/dlinkedlists.h diff --git a/bbs/menu.c b/bbs/menu.c index 3da1b57..56fb7b2 100644 --- a/bbs/menu.c +++ b/bbs/menu.c @@ -1077,7 +1077,7 @@ static int check_menus(void) RWLIST_UNLOCK(&menu->menuitems); if (!egress_possible) { /* This will strand any users that access this menu */ - bbs_warning("Menu %s contains no way to exit or return from it\n", menu->name); + bbs_warning("Menu '%s' contains no way to exit or return from it\n", menu->name); } } RWLIST_UNLOCK(&menus); diff --git a/bbs/module.c b/bbs/module.c index a3af593..eac141b 100644 --- a/bbs/module.c +++ b/bbs/module.c @@ -28,6 +28,7 @@ #include /* use PATH_MAX */ #include "include/linkedlists.h" +#include "include/dlinkedlists.h" #include "include/stringlist.h" #include "include/module.h" #include "include/reload.h" @@ -71,11 +72,29 @@ struct bbs_module { /* Next entry */ RWLIST_ENTRY(bbs_module) entry; /*! The name of the module. */ - char name[0]; + char name[]; }; static RWLIST_HEAD_STATIC(modules, bbs_module); +struct autoload_module { + RWDLLIST_ENTRY(autoload_module) entry; + /* Plan for autoloading */ + unsigned int preload:1; /* Whether to preload module */ + unsigned int required:1; /* Required, normal load (unless preloaded also). Load failure will cause startup to abort. */ + unsigned int load:1; /* Load module? */ + unsigned int noload:1; /* Don't load? */ + /* Results */ + unsigned int attempted:1; /* Attempted to load? */ + unsigned int failed:1; /* Failed to load? */ + unsigned int loaded:1; /* Loaded successfully */ + char name[]; /* Module name */ +}; + +static RWDLLIST_HEAD_STATIC(autoload_modules, autoload_module); + +static int total_modules = 0; /* Total number of modules in module dir */ + /*! \brief Autoload all modules by default */ #define DEFAULT_AUTOLOAD_SETTING 1 @@ -83,11 +102,6 @@ static int autoload_setting = DEFAULT_AUTOLOAD_SETTING; static int really_register = 0; -struct stringlist modules_preload; /* Preload */ -struct stringlist modules_required; /* Required, normal load (unless preloaded also) */ -struct stringlist modules_load; /* Normal load */ -struct stringlist modules_noload; /* Don't load */ - /*! \brief Number of modules we plan to autoload */ static int autoload_planned = 0; @@ -269,12 +283,13 @@ static struct bbs_module *find_resource(const char *resource) struct bbs_module *__bbs_require_module(const char *module, void *refmod) { + struct bbs_module *reffing_mod = refmod; struct bbs_module *mod = find_resource(module); if (mod) { - bbs_debug(5, "Module dependency '%s' is satisfied\n", module); + bbs_debug(5, "Module dependency '%s' is satisfied (required by %s)\n", module, reffing_mod->name); __bbs_module_ref(mod, 1, refmod, __FILE__, __LINE__, __func__); } else { - bbs_warning("Module %s dependency is not satisfied\n", module); + bbs_warning("Module %s dependency is not satisfied (required by %s)\n", module, reffing_mod->name); } return mod; } @@ -354,9 +369,7 @@ static struct bbs_module *load_dlopen(const char *resource_in, const char *so_ex if (resource_being_loaded) { const char *dlerror_msg = S_IF(dlerror()); - if (!suppress_logging) { - bbs_warning("Module %s didn't register itself during load?\n", resource_in); - } + /* Module didn't register itself during load, failure! */ resource_being_loaded = NULL; if (mod->lib) { @@ -375,10 +388,22 @@ static struct bbs_module *load_dlopen(const char *resource_in, const char *so_ex return mod; } +static struct autoload_module *find_autoload_module(const char *module) +{ + struct autoload_module *a; + RWDLLIST_TRAVERSE(&autoload_modules, a, entry) { + if (!strcmp(a->name, module)) { + return a; + } + } + return NULL; +} + /* Forward declaration */ -static int load_resource(const char *restrict resource_name, unsigned int suppress_logging); +static int load_resource(struct autoload_module *a, const char *restrict resource_name, unsigned int suppress_logging); -static struct bbs_module *load_dynamic_module(const char *resource_in, unsigned int suppress_logging) +/*! \note a can be NULL */ +static struct bbs_module *load_dynamic_module(struct autoload_module *a, const char *resource_in, unsigned int suppress_logging) { char fn[PATH_MAX]; size_t resource_in_len = strlen(resource_in); @@ -395,37 +420,29 @@ static struct bbs_module *load_dynamic_module(const char *resource_in, unsigned /* If we're going to try loading dependencies and then call load_dlopen again, * any warnings can be ignored the first time, since they were probably due * to missing symbols (and if not, we'll try again anyways, and log that time). */ - retry = stringlist_contains(&modules_preload, resource_in); + retry = a && a->preload; + retry = 1; /* Actually we need to always retry... not sure why we wouldn't? */ mod = load_dlopen(resource_in, so_ext, fn, RTLD_NOW | RTLD_LOCAL, suppress_logging || retry); if (!mod) { /* XXX. Here, we consider the case of modules that are both depended on by other modules * and themselves depend on yet other modules. - * In other words, they both export symbols globally, - * and they also require the symbols of other modules. + * In other words, they both export symbols globally, and they also require the symbols of other modules. * This means that among the modules to preload, order will matter, * because if C depends on B and B depends on A, then B and A will both be marked for preload, * but A *MUST* be loaded before B, or B will fail to load (cascading to C, etc.) * - * When scanning the list of modules, at some point, we will discover that B requires A and C requires B. - * It could be in either order. - * And besides that, we use directory order, not stringlist order, for the purposes of autoloading. - * That could be changed for preload, but we would need to store more state to detect these deeper dependencies - * than we do now. + * In the case of autoload, we already ordered the modules with dependencies taken into account, + * so the branch below to preload on the fly should never be taken in that case, + * only for modules loaded after the BBS is fully started. * - * In the meantime, if we're autoloading modules, and a load fails, it's probably because symbols + * In the meantime, if we're NOT autoloading modules, and a load fails, it's probably because symbols * failed to resolve, and probably due to unresolved dependencies. Try to resolve those on the fly, for now. * - * XXX This is really not elegant, it would be better to make a properly ordered list of all modules from the get go. - * Also, this is no longer safe from accidental infinte recursion. We will crash (stack overflow) if there is a dependency loop here. + * XXX This is no longer safe from accidental infinte recursion. We will crash (stack overflow) if there is a dependency loop here. * If we have a better way of handling this in the future, this hack can be removed: */ if (retry) { - /* Only bother checking if it's a preload module. - * Otherwise, checking dependencies now isn't going to do anything for us. - * Also only do this during autoload (the preload list will be emptied once autoload finishes). - */ - /* At this point, we'll have to do a lazy open again. If that fails, then really give up. */ mod = load_dlopen(resource_in, so_ext, fn, RTLD_LAZY | RTLD_LOCAL, suppress_logging); if (mod && mod->info->dependencies) { @@ -438,12 +455,29 @@ static struct bbs_module *load_dynamic_module(const char *resource_in, unsigned while ((dependency = strsep(&dependencies, ","))) { if (!find_resource(dependency)) { /* It's not loaded already. */ int mres; + struct autoload_module *d = NULL; + if (a) { + /* If we're autoloading, need to pass the dependency's autoload object, + * not that of the module dependent on it. */ + d = find_autoload_module(dependency); + /* If !d, load_resource will fail, but we'll let it handle it there */ + if (d && d->failed) { + /* We already tried loading the dependency earlier in the autoload sequence, and it failed. + * No point in trying to load it again now. */ + bbs_error("Module %s is dependent on %s, which failed to load\n", a->name, d->name); + res = -1; + break; + } + } bbs_debug(1, "Preloading %s on the fly since it's required by %s\n", dependency, resource_in); - mres = load_resource(dependency, suppress_logging); + /* Since we automatically reorder modules with dependencies for autoload, + * the only time this logic should be hit is while the BBS is already running. + * We shouldn't need to have to preload something on the fly during autoload, + * breaking from the presorted module ordering. */ + bbs_soft_assert(a == NULL); + mres = load_resource(d, dependency, suppress_logging); + /* Since dependency will have loaded bit set now, we won't try to load it another time in the future. */ if (!mres) { - /* Prevent it from being loaded again in the future */ - stringlist_push(&modules_noload, dependency); - stringlist_remove(&modules_preload, dependency); autoload_loaded++; } res |= mres; @@ -457,84 +491,159 @@ static struct bbs_module *load_dynamic_module(const char *resource_in, unsigned mod = NULL; } } - } else { - bbs_debug(3, "Failure appears to be genuine: %s cannot be loaded\n", resource_in); } } if (mod && mod->info->flags & MODFLAG_GLOBAL_SYMBOLS) { /* Close the module so we can reopen with correct flags. */ + bbs_debug(3, "Module '%s' contains global symbols, reopening\n", resource_in); + really_register = 0; logged_dlclose(resource_in, mod->lib); free_module(mod); - bbs_debug(3, "Module '%s' contains global symbols, reopening\n", resource_in); + /* At this point, we've already loaded any dependencies needed, so we're only going to load a single module, + * hence we can safely defer re-enabling really_register until after load_dlopen. + * This suppresses log messages for dlclose and registering module when we reopen it. */ mod = load_dlopen(resource_in, so_ext, fn, RTLD_NOW | RTLD_GLOBAL, 0); + really_register = 1; } - return mod; } -static void check_dependencies(const char *restrict resource_in, unsigned int suppress_logging) +static struct autoload_module *find_first_autoload_in_list(struct autoload_module *a, struct autoload_module *b) +{ + struct autoload_module *first = a; + + /* We assume a != b. Even if it is, it doesn't matter which one we return anyways. */ + while ((first = RWDLLIST_NEXT(first, entry))) { + if (first == b) { + /* By following next pointers from a, we got to b, so a is first */ + return a; + } + } + + return b; +} + +#define SHOULD_LOAD_MODULE(m) (!m->noload && (autoload_setting || m->load || m->preload || m->required)) + +static void check_dependencies(struct autoload_module *a) { char fn[PATH_MAX]; - size_t resource_in_len = strlen(resource_in); + size_t resource_in_len = strlen(a->name); const char *so_ext = ""; struct bbs_module *mod; - /* Module isn't going to load anyways, so who cares? */ - if (stringlist_contains(&modules_noload, resource_in)) { - return; - } - - if (resource_in_len < 4 || strcasecmp(resource_in + resource_in_len - 3, ".so")) { + if (resource_in_len < 4 || strcasecmp(a->name + resource_in_len - 3, ".so")) { so_ext = ".so"; } - snprintf(fn, sizeof(fn), "%s/%s%s", BBS_MODULE_DIR, resource_in, so_ext); + snprintf(fn, sizeof(fn), "%s/%s%s", BBS_MODULE_DIR, a->name, so_ext); /* Lazy load won't perform symbol resolution, so we can successfully load a module that is missing dependencies */ - mod = load_dlopen(resource_in, so_ext, fn, RTLD_LAZY | RTLD_LOCAL, suppress_logging); + mod = load_dlopen(a->name, so_ext, fn, RTLD_LAZY | RTLD_LOCAL, 0); if (!mod) { - bbs_error("Failed to check dependencies for %s\n", resource_in); + bbs_error("Failed to check dependencies for %s\n", a->name); return; } if (autoload_setting && mod->info->flags & MODFLAG_ALWAYS_PRELOAD) { /* The module wants to be loaded as early as possible during startup, - * so add it to the preload list. + * so load it first. * Do this first, since then we can avoid checking if it has any dependents. + * + * To be clear, here we are not concerned with dependency chains, + * that is handled without MODFLAG_ALWAYS_PRELOAD. The purpose of this flag + * is to explicitly move a module to the very front of the load order. + * An example of this is io_tls. No module has a direct dependency on this module, + * but ssl_available() will return 1 only after io_tls has loaded. + * Therefore, it should load before other modules, even though it is not "strictly" + * a dependency (it's not *required* for anything to load), + * it still needs to be loaded early for the desired behavior. + * * XXX This is not foolproof; ideally, we would have store a "load priority" * for all modules, and ensure that the priority of this module * is before anything that might try to use it (or behave differently if not loaded). */ - if (!stringlist_contains(&modules_preload, resource_in)) { - bbs_debug(2, "Module %s requested to be preloaded\n", resource_in); - stringlist_push(&modules_preload, resource_in); - } + bbs_debug(4, "Module %s requested to be preloaded\n", a->name); + a->preload = 1; + /* Move to beginning of list */ + RWDLLIST_REMOVE(&autoload_modules, a, entry); + RWDLLIST_INSERT_HEAD(&autoload_modules, a, entry); } else if (!strlen_zero(mod->info->dependencies)) { char dependencies_buf[256]; char *dependencies, *dependency; safe_strncpy(dependencies_buf, mod->info->dependencies, sizeof(dependencies_buf)); dependencies = dependencies_buf; while ((dependency = strsep(&dependencies, ","))) { - if (stringlist_contains(&modules_noload, dependency)) { + struct autoload_module *first, *d = find_autoload_module(dependency); + if (!d) { + bbs_warning("Module %s has a dependency on unknown module %s\n", a->name, dependency); + a->noload = 1; + } else if (d->noload) { /* The module might try to load later (if autoload or explicitly loaded), - * but if it does, it WILL fail anyways, so just noload it now. */ - if (!stringlist_contains(&modules_noload, resource_in)) { - bbs_error("Module %s depends on noloaded module %s\n", resource_in, dependency); - stringlist_push(&modules_noload, resource_in); - } - continue; - } - if (autoload_setting) { - if (!stringlist_contains(&modules_preload, dependency)) { - bbs_debug(2, "Marking %s for preload since %s depends on it\n", dependency, resource_in); - stringlist_push(&modules_preload, dependency); - } else { - bbs_debug(4, "Module %s is already marked for preload\n", dependency); + * but if it's dependent on a module that's noloaded, it WILL fail anyways, so just also noload it now. */ + bbs_error("Module %s depends on noloaded module %s\n", a->name, dependency); + a->noload = 1; + } else if (SHOULD_LOAD_MODULE(a)) { + bbs_debug(4, "Marking %s for loading since %s depends on it\n", dependency, a->name); + /* Just mark for regular load, not preload. Preload should be reserved for exceptional cases + * where a module really needs to load first (or close to it). + * In this case, we just want to move it up earlier in the load sequence, + * i.e. move d before a. Setting preload or load doesn't do that, it's the + * remove/insert before operation below that does that. */ + d->load = 1; + /* Also adjust the ordering such that the dependency precedes the thing that depends on it */ + first = find_first_autoload_in_list(a, d); + /* If d is first, that's what we want, since it needs to load first. + * If a is first, we need to swap the two to correct the ordering. + * Since we only do this when we encounter a dependency, it's + * sort of like an efficient subset of bubble sort. */ + if (a == first) { + /* Since d comes later in the list, we want to remove it from wherever it is now, + * and reinsert it just before a. But that requires a doubly linked list. + * An alternative is to instead remove a and insert it after d, + * which changes the ordering respective to these 2 elements (only) properly. + * However, it doesn't preserve other desired invariants. Consider this scenario: + * + * + * Initial relative ordering: C ... B ... A1 ... A2 (other elements are inbetween) + * C depends on B, B depends on both A1 and A2 + * + * If we make a pass and swap elements such that the + * one that is too early is moved after its dependency, + * e.g. move C after B, B after A1, B after A2, we end up: + * + * C ... B ... A1 ... A2 + * B ... C ... A1 ... A2 + * C ... A1 ... B ... A2 + * C ... A1 ... A2 ... B + * + * B is ordered after everything it depends on, so B's ordering is okay. + * However, C should be ordered after B, and now it's not. + * + * If we do the related operation of moving something that is a dependency + * before its dependents, that solves this issue: + * + * C ... B ... A1 ... A2 + * B ... C ... A1 ... A2 + * A1 ... B ... C ... A2 + * A2 ... A1 ... B ... C + * + * + * So, we have to use a doubly linked list, since we need to access + * the element BEFORE a, so we can insert d before it. + * + * TL;DR Inserting a after d is not correct. + * We need to insert d before a instead. */ +#ifdef DEBUG_LOAD_ORDER + bbs_debug(7, " -- Moved %s after %s in load order\n", a->name, d->name); +#endif + RWDLLIST_REMOVE(&autoload_modules, d, entry); + RWDLLIST_INSERT_BEFORE(&autoload_modules, a, d, entry); } } } } - logged_dlclose(resource_in, mod->lib); + logged_dlclose(a->name, mod->lib); free_module(mod); return; } @@ -623,8 +732,11 @@ static int start_resource(struct bbs_module *mod) return 0; } -/*! \brief loads a resource based upon resource_name. */ -static int load_resource(const char *restrict resource_name, unsigned int suppress_logging) +/*! + * \brief loads a resource based upon resource_name. + * \note a can be NULL + */ +static int load_resource(struct autoload_module *a, const char *restrict resource_name, unsigned int suppress_logging) { int res; struct bbs_module *mod; @@ -634,9 +746,15 @@ static int load_resource(const char *restrict resource_name, unsigned int suppre return -1; } - mod = load_dynamic_module(resource_name, suppress_logging); + if (a) { + a->attempted = 1; + } + mod = load_dynamic_module(a, resource_name, suppress_logging); if (!mod) { - bbs_warning("Could not load dynamic module %s\n", resource_name); + if (a) { + a->failed = 1; + } + bbs_error("Failed to load module %s\n", resource_name); return -1; } @@ -645,18 +763,30 @@ static int load_resource(const char *restrict resource_name, unsigned int suppre if (res) { /* If success, log in start_resource, otherwise, log here */ bbs_error("Module '%s' could not be loaded.\n", resource_name); + if (a) { + a->failed = 1; + } + /* If start_resource returned failure, that means + * the module was not inserted into the modules list. + * Therefore, we set really_register false temporarily, + * to ensure bbs_module_unregister doesn't try to remove it from the list, + * since it's not there. */ + really_register = 0; unload_dynamic_module(mod); + really_register = 1; free_module(mod); /* bbs_module_unregister isn't called if the module declined to load, so free to avoid a leak */ return -1; } else { /* Bump the ref count of any modules upon which we depend. */ + if (a) { + a->loaded = 1; + } if (!strlen_zero(mod->info->dependencies)) { char dependencies_buf[256]; char *dependencies, *dependency; safe_strncpy(dependencies_buf, mod->info->dependencies, sizeof(dependencies_buf)); dependencies = dependencies_buf; while ((dependency = strsep(&dependencies, ","))) { - bbs_debug(9, "%s requires module %s\n", mod->name, dependency); __bbs_require_module(dependency, mod); } } @@ -890,100 +1020,72 @@ static int unload_resource(const char *resource_name, int force, struct stringli return 0; } -static int on_file_plan(const char *dir_name, const char *filename, void *obj) +static int on_module(const char *dir_name, const char *filename, void *obj) { + struct autoload_module *a; + UNUSED(dir_name); UNUSED(obj); - autoload_planned++; bbs_debug(7, "Detected dynamic module %s\n", filename); - check_dependencies(filename, 0); /* Check if we need to load any dependencies for this module. */ + a = calloc(1, sizeof(*a) + strlen(filename) + 1); + if (ALLOC_FAILURE(a)) { + return -1; + } + + strcpy(a->name, filename); /* Safe */ + RWDLLIST_INSERT_HEAD(&autoload_modules, a, entry); + total_modules++; return 0; } -static int on_file_preload(const char *dir_name, const char *filename, void *obj) +static int do_autoload_module(struct autoload_module *a) { - struct bbs_module *mod = find_resource(filename); - - UNUSED(dir_name); - UNUSED(obj); - - if (mod) { - /* Could happen due to the auto-preloading that can happen if trying to resolve dependencies. */ - bbs_debug(1, "Module %s is already loaded\n", filename); - return 0; /* Always return 0 or otherwise we'd abort the entire autoloading process */ - } + struct bbs_module *mod; + const char *filename = a->name; - /* noload trumps preload if both are present */ - if (stringlist_contains(&modules_noload, filename)) { - bbs_warning("Conflicting directives 'noload' and 'preload' for %s, not preloading\n", filename); + if (a->loaded) { + /* Module already loaded, don't load it again. */ + bbs_debug(5, "Module %s already loaded\n", a->name); return 0; } - - /* Only load if it's a preload module */ - if (!stringlist_contains(&modules_preload, filename)) { + if (a->failed) { + bbs_debug(5, "Failed to load %s earlier, skipping\n", a->name); return 0; } - - bbs_debug(5, "Preloading dynamic module %s (autoload=yes or dependency)\n", filename); - - if (load_resource(filename, 0)) { - bbs_error("Failed to autoload %s\n", filename); - if (stringlist_contains(&modules_required, filename)) { - bbs_error("Aborting startup due to failing to load required module %s\n", filename); - return 1; + if (a->noload) { + if (a->preload) { + bbs_warning("Conflicting directives 'noload' and 'preload' for %s, not preloading\n", filename); } - } else { - autoload_loaded++; + autoload_planned--; + return 0; } - return bbs_abort_startup() ? 1 : 0; /* Always return 0 or otherwise we'd abort the entire autoloading process */ -} - -static int on_file_autoload(const char *dir_name, const char *filename, void *obj) -{ - int required; - struct bbs_module *mod = find_resource(filename); - - UNUSED(dir_name); - UNUSED(obj); - + mod = find_resource(a->name); if (mod) { - if (!stringlist_contains(&modules_preload, filename)) { /* If it was preloaded, then it's legitimate */ - bbs_debug(1, "Module %s is already loaded\n", filename); - } - return 0; /* Always return 0 or otherwise we'd abort the entire autoloading process */ - } - - /* If explicit noload, bail now */ - if (stringlist_contains(&modules_noload, filename)) { - bbs_debug(5, "Not loading dynamic module %s, since it's explicitly noloaded\n", filename); - autoload_planned--; + bbs_warning("Module %s is already loaded?\n", a->name); return 0; } - required = stringlist_contains(&modules_required, filename); if (!autoload_setting) { - if (!required && !stringlist_contains(&modules_load, filename)) { + if (!(a->required || a->preload || a->load)) { bbs_debug(5, "Not loading dynamic module %s, not explicitly loaded and autoload=no\n", filename); autoload_planned--; return 0; } - bbs_debug(5, "Autoloading dynamic module %s, since explicitly %s\n", filename, required ? "required" : "loaded"); + bbs_debug(5, "Autoloading dynamic module %s, since explicitly %s\n", filename, a->required ? "required" : "loaded"); } else { - /* If autoload=yes and not in the noload list, then don't even bother checking the load list. Just load it. */ bbs_debug(5, "Autoloading dynamic module %s (autoload=yes)\n", filename); } - if (load_resource(filename, 0)) { - bbs_error("Failed to autoload %s\n", filename); - if (required) { + if (load_resource(a, filename, 0)) { + /* load_resource already logs an error on failure, no need to logic individual module load failure here */ + if (a->required) { bbs_error("Aborting startup due to failing to load required module %s\n", filename); return 1; } } else { autoload_loaded++; - stringlist_remove(&modules_required, filename); } return bbs_abort_startup() ? 1 : 0; /* Always return 0 or otherwise we'd abort the entire autoloading process */ @@ -1002,10 +1104,6 @@ static int load_config(void) bbs_config_val_set_true(cfg, "general", "autoload", &autoload_setting); - RWLIST_WRLOCK(&modules_load); - RWLIST_WRLOCK(&modules_noload); - RWLIST_WRLOCK(&modules_preload); - RWLIST_WRLOCK(&modules_required); while ((section = bbs_config_walk(cfg, section))) { if (!strcmp(bbs_config_section_name(section), "general")) { continue; /* Skip general, already handled */ @@ -1015,56 +1113,107 @@ static int load_config(void) } /* [modules] section */ while ((keyval = bbs_config_section_walk(section, keyval))) { + struct autoload_module *a; const char *key = bbs_keyval_key(keyval), *value = bbs_keyval_val(keyval); + a = find_autoload_module(value); + if (!a) { + /* Couldn't find the module... */ + bbs_warning("Unknown module name '%s' with directive '%s'\n", value, key); + continue; + } if (!strcmp(key, "load")) { bbs_debug(7, "Explicitly planning to load '%s'\n", value); - stringlist_push(&modules_load, value); + a->load = 1; } else if (!strcmp(key, "noload")) { bbs_debug(7, "Explicitly planning to not load '%s'\n", value); - stringlist_push(&modules_noload, value); + a->noload = 1; } else if (!strcmp(key, "preload")) { bbs_debug(7, "Explicitly planning to preload '%s'\n", value); - stringlist_push(&modules_preload, value); + a->preload = 1; + /* For now, just move to the beginning of the list. + * This way, all preloads are before all the non-preloads. */ + RWDLLIST_REMOVE(&autoload_modules, a, entry); + RWDLLIST_INSERT_HEAD(&autoload_modules, a, entry); } else if (!strcmp(key, "require")) { bbs_debug(7, "Explicitly planning to require '%s'\n", value); - stringlist_push(&modules_required, value); + a->required = 1; } else { bbs_warning("Invalid directive %s=%s, ignoring\n", key, value); } } } - RWLIST_UNLOCK(&modules_load); - RWLIST_UNLOCK(&modules_noload); - RWLIST_UNLOCK(&modules_preload); - RWLIST_UNLOCK(&modules_required); bbs_config_free(cfg); /* Destroy the config now, rather than waiting until shutdown, since it will NEVER be used again for anything. */ return 0; } -static int autoload_modules(void) +static int try_autoload_modules(void) { - int res = 0; + struct autoload_module *a, **alist; + int c = 0; + int abort = 0; + int res = -1; + bbs_debug(1, "Autoloading modules\n"); - stringlist_init(&modules_load); - stringlist_init(&modules_noload); - stringlist_init(&modules_preload); - stringlist_init(&modules_required); + RWDLLIST_WRLOCK(&autoload_modules); + bbs_dir_traverse(BBS_MODULE_DIR, on_module, NULL, -1); /* Initialize autoload_modules with an object for each module in the modules directory */ - /* Check config for load settings. */ - load_config(); + /* Now, check config for settings */ + if (load_config()) { + goto cleanup; + } - RWLIST_WRLOCK(&modules); - /* Check what modules exist in the first place. Additionally, check for dependencies. */ - bbs_dir_traverse(BBS_MODULE_DIR, on_file_plan, NULL, -1); + /* Now, initialize the objects themselves by lazy loading each module. This will also partially sort the list. + * Since we need to be able to swap elements in the list during traversal, but the actual order of the traversal doesn't matter, + * allocate a temporary array for traversing all the elements. Even RWLIST_TRAVERSE_SAFE_BEGIN doesn't help here. */ + alist = malloc((size_t) total_modules * sizeof(*a)); + if (ALLOC_FAILURE(alist)) { + goto cleanup; + } + RWDLLIST_TRAVERSE(&autoload_modules, a, entry) { + alist[c++] = a; + } + for (c = 0; c < total_modules; c++) { + if (SHOULD_LOAD_MODULE(alist[c])) { + check_dependencies(alist[c]); /* Check if we need to load any dependencies for this module. */ + } + } + free(alist); + /* Okay, we made a plan for what we're going to do, now execute it. */ + autoload_planned = total_modules; bbs_debug(1, "Detected %d dynamic module%s\n", autoload_planned, ESS(autoload_planned)); really_register = 1; /* Now, actually try to load them. */ - if (bbs_dir_traverse(BBS_MODULE_DIR, on_file_preload, NULL, -1) || bbs_dir_traverse(BBS_MODULE_DIR, on_file_autoload, NULL, -1)) { - res = -1; - goto cleanup; +#ifdef DEBUG_LOAD_ORDER + c = 0; + RWDLLIST_TRAVERSE(&autoload_modules, a, entry) { + if (autoload_setting) { + bbs_debug(3, "Load order %d/%d: %s\n", ++c, total_modules, a->name); + } else { + bbs_debug(3, "Load order %d/%d: %s%s\n", ++c, total_modules, a->name, a->noload ? "" : a->preload ? "\t\t(PRELOAD)" : a->required ? "\t\t(REQUIRE)" : a->load ? "\t\t(LOAD)" : ""); + } + } +#endif + RWDLLIST_TRAVERSE(&autoload_modules, a, entry) { + /* If a required module fails to load, we will stop loading modules thenceforth. + * However, when autoload=no, in order to ensure that autoload_planned is correct, + * we want to finish the traversal and continue decrementing based on module properties. */ + if (abort) { + /* abort is only true if !autoload_modules (autoload=no), so it wouldn't be loaded + * unless it's explicitly going to be loaded. */ + if (!SHOULD_LOAD_MODULE(a)) { + autoload_planned--; + } + } else if (do_autoload_module(a)) { + if (!autoload_setting) { + abort = 1; + } else { + /* We still abort, but we do it immediately by breaking, so no need to set the flag */ + break; + } + } } if (autoload_planned != autoload_loaded) { @@ -1074,26 +1223,27 @@ static int autoload_modules(void) bbs_debug(1, "Successfully autoloaded %d module%s\n", autoload_planned, ESS(autoload_planned)); } - if (!RWLIST_EMPTY(&modules_required)) { - struct stringitem *i = NULL; - const char *s; - /* We remove a required module from the list when it loads. - * If we didn't already abort, that means we tried to require - * a module that doesn't even exist (hence the traversal didn't encounter it) - * Enumerate which modules and then abort. */ - res = -1; - while ((s = stringlist_next(&modules_required, &i))) { - bbs_error("Required module '%s' failed to load\n", s); + res = 0; + /* Do a final pass to see if we're good to go. */ + RWDLLIST_TRAVERSE(&autoload_modules, a, entry) { + if (!a->loaded) { + /* Enumerate any modules which failed to load before we abort, so it's all in one place. */ + if (a->required) { + bbs_error("Required module '%s' failed to load\n", a->name); + res = -1; + } else if (!a->noload && !a->loaded && a->attempted && (a->load || autoload_setting)) { + /* For all other modules that failed to load: + * - Ignore if noload + * - Ignore if we never attempted to load it because we aborted due to a required module failing to load + * - Ignore if autoload=no, and don't have a load=yes */ + bbs_warning("Module '%s' failed to load\n", a->name); + } } } cleanup: - stringlist_empty(&modules_load); - stringlist_empty(&modules_noload); - stringlist_empty(&modules_preload); - stringlist_empty(&modules_required); - - RWLIST_UNLOCK(&modules); + RWDLLIST_REMOVE_ALL(&autoload_modules, entry, free); + RWDLLIST_UNLOCK(&autoload_modules); return res; } @@ -1101,7 +1251,7 @@ int bbs_module_load(const char *name) { int res; RWLIST_WRLOCK(&modules); - res = load_resource(name, 0); + res = load_resource(NULL, name, 0); RWLIST_UNLOCK(&modules); return res; } @@ -1140,7 +1290,7 @@ int bbs_module_reload(const char *name, int try_delayed) * can load properly when we try to load them afterwards. */ while ((module = stringlist_pop(&unloaded))) { - lres |= load_resource(module, 0); + lres |= load_resource(NULL, module, 0); free(module); } if (lres) { @@ -1527,6 +1677,7 @@ static struct bbs_cli_entry cli_commands_modules[] = { }; static int loaded_modules = 0; +static int really_loaded_modules = 0; int load_modules(void) { @@ -1535,19 +1686,18 @@ int load_modules(void) /* No modules should be registered on startup. */ RWLIST_WRLOCK(&modules); - bbs_assert(RWLIST_EMPTY(&modules)); - RWLIST_UNLOCK(&modules); + bbs_assert(RWLIST_EMPTY(&modules)); loaded_modules = 1; - res = autoload_modules(); - - RWLIST_WRLOCK(&modules); + res = try_autoload_modules(); c = RWLIST_SIZE(&modules, mod, entry); + RWLIST_UNLOCK(&modules); bbs_assert(c == autoload_loaded); if (!res) { bbs_cli_register_multiple(cli_commands_modules); + really_loaded_modules = 1; } return res; } @@ -1662,15 +1812,12 @@ int unload_modules(void) } RWLIST_UNLOCK(&modules); - bbs_cli_unregister_multiple(cli_commands_modules); + /* If startup aborts due to a required module failing to load, + * the CLI commands were never registered, so don't attempt to unregister them. */ + if (really_loaded_modules) { + bbs_cli_unregister_multiple(cli_commands_modules); + } RWLIST_WRLOCK_REMOVE_ALL(&reload_handlers, entry, free); - /* Some functions use these even after BBS is started, so we don't destroy after autoload, just empty */ - if (loaded_modules) { - stringlist_destroy(&modules_load); - stringlist_destroy(&modules_noload); - stringlist_destroy(&modules_preload); - stringlist_destroy(&modules_required); - } return 0; } diff --git a/configs/modules.conf b/configs/modules.conf index 8286fb3..166c059 100644 --- a/configs/modules.conf +++ b/configs/modules.conf @@ -5,10 +5,11 @@ autoload=yes ; By default, all modules are loaded automatically on startup. ; You can explicitly load only the modules you want by using autoload=no. [modules] -; You can specify modules to preload, for module dependencies that must be met before dependent modules load. -; For all operations here, the order of preload, load, and noload does not matter (and should not be relied upon). -; require is equivalent to load, but will abort startup if the module fails to load. -; To preload and require a module, specify both directives. +; For all operations here, the order of preload, require, load, and noload does not matter (and should not be relied upon). +; require is equivalent to load, but will abort BBS startup if the module fails to load. +; load will always attempt to load a specific module, and noload will always prevent loading a specific module, +; independent of the autoload setting. +; The 'preload' setting is similar to 'load' and will move it earlier in the loading sequence. Normally, this does not need to be specified. ; You can specify modules to automatically load or not load at startup here. ; You MUST specify the .so extension in modules.conf (but optional in sysop console) @@ -16,10 +17,12 @@ autoload=yes ; By default, all modules are loaded automatically on startup. ; using /load , even if they were not autoloaded. ; Library modules -preload = io_compress.so -preload = io_tls.so -preload = mod_lmdb.so -preload = mod_mysql.so ; mod_auth_mysql.so and mod_chanserv.so depend on this module +preload = io_tls.so ; Support for TLS. This module is always preloaded, if loaded, so load= would also technically work. + ; The io modules are not "hard" dependencies for anything and will not be autoloaded if needed, + ; therefore be sure to explicitly load (or preload) it if autoload=no. +load = io_compress.so +load = mod_lmdb.so +load = mod_mysql.so ; mod_auth_mysql.so and mod_chanserv.so depend on this module ; Basic doors load = door_usermgmt.so @@ -50,7 +53,7 @@ load = door_utilities.so load = door_ibbs.so ; IRC modules -preload = net_irc.so ; mod_discord.so, mod_chanserv.so, and mod_irc_relay.so depend on this module +load = net_irc.so ; mod_discord.so, mod_chanserv.so, and mod_irc_relay.so depend on this module load = mod_irc_client.so ; mod_irc_relay.so depends on this module load = mod_irc_relay.so load = mod_chanserv.so @@ -58,8 +61,8 @@ load = mod_discord.so load = mod_slack.so ; Email modules -preload = mod_mail.so -preload = mod_mimeparse.so +load = mod_mail.so +load = mod_mimeparse.so load = mod_mail_trash.so load = mod_mail_events.so load = mod_mailscript.so @@ -84,7 +87,7 @@ load = net_sieve.so load = net_smtp.so ; Web server -preload = mod_http.so +load = mod_http.so load = mod_http_proxy.so load = net_http.so load = net_ws.so @@ -102,8 +105,8 @@ load = net_gopher.so load = net_msp.so ; Asterisk modules -preload = mod_ncurses.so -preload = mod_asterisk_ami.so +load = mod_ncurses.so +load = mod_asterisk_ami.so load = mod_asterisk_queues.so load = mod_operator.so diff --git a/include/dlinkedlists.h b/include/dlinkedlists.h new file mode 100755 index 0000000..3df08bb --- /dev/null +++ b/include/dlinkedlists.h @@ -0,0 +1,1164 @@ +/* + * LBBS -- The Lightweight Bulletin Board System + * + * Copyright (C) 2024, Naveen Albert + * + * Naveen Albert + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief Read and write lock doubly linked lists + * + * \author Naveen Albert + * + * \note Parts of this doubly linked list implementation based on code in Asterisk's dlinkedlists.h (also GPLv2) + */ + +#ifndef DLINKEDLISTS_H +#define DLINKEDLISTS_H + +/*! + * \brief Locks a list. + * \param head This is a pointer to the list head structure + * + * This macro attempts to place an exclusive lock in the + * list head structure pointed to by head. + * \retval 0 on success + * \retval non-zero on failure + */ +#define DLLIST_LOCK(head) bbs_mutex_lock(&(head)->lock) + +/*! + * \brief Write locks a list. + * \param head This is a pointer to the list head structure + * + * This macro attempts to place an exclusive write lock in the + * list head structure pointed to by head. + * \retval 0 on success + * \retval non-zero on failure + */ +#define RWDLLIST_WRLOCK(head) bbs_rwlock_wrlock(&(head)->lock) + +/*! + * \brief Read locks a list. + * \param head This is a pointer to the list head structure + * + * This macro attempts to place a read lock in the + * list head structure pointed to by head. + * \retval 0 on success + * \retval non-zero on failure + */ +#define RWDLLIST_RDLOCK(head) bbs_rwlock_rdlock(&(head)->lock) + +/*! + * \brief Locks a list, without blocking if the list is locked. + * \param head This is a pointer to the list head structure + * + * This macro attempts to place an exclusive lock in the + * list head structure pointed to by head. + * \retval 0 on success + * \retval non-zero on failure + */ +#define DLLIST_TRYLOCK(head) bbs_mutex_trylock(&(head)->lock) + +/*! + * \brief Write locks a list, without blocking if the list is locked. + * \param head This is a pointer to the list head structure + * + * This macro attempts to place an exclusive write lock in the + * list head structure pointed to by head. + * \retval 0 on success + * \retval non-zero on failure + */ +#define RWDLLIST_TRYWRLOCK(head) bbs_rwlock_trywrlock(&(head)->lock) + +/*! + * \brief Read locks a list, without blocking if the list is locked. + * \param head This is a pointer to the list head structure + * + * This macro attempts to place a read lock in the + * list head structure pointed to by head. + * \retval 0 on success + * \retval non-zero on failure + */ +#define RWDLLIST_TRYRDLOCK(head) bbs_rwlock_tryrdlock(&(head)->lock) + +/*! + * \brief Attempts to unlock a list. + * \param head This is a pointer to the list head structure + * + * This macro attempts to remove an exclusive lock from the + * list head structure pointed to by head. If the list + * was not locked by this thread, this macro has no effect. + */ +#define DLLIST_UNLOCK(head) bbs_mutex_unlock(&(head)->lock) + +/*! + * \brief Attempts to unlock a read/write based list. + * \param head This is a pointer to the list head structure + * + * This macro attempts to remove a read or write lock from the + * list head structure pointed to by head. If the list + * was not locked by this thread, this macro has no effect. + */ +#define RWDLLIST_UNLOCK(head) bbs_rwlock_unlock(&(head)->lock) + +/*! + * \brief Defines a structure to be used to hold a list of specified type. + * \param name This will be the name of the defined structure. + * \param type This is the type of each list entry. + * + * This macro creates a structure definition that can be used + * to hold a list of the entries of type \a type. It does not actually + * declare (allocate) a structure; to do that, either follow this + * macro with the desired name of the instance you wish to declare, + * or use the specified \a name to declare instances elsewhere. + * + * Example usage: + * \code + * static DLLIST_HEAD(entry_list, entry) entries; + * \endcode + * + * This would define \c struct \c entry_list, and declare an instance of it named + * \a entries, all intended to hold a list of type \c struct \c entry. + */ +#define DLLIST_HEAD(name, type) \ + struct name { \ + struct type *first; \ + struct type *last; \ + bbs_mutex_t lock; \ + } + +/*! + * \brief Defines a structure to be used to hold a read/write list of specified type. + * \param name This will be the name of the defined structure. + * \param type This is the type of each list entry. + * + * This macro creates a structure definition that can be used + * to hold a list of the entries of type \a type. It does not actually + * declare (allocate) a structure; to do that, either follow this + * macro with the desired name of the instance you wish to declare, + * or use the specified \a name to declare instances elsewhere. + * + * Example usage: + * \code + * static RWDLLIST_HEAD(entry_list, entry) entries; + * \endcode + * + * This would define \c struct \c entry_list, and declare an instance of it named + * \a entries, all intended to hold a list of type \c struct \c entry. + */ +#define RWDLLIST_HEAD(name, type) \ + struct name { \ + struct type *first; \ + struct type *last; \ + bbs_rwlock_t lock; \ + } + +/*! + * \brief Defines a structure to be used to hold a list of specified type (with no lock). + * \param name This will be the name of the defined structure. + * \param type This is the type of each list entry. + * + * This macro creates a structure definition that can be used + * to hold a list of the entries of type \a type. It does not actually + * declare (allocate) a structure; to do that, either follow this + * macro with the desired name of the instance you wish to declare, + * or use the specified \a name to declare instances elsewhere. + * + * Example usage: + * \code + * static DLLIST_HEAD_NOLOCK(entry_list, entry) entries; + * \endcode + * + * This would define \c struct \c entry_list, and declare an instance of it named + * \a entries, all intended to hold a list of type \c struct \c entry. + */ +#define DLLIST_HEAD_NOLOCK(name, type) \ + struct name { \ + struct type *first; \ + struct type *last; \ + } + +/*! + * \brief Defines initial values for a declaration of DLLIST_HEAD + */ +#define DLLIST_HEAD_INIT_VALUE \ + { \ + .first = NULL, \ + .last = NULL, \ + .lock = BBS_RWLOCK_INITIALIZER, \ + } + +/*! + * \brief Defines initial values for a declaration of RWDLLIST_HEAD + */ +#define RWDLLIST_HEAD_INIT_VALUE \ + { \ + .first = NULL, \ + .last = NULL, \ + .lock = BBS_RWLOCK_INITIALIZER, \ + } + +/*! + * \brief Defines initial values for a declaration of DLLIST_HEAD_NOLOCK + */ +#define DLLIST_HEAD_NOLOCK_INIT_VALUE \ + { \ + .first = NULL, \ + .last = NULL, \ + } + +/*! + * \brief Defines a structure to be used to hold a list of specified type, statically initialized. + * \param name This will be the name of the defined structure. + * \param type This is the type of each list entry. + * + * This macro creates a structure definition that can be used + * to hold a list of the entries of type \a type, and allocates an instance + * of it, initialized to be empty. + * + * Example usage: + * \code + * static DLLIST_HEAD_STATIC(entry_list, entry); + * \endcode + * + * This would define \c struct \c entry_list, intended to hold a list of + * type \c struct \c entry. + */ +#if defined(MUTEX_INIT_W_CONSTRUCTORS) +#define DLLIST_HEAD_STATIC(name, type) \ + struct name { \ + struct type *first; \ + struct type *last; \ + bbs_mutex_t lock; \ + } name; \ + static void __attribute__((constructor)) __init_##name(void) \ + { \ + DLLIST_HEAD_INIT(&name); \ + } \ + static void __attribute__((destructor)) __fini_##name(void) \ + { \ + DLLIST_HEAD_DESTROY(&name); \ + } \ + struct __dummy_##name +#else +#define DLLIST_HEAD_STATIC(name, type) \ + struct name { \ + struct type *first; \ + struct type *last; \ + bbs_mutex_t lock; \ + } name = DLLIST_HEAD_INIT_VALUE +#endif + +/*! + * \brief Defines a structure to be used to hold a read/write list of specified type, statically initialized. + * \param name This will be the name of the defined structure. + * \param type This is the type of each list entry. + * + * This macro creates a structure definition that can be used + * to hold a list of the entries of type \a type, and allocates an instance + * of it, initialized to be empty. + * + * Example usage: + * \code + * static RWDLLIST_HEAD_STATIC(entry_list, entry); + * \endcode + * + * This would define \c struct \c entry_list, intended to hold a list of + * type \c struct \c entry. + */ +#define RWDLLIST_HEAD_STATIC(name, type) \ + struct name { \ + struct type *first; \ + struct type *last; \ + bbs_rwlock_t lock; \ + } name = RWDLLIST_HEAD_INIT_VALUE + +/*! + * \brief Defines a structure to be used to hold a list of specified type, statically initialized. + * + * This is the same as DLLIST_HEAD_STATIC, except without the lock included. + */ +#define DLLIST_HEAD_NOLOCK_STATIC(name, type) \ + struct name { \ + struct type *first; \ + struct type *last; \ + } name = DLLIST_HEAD_NOLOCK_INIT_VALUE + +/*! + * \brief Initializes a list head structure with a specified first entry. + * \param head This is a pointer to the list head structure + * \param entry pointer to the list entry that will become the head of the list + * + * This macro initializes a list head structure by setting the head + * entry to the supplied value and recreating the embedded lock. + */ +#define DLLIST_HEAD_SET(head, entry) \ + do { \ + (head)->first = (entry); \ + (head)->last = (entry); \ + bbs_mutex_init(&(head)->lock); \ + } while (0) + +/*! + * \brief Initializes an rwlist head structure with a specified first entry. + * \param head This is a pointer to the list head structure + * \param entry pointer to the list entry that will become the head of the list + * + * This macro initializes a list head structure by setting the head + * entry to the supplied value and recreating the embedded lock. + */ +#define RWDLLIST_HEAD_SET(head, entry) \ + do { \ + (head)->first = (entry); \ + (head)->last = (entry); \ + bbs_rwlock_init(&(head)->lock); \ + } while (0) + +/*! + * \brief Initializes a list head structure with a specified first entry. + * \param head This is a pointer to the list head structure + * \param entry pointer to the list entry that will become the head of the list + * + * This macro initializes a list head structure by setting the head + * entry to the supplied value. + */ +#define DLLIST_HEAD_SET_NOLOCK(head, entry) \ + do { \ + (head)->first = (entry); \ + (head)->last = (entry); \ + } while (0) + +/*! + * \brief Declare previous/forward links inside a list entry. + * \param type This is the type of each list entry. + * + * This macro declares a structure to be used to doubly link list entries together. + * It must be used inside the definition of the structure named in + * \a type, as follows: + * + * \code + * struct list_entry { + * ... + * DLLIST_ENTRY(list_entry) list; + * } + * \endcode + * + * The field name \a list here is arbitrary, and can be anything you wish. + */ +#define DLLIST_ENTRY(type) DLLIST_HEAD_NOLOCK(, type) + +#define RWDLLIST_ENTRY DLLIST_ENTRY + +/*! + * \brief Returns the first entry contained in a list. + * \param head This is a pointer to the list head structure + */ +#define DLLIST_FIRST(head) ((head)->first) + +#define RWDLLIST_FIRST DLLIST_FIRST + +/*! + * \brief Returns the last entry contained in a list. + * \param head This is a pointer to the list head structure + */ +#define DLLIST_LAST(head) ((head)->last) + +#define RWDLLIST_LAST DLLIST_LAST + +#define DLLIST_NEXT_DIRECTION(elm, field, direction) ((elm)->field.direction) + +#define RWDLLIST_NEXT_DIRECTION DLLIST_NEXT_DIRECTION + +/*! + * \brief Returns the next entry in the list after the given entry. + * \param elm This is a pointer to the current entry. + * \param field This is the name of the field (declared using DLLIST_ENTRY()) + * used to link entries of this list together. + */ +#define DLLIST_NEXT(elm, field) DLLIST_NEXT_DIRECTION(elm, field, first) + +#define RWDLLIST_NEXT DLLIST_NEXT + +/*! + * \brief Returns the previous entry in the list before the given entry. + * \param elm This is a pointer to the current entry. + * \param field This is the name of the field (declared using DLLIST_ENTRY()) + * used to link entries of this list together. + */ +#define DLLIST_PREV(elm, field) DLLIST_NEXT_DIRECTION(elm, field, last) + +#define RWDLLIST_PREV DLLIST_PREV + +/*! + * \brief Checks whether the specified list contains any entries. + * \param head This is a pointer to the list head structure + * + * \return non-zero if the list has entries + * \return zero if not. + */ +#define DLLIST_EMPTY(head) (DLLIST_FIRST(head) == NULL) + +#define RWDLLIST_EMPTY DLLIST_EMPTY + +/*! + * \brief Checks whether the specified list contains the element. + * \param head This is a pointer to the list head structure + * \param elm This is a pointer to the list element to see if in list. + * \param field List node field for the next node information. + * + * \return elm if the list has elm in it. + * \return NULL if not. + */ +#define DLLIST_IS_MEMBER(head, elm, field) \ + ({ \ + typeof((head)->first) __cur; \ + typeof((elm)) __elm = (elm); \ + if (!__elm) { \ + __cur = NULL; \ + } else { \ + __cur = (head)->first; \ + while (__cur && __cur != __elm) { \ + __cur = __cur->field.first; \ + } \ + } \ + __cur; \ + }) + +#define RWDLLIST_IS_MEMBER DLLIST_IS_MEMBER + +/*! + * \brief Traverse a doubly linked list using the specified direction list. + * + * \param head List head structure pointer. + * \param var This is the name of the variable that will hold a pointer to the + * current list node on each iteration. It must be declared before calling + * this macro. + * \param field List node field for the next node information. (declared using DLLIST_ENTRY()) + * \param start Specified list node to start traversal: first or last + * + * This macro is use to loop over (traverse) the nodes in a list. It uses a + * \a for loop, and supplies the enclosed code with a pointer to each list + * node as it loops. It is typically used as follows: + * \code + * static DLLIST_HEAD(entry_list, list_entry) entries; + * ... + * struct list_entry { + * ... + * DLLIST_ENTRY(list_entry) list; + * } + * ... + * struct list_entry *current; + * ... + * DLLIST_TRAVERSE_DIRECTION(&entries, current, list, first) { + * (do something with current here (travers list in forward direction)) + * } + * ... + * DLLIST_TRAVERSE_DIRECTION(&entries, current, list, last) { + * (do something with current here (travers list in reverse direction)) + * } + * \endcode + */ +#define DLLIST_TRAVERSE_DIRECTION(head, var, field, start) \ + for ((var) = (head)->start; (var); (var) = DLLIST_NEXT_DIRECTION(var, field, start)) + +#define RWDLLIST_TRAVERSE_DIRECTION DLLIST_TRAVERSE_DIRECTION + +/*! + * \brief Loops over (traverses) the entries in a list. + * \param head This is a pointer to the list head structure + * \param var This is the name of the variable that will hold a pointer to the + * current list entry on each iteration. It must be declared before calling + * this macro. + * \param field This is the name of the field (declared using DLLIST_ENTRY()) + * used to link entries of this list together. + * + * This macro is use to loop over (traverse) the entries in a list. It uses a + * \a for loop, and supplies the enclosed code with a pointer to each list + * entry as it loops. It is typically used as follows: + * \code + * static DLLIST_HEAD(entry_list, list_entry) entries; + * ... + * struct list_entry { + * ... + * DLLIST_ENTRY(list_entry) list; + * } + * ... + * struct list_entry *current; + * ... + * DLLIST_TRAVERSE(&entries, current, list) { + * (do something with current here) + * } + * \endcode + * \warning If you modify the forward-link pointer contained in the \a current entry while + * inside the loop, the behavior will be unpredictable. At a minimum, the following + * macros will modify the forward-link pointer, and should not be used inside + * DLLIST_TRAVERSE() against the entry pointed to by the \a current pointer without + * careful consideration of their consequences: + * \li DLLIST_NEXT() (when used as an lvalue) + * \li DLLIST_INSERT_AFTER() + * \li DLLIST_INSERT_HEAD() + * \li DLLIST_INSERT_TAIL() + */ +#define DLLIST_TRAVERSE(head,var,field) \ + DLLIST_TRAVERSE_DIRECTION(head, var, field, first) + +#define RWDLLIST_TRAVERSE DLLIST_TRAVERSE + +/*! + * \brief Loops over (traverses) the entries in a list in reverse order, starting at the end. + * \param head This is a pointer to the list head structure + * \param var This is the name of the variable that will hold a pointer to the + * current list entry on each iteration. It must be declared before calling + * this macro. + * \param field This is the name of the field (declared using DLLIST_ENTRY()) + * used to link entries of this list together. + * + * This macro is use to loop over (traverse) the entries in a list in reverse order. It uses a + * \a for loop, and supplies the enclosed code with a pointer to each list + * entry as it loops. It is typically used as follows: + * \code + * static DLLIST_HEAD(entry_list, list_entry) entries; + * ... + * struct list_entry { + * ... + * DLLIST_ENTRY(list_entry) list; + * } + * ... + * struct list_entry *current; + * ... + * DLLIST_TRAVERSE_BACKWARDS(&entries, current, list) { + * (do something with current here) + * } + * \endcode + * \warning If you modify the forward-link pointer contained in the \a current entry while + * inside the loop, the behavior will be unpredictable. At a minimum, the following + * macros will modify the forward-link pointer, and should not be used inside + * DLLIST_TRAVERSE() against the entry pointed to by the \a current pointer without + * careful consideration of their consequences: + * \li DLLIST_PREV() (when used as an lvalue) + * \li DLLIST_INSERT_BEFORE() + * \li DLLIST_INSERT_HEAD() + * \li DLLIST_INSERT_TAIL() + */ +#define DLLIST_TRAVERSE_BACKWARDS(head,var,field) \ + DLLIST_TRAVERSE_DIRECTION(head, var, field, last) + +#define RWDLLIST_TRAVERSE_BACKWARDS DLLIST_TRAVERSE_BACKWARDS + +/*! + * \brief Safe traversal of a doubly linked list using the specified direction list. + * + * \param head List head structure pointer. + * \param var This is the name of the variable that will hold a pointer to the + * current list node on each iteration. It must be declared before calling + * this macro. + * \param field List node field for the next node information. (declared using DLLIST_ENTRY()) + * \param start Specified list node to start traversal: first or last + * + * This macro is used to safely loop over (traverse) the nodes in a list. It + * uses a \a for loop, and supplies the enclosed code with a pointer to each list + * node as it loops. It is typically used as follows: + * + * \code + * static DLLIST_HEAD(entry_list, list_entry) entries; + * ... + * struct list_entry { + * ... + * DLLIST_ENTRY(list_entry) list; + * } + * ... + * struct list_entry *current; + * ... + * DLLIST_TRAVERSE_DIRECTION_SAFE_BEGIN(&entries, current, list, first) { + * (do something with current here (travers list in forward direction)) + * } + * ... + * DLLIST_TRAVERSE_DIRECTION_SAFE_BEGIN(&entries, current, list, last) { + * (do something with current here (travers list in reverse direction)) + * } + * DLLIST_TRAVERSE_DIRECTION_SAFE_END; + * \endcode + * + * It differs from DLLIST_TRAVERSE() in that the code inside the loop can modify + * (or even free, after calling DLLIST_REMOVE_CURRENT()) the entry pointed to by + * the \a current pointer without affecting the loop traversal. + */ +#define DLLIST_TRAVERSE_DIRECTION_SAFE_BEGIN(head, var, field, start) \ + do { \ + typeof((head)) __list_head = (head); \ + typeof(__list_head->first) __list_current; \ + typeof(__list_head->first) __list_first; \ + typeof(__list_head->first) __list_last; \ + typeof(__list_head->first) __list_next; \ + for ((var) = __list_head->start, \ + __list_current = (var), \ + __list_first = (var) ? (var)->field.first : NULL, \ + __list_last = (var) ? (var)->field.last : NULL, \ + __list_next = (var) ? DLLIST_NEXT_DIRECTION(var, field, start) : NULL; \ + (var); \ + (void) __list_current,/* To quiet compiler? */ \ + (void) __list_first,/* To quiet compiler? */ \ + (void) __list_last,/* To quiet compiler? */ \ + (var) = __list_next, \ + __list_current = (var), \ + __list_first = (var) ? (var)->field.first : NULL, \ + __list_last = (var) ? (var)->field.last : NULL, \ + __list_next = (var) ? DLLIST_NEXT_DIRECTION(var, field, start) : NULL \ + ) + +#define RWDLLIST_TRAVERSE_DIRECTION_SAFE_BEGIN DLLIST_TRAVERSE_DIRECTION_SAFE_BEGIN + +/*! + * \brief Inserts a list node before the current node during a traversal. + * \param elm This is a pointer to the entry to be inserted. + * \param field This is the name of the field (declared using DLLIST_ENTRY()) + * used to link nodes of this list together. + */ +#define DLLIST_INSERT_BEFORE_CURRENT(elm, field) \ + do { \ + typeof((elm)) __elm = (elm); \ + __elm->field.last = __list_last; \ + __elm->field.first = __list_current; \ + if (__list_head->first == __list_current) { \ + __list_head->first = __elm; \ + } else { \ + __list_last->field.first = __elm; \ + } \ + __list_current->field.last = __elm; \ + if (__list_next == __list_last) { \ + __list_next = __elm; \ + } \ + __list_last = __elm; \ + } while (0) + +#define RWDLLIST_INSERT_BEFORE_CURRENT DLLIST_INSERT_BEFORE_CURRENT + +/*! + * \brief Inserts a list node after the current node during a traversal. + * \param elm This is a pointer to the node to be inserted. + * \param field This is the name of the field (declared using DLLIST_ENTRY()) + * used to link nodes of this list together. + */ +#define DLLIST_INSERT_AFTER_CURRENT(elm, field) \ + do { \ + typeof((elm)) __elm = (elm); \ + __elm->field.first = __list_first; \ + __elm->field.last = __list_current; \ + if (__list_head->last == __list_current) { \ + __list_head->last = __elm; \ + } else { \ + __list_first->field.last = __elm; \ + } \ + __list_current->field.first = __elm; \ + if (__list_next == __list_first) { \ + __list_next = __elm; \ + } \ + __list_first = __elm; \ + } while (0) + +#define RWDLLIST_INSERT_AFTER_CURRENT DLLIST_INSERT_AFTER_CURRENT + +/*! + * \brief Removes the \a current entry from a list during a traversal. + * \param field This is the name of the field (declared using DLLIST_ENTRY()) + * used to link entries of this list together. + * + * \note This macro can \b only be used inside an DLLIST_TRAVERSE_SAFE_BEGIN() + * block; it is used to unlink the current entry from the list without affecting + * the list traversal (and without having to re-traverse the list to modify the + * previous entry, if any). + */ +#define DLLIST_REMOVE_CURRENT(field) \ + do { \ + if (__list_first) { \ + __list_first->field.last = __list_last; \ + } else { \ + __list_head->last = __list_last; \ + } \ + if (__list_last) { \ + __list_last->field.first = __list_first; \ + } else { \ + __list_head->first = __list_first; \ + } \ + __list_current->field.first = NULL; \ + __list_current->field.last = NULL; \ + __list_current = NULL; \ + } while (0) + +#define RWDLLIST_REMOVE_CURRENT DLLIST_REMOVE_CURRENT + +/*! + * \brief Move the current list entry to another list at the tail. + * + * \note This is a silly macro. It should be done explicitly + * otherwise the field parameter must be the same for the two + * lists. + * + * DLLIST_REMOVE_CURRENT(field); + * DLLIST_INSERT_TAIL(newhead, var, other_field); + */ +#define DLLIST_MOVE_CURRENT(newhead, field) \ + do { \ + typeof ((newhead)->first) __list_cur = __list_current; \ + DLLIST_REMOVE_CURRENT(field); \ + DLLIST_INSERT_TAIL((newhead), __list_cur, field); \ + } while (0) + +#define RWDLLIST_MOVE_CURRENT DLLIST_MOVE_CURRENT + +/*! + * \brief Move the current list entry to another list at the head. + * + * \note This is a silly macro. It should be done explicitly + * otherwise the field parameter must be the same for the two + * lists. + * + * DLLIST_REMOVE_CURRENT(field); + * DLLIST_INSERT_HEAD(newhead, var, other_field); + */ +#define DLLIST_MOVE_CURRENT_BACKWARDS(newhead, field) \ + do { \ + typeof ((newhead)->first) __list_cur = __list_current; \ + DLLIST_REMOVE_CURRENT(field); \ + DLLIST_INSERT_HEAD((newhead), __list_cur, field); \ + } while (0) + +#define RWDLLIST_MOVE_CURRENT_BACKWARDS DLLIST_MOVE_CURRENT_BACKWARDS + +#define DLLIST_TRAVERSE_DIRECTION_SAFE_END \ + } while (0) + +#define RWDLLIST_TRAVERSE_DIRECTION_SAFE_END DLLIST_TRAVERSE_DIRECTION_SAFE_END + +/*! + * \brief Loops safely over (traverses) the entries in a list. + * \param head This is a pointer to the list head structure + * \param var This is the name of the variable that will hold a pointer to the + * current list entry on each iteration. It must be declared before calling + * this macro. + * \param field This is the name of the field (declared using DLLIST_ENTRY()) + * used to link entries of this list together. + * + * This macro is used to safely loop over (traverse) the entries in a list. It + * uses a \a for loop, and supplies the enclosed code with a pointer to each list + * entry as it loops. It is typically used as follows: + * + * \code + * static DLLIST_HEAD(entry_list, list_entry) entries; + * ... + * struct list_entry { + * ... + * DLLIST_ENTRY(list_entry) list; + * } + * ... + * struct list_entry *current; + * ... + * DLLIST_TRAVERSE_SAFE_BEGIN(&entries, current, list) { + * (do something with current here) + * } + * DLLIST_TRAVERSE_SAFE_END; + * \endcode + * + * It differs from DLLIST_TRAVERSE() in that the code inside the loop can modify + * (or even free, after calling DLLIST_REMOVE_CURRENT()) the entry pointed to by + * the \a current pointer without affecting the loop traversal. + */ +#define DLLIST_TRAVERSE_SAFE_BEGIN(head, var, field) \ + DLLIST_TRAVERSE_DIRECTION_SAFE_BEGIN(head, var, field, first) + +#define RWDLLIST_TRAVERSE_SAFE_BEGIN DLLIST_TRAVERSE_SAFE_BEGIN + +/*! + * \brief Loops safely over (traverses) the entries in a list. + * \param head This is a pointer to the list head structure + * \param var This is the name of the variable that will hold a pointer to the + * current list entry on each iteration. It must be declared before calling + * this macro. + * \param field This is the name of the field (declared using DLLIST_ENTRY()) + * used to link entries of this list together. + * + * This macro is used to safely loop over (traverse) the entries in a list. It + * uses a \a for loop, and supplies the enclosed code with a pointer to each list + * entry as it loops. It is typically used as follows: + * + * \code + * static DLLIST_HEAD(entry_list, list_entry) entries; + * ... + * struct list_entry { + * ... + * DLLIST_ENTRY(list_entry) list; + * } + * ... + * struct list_entry *current; + * ... + * DLLIST_TRAVERSE_SAFE_BEGIN(&entries, current, list) { + * (do something with current here) + * } + * DLLIST_TRAVERSE_SAFE_END; + * \endcode + * + * It differs from DLLIST_TRAVERSE() in that the code inside the loop can modify + * (or even free, after calling DLLIST_REMOVE_CURRENT()) the entry pointed to by + * the \a current pointer without affecting the loop traversal. + */ +#define DLLIST_TRAVERSE_BACKWARDS_SAFE_BEGIN(head, var, field) \ + DLLIST_TRAVERSE_DIRECTION_SAFE_BEGIN(head, var, field, last) + +#define RWDLLIST_TRAVERSE_BACKWARDS_SAFE_BEGIN DLLIST_TRAVERSE_BACKWARDS_SAFE_BEGIN + +/*! + * \brief Inserts a list entry after the current entry during a backwards traversal. Since + * this is a backwards traversal, this will insert the entry AFTER the current + * element. Since this is a backwards traveral, though, this would be BEFORE + * the current entry in traversal order. Confusing? + * \param elm This is a pointer to the entry to be inserted. + * \param field This is the name of the field (declared using DLLIST_ENTRY()) + * used to link entries of this list together. + */ +#define DLLIST_INSERT_BEFORE_CURRENT_BACKWARDS(elm, field) \ + DLLIST_INSERT_AFTER_CURRENT(elm, field) + +#define RWDLLIST_INSERT_BEFORE_CURRENT_BACKWARDS DLLIST_INSERT_BEFORE_CURRENT_BACKWARDS + +/*! + * \brief Closes a safe loop traversal block. + */ +#define DLLIST_TRAVERSE_SAFE_END DLLIST_TRAVERSE_DIRECTION_SAFE_END + +#define RWDLLIST_TRAVERSE_SAFE_END DLLIST_TRAVERSE_SAFE_END + +/*! + * \brief Closes a safe loop traversal block. + */ +#define DLLIST_TRAVERSE_BACKWARDS_SAFE_END DLLIST_TRAVERSE_DIRECTION_SAFE_END + +#define RWDLLIST_TRAVERSE_BACKWARDS_SAFE_END DLLIST_TRAVERSE_BACKWARDS_SAFE_END + +/*! + * \brief Initializes a list head structure. + * \param head This is a pointer to the list head structure + * + * This macro initializes a list head structure by setting the head + * entry to \a NULL (empty list) and recreating the embedded lock. + */ +#define DLLIST_HEAD_INIT(head) \ + { \ + (head)->first = NULL; \ + (head)->last = NULL; \ + bbs_mutex_init(&(head)->lock); \ + } + +/*! + * \brief Initializes an rwlist head structure. + * \param head This is a pointer to the list head structure + * + * This macro initializes a list head structure by setting the head + * entry to \a NULL (empty list) and recreating the embedded lock. + */ +#define RWDLLIST_HEAD_INIT(head) \ + { \ + (head)->first = NULL; \ + (head)->last = NULL; \ + bbs_rwlock_init(&(head)->lock); \ + } + +/*! + * \brief Destroys a list head structure. + * \param head This is a pointer to the list head structure + * + * This macro destroys a list head structure by setting the head + * entry to \a NULL (empty list) and destroying the embedded lock. + * It does not free the structure from memory. + */ +#define DLLIST_HEAD_DESTROY(head) \ + { \ + (head)->first = NULL; \ + (head)->last = NULL; \ + bbs_mutex_destroy(&(head)->lock); \ + } + +/*! + * \brief Destroys an rwlist head structure. + * \param head This is a pointer to the list head structure + * + * This macro destroys a list head structure by setting the head + * entry to \a NULL (empty list) and destroying the embedded lock. + * It does not free the structure from memory. + */ +#define RWDLLIST_HEAD_DESTROY(head) \ + { \ + (head)->first = NULL; \ + (head)->last = NULL; \ + bbs_rwlock_destroy(&(head)->lock); \ + } + +/*! + * \brief Initializes a list head structure. + * \param head This is a pointer to the list head structure + * + * This macro initializes a list head structure by setting the head + * entry to \a NULL (empty list). There is no embedded lock handling + * with this macro. + */ +#define DLLIST_HEAD_INIT_NOLOCK(head) \ + { \ + (head)->first = NULL; \ + (head)->last = NULL; \ + } + +/*! + * \brief Inserts a list entry after a given entry. + * \param head This is a pointer to the list head structure + * \param listelm This is a pointer to the entry after which the new entry should + * be inserted. + * \param elm This is a pointer to the entry to be inserted. + * \param field This is the name of the field (declared using DLLIST_ENTRY()) + * used to link entries of this list together. + */ +#define DLLIST_INSERT_AFTER(head, listelm, elm, field) \ + do { \ + typeof((listelm)) __listelm = (listelm); \ + typeof((elm)) __elm = (elm); \ + __elm->field.first = __listelm->field.first; \ + __elm->field.last = __listelm; \ + if ((head)->last == __listelm) { \ + (head)->last = __elm; \ + } else { \ + __listelm->field.first->field.last = __elm; \ + } \ + __listelm->field.first = __elm; \ + } while (0) + +#define RWDLLIST_INSERT_AFTER DLLIST_INSERT_AFTER + +/*! + * \brief Inserts a list entry before a given entry. + * \param head This is a pointer to the list head structure + * \param listelm This is a pointer to the entry before which the new entry should + * be inserted. + * \param elm This is a pointer to the entry to be inserted. + * \param field This is the name of the field (declared using DLLIST_ENTRY()) + * used to link entries of this list together. + */ +#define DLLIST_INSERT_BEFORE(head, listelm, elm, field) \ + do { \ + typeof((listelm)) __listelm = (listelm); \ + typeof((elm)) __elm = (elm); \ + __elm->field.last = __listelm->field.last; \ + __elm->field.first = __listelm; \ + if ((head)->first == __listelm) { \ + (head)->first = __elm; \ + } else { \ + __listelm->field.last->field.first = __elm; \ + } \ + __listelm->field.last = __elm; \ + } while (0) + +#define RWDLLIST_INSERT_BEFORE DLLIST_INSERT_BEFORE + +/*! + * \brief Inserts a list entry at the head of a list. + * \param head This is a pointer to the list head structure + * \param elm This is a pointer to the entry to be inserted. + * \param field This is the name of the field (declared using DLLIST_ENTRY()) + * used to link entries of this list together. + */ +#define DLLIST_INSERT_HEAD(head, elm, field) \ + do { \ + typeof((elm)) __elm = (elm); \ + __elm->field.last = NULL; \ + __elm->field.first = (head)->first; \ + if (!(head)->first) { \ + (head)->last = __elm; \ + } else { \ + (head)->first->field.last = __elm; \ + } \ + (head)->first = __elm; \ + } while (0) + +#define RWDLLIST_INSERT_HEAD DLLIST_INSERT_HEAD + +/*! + * \brief Appends a list entry to the tail of a list. + * \param head This is a pointer to the list head structure + * \param elm This is a pointer to the entry to be appended. + * \param field This is the name of the field (declared using DLLIST_ENTRY()) + * used to link entries of this list together. + * + * Note: The link field in the appended entry is \b not modified, so if it is + * actually the head of a list itself, the entire list will be appended + * temporarily (until the next DLLIST_INSERT_TAIL is performed). + */ +#define DLLIST_INSERT_TAIL(head, elm, field) \ + do { \ + typeof((elm)) __elm = (elm); \ + __elm->field.first = NULL; \ + if (!(head)->first) { \ + __elm->field.last = NULL; \ + (head)->first = __elm; \ + } else { \ + __elm->field.last = (head)->last; \ + (head)->last->field.first = __elm; \ + } \ + (head)->last = __elm; \ + } while (0) + +#define RWDLLIST_INSERT_TAIL DLLIST_INSERT_TAIL + +/*! + * \brief Appends a whole list to the tail of a list. + * \param head This is a pointer to the list head structure + * \param list This is a pointer to the list to be appended. + * \param field This is the name of the field (declared using DLLIST_ENTRY()) + * used to link entries of this list together. + * + * Note: The source list (the \a list parameter) will be empty after + * calling this macro (the list entries are \b moved to the target list). + */ +#define DLLIST_APPEND_DLLIST(head, list, field) \ + do { \ + if (!(head)->first) { \ + (head)->first = (list)->first; \ + (head)->last = (list)->last; \ + } else { \ + (head)->last->field.first = (list)->first; \ + (list)->first->field.last = (head)->last; \ + (head)->last = (list)->last; \ + } \ + (list)->first = NULL; \ + (list)->last = NULL; \ + } while (0) + +#define RWDLLIST_APPEND_DLLIST DLLIST_APPEND_DLLIST + +/*! + * \brief Removes and returns the head entry from a list. + * \param head This is a pointer to the list head structure + * \param field This is the name of the field (declared using DLLIST_ENTRY()) + * used to link entries of this list together. + * + * Removes the head entry from the list, and returns a pointer to it. + * This macro is safe to call on an empty list. + */ +#define DLLIST_REMOVE_HEAD(head, field) \ + ({ \ + typeof((head)->first) cur = (head)->first; \ + if (cur) { \ + (head)->first = cur->field.first; \ + if ((head)->first) { \ + (head)->first->field.last = NULL; \ + } \ + cur->field.first = NULL; \ + cur->field.last = NULL; \ + if ((head)->last == cur) { \ + (head)->last = NULL; \ + } \ + } \ + cur; \ + }) + +#define RWDLLIST_REMOVE_HEAD DLLIST_REMOVE_HEAD + +/*! + * \brief Removes and returns the tail node from a list. + * \param head This is a pointer to the list head structure + * \param field This is the name of the field (declared using DLLIST_ENTRY()) + * used to link nodes of this list together. + * + * Removes the tail entry from the list, and returns a pointer to it. + * This macro is safe to call on an empty list. + */ +#define DLLIST_REMOVE_TAIL(head, field) \ + ({ \ + typeof((head)->last) cur = (head)->last; \ + if (cur) { \ + (head)->last = cur->field.last; \ + if ((head)->last) { \ + (head)->last->field.first = NULL; \ + } \ + cur->field.first = NULL; \ + cur->field.last = NULL; \ + if ((head)->first == cur) { \ + (head)->first = NULL; \ + } \ + } \ + cur; \ + }) + +#define RWDLLIST_REMOVE_TAIL DLLIST_REMOVE_TAIL + +/*! + * \brief Removes a specific entry from a list. + * \param head This is a pointer to the list head structure + * \param elm This is a pointer to the entry to be removed. + * \param field This is the name of the field (declared using DLLIST_ENTRY()) + * used to link entries of this list together. + * \warning The removed entry is \b not freed. + */ +#define DLLIST_REMOVE(head, elm, field) \ + do { \ + typeof((elm)) __elm = (elm); \ + if (__elm) { \ + if (__elm->field.first) { \ + __elm->field.first->field.last = __elm->field.last; \ + } else { \ + (head)->last = __elm->field.last; \ + } \ + if (__elm->field.last) { \ + __elm->field.last->field.first = __elm->field.first; \ + } else { \ + (head)->first = __elm->field.first; \ + } \ + __elm->field.first = NULL; \ + __elm->field.last = NULL; \ + } \ + } while (0) + +#define RWDLLIST_REMOVE DLLIST_REMOVE + +/*! + * \brief Removes a specific node from a list if it is in the list. + * \param head This is a pointer to the list head structure + * \param elm This is a pointer to the node to be removed. + * \param field This is the name of the field (declared using DLLIST_ENTRY()) + * used to link nodes of this list together. + * \warning The removed node is \b not freed. + * \return elm if the list had elm in it. + * \return NULL if not. + */ +#define DLLIST_REMOVE_VERIFY(head, elm, field) \ + ({ \ + typeof((elm)) __res = DLLIST_IS_MEMBER(head, elm, field); \ + DLLIST_REMOVE(head, __res, field); \ + __res; \ + }) + +#define RWDLLIST_REMOVE_VERIFY DLLIST_REMOVE_VERIFY + +/*! + * \brief Removes all the entries from a list and invokes a destructor on each entry + * \param head This is a pointer to the list head structure + * \param field This is the name of the field (declared using RWLIST_ENTRY()) + * used to link entries of this list together. + * \param destructor A destructor function to call on each element (e.g. free) + * + * This macro is safe to call on an empty list. + */ +#define DLLIST_REMOVE_ALL(head, field, destructor) { \ + typeof((head)) __list_head = head; \ + typeof(__list_head->first) __list_current; \ + while ((__list_current = DLLIST_REMOVE_HEAD(head, field))) { \ + destructor(__list_current); \ + } \ +} + +#define RWDLLIST_REMOVE_ALL DLLIST_REMOVE_ALL + +#endif /* _DLINKEDLISTS_H */ diff --git a/include/module.h b/include/module.h index 5ce25e8..f5a7e2d 100644 --- a/include/module.h +++ b/include/module.h @@ -96,7 +96,7 @@ void __bbs_module_unref(struct bbs_module *mod, int pair, void *refmod, const ch */ #define bbs_require_module(module) __bbs_require_module(module, BBS_MODULE_SELF) -struct bbs_module *__bbs_require_module(const char *module, void *refmod); +struct bbs_module *__bbs_require_module(const char *module, void *refmod) __attribute__ ((nonnull (1, 2))); /*! \brief Indicate that this module is no longer dependent on the specified module. */ void __bbs_unrequire_module(struct bbs_module *mod, void *refmod); diff --git a/modules/mod_chanserv.c b/modules/mod_chanserv.c index fcefe8f..8cc0eea 100644 --- a/modules/mod_chanserv.c +++ b/modules/mod_chanserv.c @@ -1152,7 +1152,7 @@ static int chanserv_init(void) static int load_config(void) { - struct bbs_config *cfg = bbs_config_load("mod_chanserv.conf", 1); + struct bbs_config *cfg = bbs_config_load("mod_chanserv.conf", 0); if (!cfg) { bbs_error("mod_chanserv.conf is missing, module will decline to load\n"); diff --git a/modules/mod_mysql.c b/modules/mod_mysql.c index 33212d6..7806405 100644 --- a/modules/mod_mysql.c +++ b/modules/mod_mysql.c @@ -646,7 +646,12 @@ static int load_config(void) } /* Don't destroy the config, mod_auth_mysql will read it again to parse some settings that apply only to it. - * XXX These things should really be in separate config files? Need a mod_mysql.conf */ + * XXX These things should really be in separate config files? Need a mod_mysql.conf + * + * UPDATE: mod_auth_mysql loads the config file with caching disabled, so it will get reparsed anyways. + * This is probably a good thing since it reduces the number of places the DB password is in memory... + * As such, there's no downside to destroying the config here. */ + bbs_config_free(cfg); return 0; } diff --git a/modules/mod_sysop.c b/modules/mod_sysop.c index 5c6715f..0b5ac2c 100644 --- a/modules/mod_sysop.c +++ b/modules/mod_sysop.c @@ -301,7 +301,11 @@ static void *sysop_handler(void *varg) pfds[1].fd = console_alertpipe[0]; pfds[1].events = POLLIN; - show_copyright(sysopfdout, 1); + if (console->remote || bbs_is_fully_started()) { + /* For foreground console, if BBS is still starting, + * we already registered a startup callback to show copyright later. */ + show_copyright(sysopfdout, 1); + } histentry = NULL; /* initiailization must be after pthread_cleanup_push to avoid "variable might be clobbered" warning */ for (;;) {