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 (;;) {