diff --git a/include/ircd/mods.h b/include/ircd/mods.h deleted file mode 100644 index a82b8f201..000000000 --- a/include/ircd/mods.h +++ /dev/null @@ -1,302 +0,0 @@ -// Matrix Construct -// -// Copyright (C) Matrix Construct Developers, Authors & Contributors -// Copyright (C) 2016-2018 Jason Volk -// -// Permission to use, copy, modify, and/or distribute this software for any -// purpose with or without fee is hereby granted, provided that the above -// copyright notice and this permission notice is present in all copies. The -// full license for this software is available in the LICENSE file. - -#pragma once -#define HAVE_IRCD_MODS_H - -/// Modules system -namespace ircd::mods -{ - IRCD_EXCEPTION(ircd::error, error) - IRCD_EXCEPTION(error, filesystem_error) - IRCD_EXCEPTION(error, invalid_export) - IRCD_EXCEPTION(error, expired_symbol) - IRCD_EXCEPTION(error, undefined_symbol) - - struct mod; - struct module; - struct sym_ptr; - template struct import; - template struct import_shared; - - struct paths extern paths; - - // Platform (.so|.dll) postfixing - std::string postfixed(const std::string &name); - std::string unpostfixed(const std::string &name); - - // Section & Symbol utilites - std::vector sections(const std::string &fullpath); - std::vector symbols(const std::string &fullpath, const std::string §ion); - std::vector symbols(const std::string &fullpath); - std::unordered_map mangles(const std::vector &); - std::unordered_map mangles(const std::string &fullpath, const std::string §ion); - std::unordered_map mangles(const std::string &fullpath); - - // Find module names where symbol resides - bool has_symbol(const std::string &name, const std::string &symbol); - std::vector find_symbol(const std::string &symbol); - - // returns dir/name of first dir containing 'name' (and this will be a loadable module) - // Unlike libltdl, the reason each individual candidate failed is presented in a vector. - std::string search(const std::string &name, std::vector &why); - std::string search(const std::string &name); - - // Potential modules available to load - std::forward_list available(); - bool available(const std::string &name); - bool loaded(const std::string &name); - - string_view name(const mod &); - string_view path(const mod &); -} - -// Bring struct module into main ircd:: -namespace ircd -{ - using mods::module; - using mods::import; - using mods::import_shared; -} - -struct ircd::mods::paths -:std::vector -{ - bool added(const std::string &dir) const; - - bool del(const std::string &dir); - bool add(const std::string &dir, std::nothrow_t); - bool add(const std::string &dir); - - paths(); -}; - -struct ircd::mods::module -:std::shared_ptr -{ - const std::string &name() const; - const std::string &path() const; - const std::string &mangle(const std::string &) const; - - bool has(const std::string &name) const; - - template const T *ptr(const std::string &name) const; - template T *ptr(const std::string &name); - - template const T &get(const std::string &name) const; - template T &get(const std::string &name); - - module(std::shared_ptr ptr = {}) - :std::shared_ptr{std::move(ptr)} - {} - - module(const std::string &name); - ~module() noexcept; -}; - -namespace ircd::mods -{ - template<> const uint8_t *module::ptr(const std::string &name) const; - template<> uint8_t *module::ptr(const std::string &name); -} - -template -T & -ircd::mods::module::get(const std::string &name) -{ - return *ptr(name); -} - -template -const T & -ircd::mods::module::get(const std::string &name) -const -{ - return *ptr(name); -} - -template -T * -ircd::mods::module::ptr(const std::string &name) -{ - return reinterpret_cast(ptr(name)); -} - -template -const T * -ircd::mods::module::ptr(const std::string &name) -const -{ - return reinterpret_cast(ptr(name)); -} - -/// Representation of a symbol in a loaded library (non-template; low level). -/// -class ircd::mods::sym_ptr -:std::weak_ptr -{ - void *ptr {nullptr}; - - public: - bool operator!() const; - operator bool() const; - - template const T *get() const; - template const T *operator->() const; - template const T &operator*() const; - template auto operator()(args&&... a) const; - - template T *get(); - template T *operator->(); - template T &operator*(); - - sym_ptr() = default; - sym_ptr(module, const std::string &symname); - sym_ptr(const std::string &modname, const std::string &symname); - ~sym_ptr() noexcept; -}; - -template -T & -ircd::mods::sym_ptr::operator*() -{ - if(unlikely(expired())) - throw expired_symbol("The reference to a symbol in another module is no longer valid"); - - return *get(); -} - -template -T * -ircd::mods::sym_ptr::operator->() -{ - return get(); -} - -template -T * -ircd::mods::sym_ptr::get() -{ - return reinterpret_cast(ptr); -} - -template -auto -ircd::mods::sym_ptr::operator()(args&&... a) -const -{ - return (*get())(std::forward(a)...); -} - -template -const T & -ircd::mods::sym_ptr::operator*() -const -{ - if(unlikely(expired())) - throw expired_symbol("The const reference to a symbol in another module is no longer valid"); - - return *get(); -} - -template -const T * -ircd::mods::sym_ptr::operator->() -const -{ - return get(); -} - -template -const T * -ircd::mods::sym_ptr::get() -const -{ - return reinterpret_cast(ptr); -} - -inline ircd::mods::sym_ptr::operator -bool() -const -{ - return !bool(*this); -} - -inline bool -ircd::mods::sym_ptr::operator!() -const -{ - return !ptr || expired(); -} - -/// Representation of a symbol in a loaded shared library -/// -template -struct ircd::mods::import -:sym_ptr -{ - template - auto operator()(args&&... a) const - { - return sym_ptr::operator()(std::forward(a)...); - } - - const T *operator->() const { return sym_ptr::operator->(); } - const T &operator*() const { return sym_ptr::operator*(); } - operator const T &() const { return sym_ptr::operator*(); } - - T *operator->() { return sym_ptr::operator->(); } - T &operator*() { return sym_ptr::operator*(); } - operator T &() { return sym_ptr::operator*(); } - - using sym_ptr::sym_ptr; -}; - -/// Convenience for importing an std::shared_ptr from a loaded lib -/// -template -struct ircd::mods::import_shared -:import> -,std::shared_ptr -{ - using std::shared_ptr::get; - using std::shared_ptr::operator bool; - using std::shared_ptr::operator->; - using std::shared_ptr::operator*; - - operator const T &() const { return std::shared_ptr::operator*(); } - operator T &() { return std::shared_ptr::operator*(); } - - import_shared() = default; - import_shared(module, const std::string &symname); - import_shared(const std::string &modname, const std::string &symname); -}; - -template -ircd::mods::import_shared::import_shared(const std::string &modname, - const std::string &symname) -:import_shared -{ - module(modname), symname -} -{} - -template -ircd::mods::import_shared::import_shared(module module, - const std::string &symname) -:import> -{ - module, symname -} -,std::shared_ptr -{ - import>::operator*() -}{} diff --git a/include/ircd/mods/import.h b/include/ircd/mods/import.h new file mode 100644 index 000000000..4c1e53c95 --- /dev/null +++ b/include/ircd/mods/import.h @@ -0,0 +1,40 @@ +// Matrix Construct +// +// Copyright (C) Matrix Construct Developers, Authors & Contributors +// Copyright (C) 2016-2018 Jason Volk +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice is present in all copies. The +// full license for this software is available in the LICENSE file. + +#pragma once +#define HAVE_IRCD_MODS_IMPORT_H + +namespace ircd::mods +{ + template struct import; +} + +/// Representation of a symbol in a loaded shared library +/// +template +struct ircd::mods::import +:sym_ptr +{ + template + auto operator()(args&&... a) const + { + return sym_ptr::operator()(std::forward(a)...); + } + + const T *operator->() const { return sym_ptr::operator->(); } + const T &operator*() const { return sym_ptr::operator*(); } + operator const T &() const { return sym_ptr::operator*(); } + + T *operator->() { return sym_ptr::operator->(); } + T &operator*() { return sym_ptr::operator*(); } + operator T &() { return sym_ptr::operator*(); } + + using sym_ptr::sym_ptr; +}; diff --git a/include/ircd/mods/import_shared.h b/include/ircd/mods/import_shared.h new file mode 100644 index 000000000..b499774fe --- /dev/null +++ b/include/ircd/mods/import_shared.h @@ -0,0 +1,58 @@ +// Matrix Construct +// +// Copyright (C) Matrix Construct Developers, Authors & Contributors +// Copyright (C) 2016-2018 Jason Volk +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice is present in all copies. The +// full license for this software is available in the LICENSE file. + +#pragma once +#define HAVE_IRCD_MODS_IMPORT_SHARED_H + +namespace ircd::mods +{ + template struct import_shared; +} + +/// Convenience for importing an std::shared_ptr from a loaded lib +/// +template +struct ircd::mods::import_shared +:import> +,std::shared_ptr +{ + using std::shared_ptr::get; + using std::shared_ptr::operator bool; + using std::shared_ptr::operator->; + using std::shared_ptr::operator*; + + operator const T &() const { return std::shared_ptr::operator*(); } + operator T &() { return std::shared_ptr::operator*(); } + + import_shared() = default; + import_shared(module, const std::string &symname); + import_shared(const std::string &modname, const std::string &symname); +}; + +template +ircd::mods::import_shared::import_shared(const std::string &modname, + const std::string &symname) +:import_shared +{ + module(modname), symname +} +{} + +template +ircd::mods::import_shared::import_shared(module module, + const std::string &symname) +:import> +{ + module, symname +} +,std::shared_ptr +{ + import>::operator*() +}{} diff --git a/include/ircd/mods/mods.h b/include/ircd/mods/mods.h new file mode 100644 index 000000000..6d18a95ec --- /dev/null +++ b/include/ircd/mods/mods.h @@ -0,0 +1,61 @@ +// Matrix Construct +// +// Copyright (C) Matrix Construct Developers, Authors & Contributors +// Copyright (C) 2016-2018 Jason Volk +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice is present in all copies. The +// full license for this software is available in the LICENSE file. + +#pragma once +#define HAVE_IRCD_MODS_MODS_H + +/// Modules system +namespace ircd::mods +{ + struct mod; + + IRCD_EXCEPTION(ircd::error, error) + IRCD_EXCEPTION(error, filesystem_error) + IRCD_EXCEPTION(error, invalid_export) + IRCD_EXCEPTION(error, expired_symbol) + IRCD_EXCEPTION(error, undefined_symbol) + + string_view name(const mod &); + string_view path(const mod &); +} + +#include "paths.h" +#include "symbols.h" +#include "module.h" +#include "sym_ptr.h" +#include "import.h" +#include "import_shared.h" + +// misc util +namespace ircd::mods +{ + bool loaded(const std::string &name); + bool available(const std::string &name); + bool is_module(const std::string &fullpath); + bool is_module(const std::string &fullpath, std::nothrow_t); + bool is_module(const std::string &fullpath, std::string &why); + + // returns dir/name of first dir containing 'name' (and this will be + // a loadable module). Unlike libltdl, the reason each individual + // candidate failed is presented in a vector. + std::string search(const std::string &name, std::vector &why); + std::string search(const std::string &name); + + // Potential modules available to load + std::forward_list available(); +} + +// Exports down into ircd:: +namespace ircd +{ + using mods::module; + using mods::import; + using mods::import_shared; +} diff --git a/include/ircd/mods/module.h b/include/ircd/mods/module.h new file mode 100644 index 000000000..0524f4f1f --- /dev/null +++ b/include/ircd/mods/module.h @@ -0,0 +1,76 @@ +// Matrix Construct +// +// Copyright (C) Matrix Construct Developers, Authors & Contributors +// Copyright (C) 2016-2018 Jason Volk +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice is present in all copies. The +// full license for this software is available in the LICENSE file. + +#pragma once +#define HAVE_IRCD_MODS_MODULE_H + +namespace ircd::mods +{ + struct module; +} + +struct ircd::mods::module +:std::shared_ptr +{ + const std::string &name() const; + const std::string &path() const; + const std::string &mangle(const std::string &) const; + + bool has(const std::string &name) const; + + template const T *ptr(const std::string &name) const; + template T *ptr(const std::string &name); + + template const T &get(const std::string &name) const; + template T &get(const std::string &name); + + module(std::shared_ptr ptr = {}) + :std::shared_ptr{std::move(ptr)} + {} + + module(const std::string &name); + ~module() noexcept; +}; + +namespace ircd::mods +{ + template<> const uint8_t *module::ptr(const std::string &name) const; + template<> uint8_t *module::ptr(const std::string &name); +} + +template +T & +ircd::mods::module::get(const std::string &name) +{ + return *ptr(name); +} + +template +const T & +ircd::mods::module::get(const std::string &name) +const +{ + return *ptr(name); +} + +template +T * +ircd::mods::module::ptr(const std::string &name) +{ + return reinterpret_cast(ptr(name)); +} + +template +const T * +ircd::mods::module::ptr(const std::string &name) +const +{ + return reinterpret_cast(ptr(name)); +} diff --git a/include/ircd/mods/paths.h b/include/ircd/mods/paths.h new file mode 100644 index 000000000..3624def09 --- /dev/null +++ b/include/ircd/mods/paths.h @@ -0,0 +1,33 @@ +// Matrix Construct +// +// Copyright (C) Matrix Construct Developers, Authors & Contributors +// Copyright (C) 2016-2018 Jason Volk +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice is present in all copies. The +// full license for this software is available in the LICENSE file. + +#pragma once +#define HAVE_IRCD_MODS_PATHS_H + +namespace ircd::mods +{ + struct paths extern paths; + + // Platform (.so|.dll) postfixing + std::string postfixed(const std::string &name); + std::string unpostfixed(const std::string &name); +} + +struct ircd::mods::paths +:std::vector +{ + bool added(const std::string &dir) const; + + bool del(const std::string &dir); + bool add(const std::string &dir, std::nothrow_t); + bool add(const std::string &dir); + + paths(); +}; diff --git a/include/ircd/mods/sym_ptr.h b/include/ircd/mods/sym_ptr.h new file mode 100644 index 000000000..4ee5147f5 --- /dev/null +++ b/include/ircd/mods/sym_ptr.h @@ -0,0 +1,117 @@ +// Matrix Construct +// +// Copyright (C) Matrix Construct Developers, Authors & Contributors +// Copyright (C) 2016-2018 Jason Volk +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice is present in all copies. The +// full license for this software is available in the LICENSE file. + +#pragma once +#define HAVE_IRCD_MODS_SYM_PTR_H + +namespace ircd::mods +{ + struct sym_ptr; +} + +/// Representation of a symbol in a loaded library (non-template; low level). +/// +class ircd::mods::sym_ptr +:std::weak_ptr +{ + void *ptr {nullptr}; + + public: + bool operator!() const; + operator bool() const; + + template const T *get() const; + template const T *operator->() const; + template const T &operator*() const; + template auto operator()(args&&... a) const; + + template T *get(); + template T *operator->(); + template T &operator*(); + + sym_ptr() = default; + sym_ptr(module, const std::string &symname); + sym_ptr(const std::string &modname, const std::string &symname); + ~sym_ptr() noexcept; +}; + +template +T & +ircd::mods::sym_ptr::operator*() +{ + if(unlikely(expired())) + throw expired_symbol("The reference to a symbol in another module is no longer valid"); + + return *get(); +} + +template +T * +ircd::mods::sym_ptr::operator->() +{ + return get(); +} + +template +T * +ircd::mods::sym_ptr::get() +{ + return reinterpret_cast(ptr); +} + +template +auto +ircd::mods::sym_ptr::operator()(args&&... a) +const +{ + return (*get())(std::forward(a)...); +} + +template +const T & +ircd::mods::sym_ptr::operator*() +const +{ + if(unlikely(expired())) + throw expired_symbol("The const reference to a symbol in another module is no longer valid"); + + return *get(); +} + +template +const T * +ircd::mods::sym_ptr::operator->() +const +{ + return get(); +} + +template +const T * +ircd::mods::sym_ptr::get() +const +{ + return reinterpret_cast(ptr); +} + +inline ircd::mods::sym_ptr::operator +bool() +const +{ + return !bool(*this); +} + +inline bool +ircd::mods::sym_ptr::operator!() +const +{ + return !ptr || expired(); +} diff --git a/include/ircd/mods/symbols.h b/include/ircd/mods/symbols.h new file mode 100644 index 000000000..25ab8bb6b --- /dev/null +++ b/include/ircd/mods/symbols.h @@ -0,0 +1,29 @@ +// Matrix Construct +// +// Copyright (C) Matrix Construct Developers, Authors & Contributors +// Copyright (C) 2016-2018 Jason Volk +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice is present in all copies. The +// full license for this software is available in the LICENSE file. + +#pragma once +#define HAVE_IRCD_MODS_SYMBOLS_H + +namespace ircd::mods +{ + // Section & Symbol utilites + std::vector sections(const std::string &fullpath); + + std::vector symbols(const std::string &fullpath, const std::string §ion); + std::vector symbols(const std::string &fullpath); + + std::unordered_map mangles(const std::vector &); + std::unordered_map mangles(const std::string &fullpath, const std::string §ion); + std::unordered_map mangles(const std::string &fullpath); + + // Find module names where symbol resides + bool has_symbol(const std::string &name, const std::string &symbol); + std::vector find_symbol(const std::string &symbol); +} diff --git a/include/ircd/stdinc.h b/include/ircd/stdinc.h index bdd45b882..e8b0b34c8 100644 --- a/include/ircd/stdinc.h +++ b/include/ircd/stdinc.h @@ -223,7 +223,7 @@ namespace ircd #include "ctx/ctx.h" #include "db/db.h" #include "js.h" -#include "mods.h" +#include "mods/mods.h" #include "rfc3986.h" #include "rfc1035.h" #include "net/net.h" diff --git a/ircd/mods.cc b/ircd/mods.cc index 8980f3d9e..0259bfbf0 100644 --- a/ircd/mods.cc +++ b/ircd/mods.cc @@ -40,260 +40,200 @@ ircd::mods::mod::loaded /////////////////////////////////////////////////////////////////////////////// // -// mods/mods.h +// mods/mods.h (misc util) // -ircd::string_view -ircd::mods::name(const mod &mod) +std::forward_list +ircd::mods::available() { - return mod.name(); -} + using filesystem::path; + using filesystem::directory_iterator; -ircd::string_view -ircd::mods::path(const mod &mod) -{ - return mod.location(); -} - -/////////////////////////////////////////////////////////////////////////////// -// -// (internal) mods.h -// - -// -// mod::mod -// - -ircd::mods::mod::mod(const filesystem::path &path, - const load_mode::type &mode) -try -:path{path} -,mode{mode} -//,mangles{mods::mangles(path)} -,handle{[this, &path, &mode] -{ - const auto theirs + std::forward_list ret; + for(const auto &dir : paths) try { - std::get_terminate() - }; - - const auto ours([] + for(directory_iterator it(dir); it != directory_iterator(); ++it) + if(is_module(it->path(), std::nothrow)) + ret.emplace_front(unpostfixed(relative(it->path(), dir).string())); + } + catch(const filesystem::filesystem_error &e) { - log.critical("std::terminate() called during the static construction of a module."); - - if(std::current_exception()) try - { - std::rethrow_exception(std::current_exception()); - } - catch(const std::exception &e) - { - log.error("%s", e.what()); - } - }); - - const unwind reset{[this, &theirs] - { - assert(loading.top() == this); - loading.pop(); - std::set_terminate(theirs); - }}; - - loading.push(this); - std::set_terminate(ours); - return boost::dll::shared_library{path, mode}; -}()} -,_name -{ - unpostfixed(handle.location().filename().string()) -} -,_location -{ - handle.location().string() -} -,header -{ - &handle.get(mapi::header_symbol_name) -} -{ - log.debug("Loaded static segment of '%s' @ `%s' with %zu symbols", - name(), - location(), - mangles.size()); - - if(unlikely(!header)) - throw error("Unexpected null header"); - - if(header->magic != mapi::MAGIC) - throw error("Bad magic [%04x] need: [%04x]", - header->magic, - mapi::MAGIC); - - // Tell the module where to find us. - header->self = this; - - // Set some basic metadata - auto &meta(header->meta); - meta["name"] = name(); - meta["location"] = location(); - - if(!loading.empty()) - { - const auto &m(mod::loading.top()); - m->children.emplace_back(this); - log.debug("Module '%s' recursively loaded by '%s'", - name(), - m->path.filename().string()); + log.warning("Module path [%s]: %s", dir, e.what()); + continue; } - // If init throws an exception from here the loading process will back out. - if(header->init) - header->init(); - - log.info("Loaded module %s v%u \"%s\"", - name(), - header->version, - description().size()? description() : ""s); - - // Without init exception, the module is now considered loaded. - loaded.emplace(name(), this); + return ret; } -catch(const boost::system::system_error &e) + +filesystem::path +ircd::mods::fullpath(const std::string &name) { - switch(e.code().value()) + std::vector why; + const filesystem::path path(search(name, why)); + if(path.empty()) { - case boost::system::errc::bad_file_descriptor: + for(const auto &str : why) + log.error("candidate for module '%s' failed: %s", name, str); + + throw error { - const string_view what(e.what()); - const auto pos(what.find("undefined symbol: ")); - if(pos == std::string_view::npos) - break; - - const string_view msg(what.substr(pos)); - const std::string mangled(between(msg, ": ", ")")); - const std::string demangled(demangle(mangled)); - throw error("undefined symbol: '%s' (%s)", - demangled, - mangled); - } - - default: - break; + "No valid module by name `%s'", name + }; } - throw error("%s", string(e)); + return path; } -// Allows module to communicate static destruction is taking place when mapi::header -// destructs. If dlclose() returns without this being set, dlclose() lied about really -// unloading the module. That is considered a "stuck" module. -bool ircd::mapi::static_destruction; - -ircd::mods::mod::~mod() -noexcept try +std::string +ircd::mods::search(const std::string &name) { - unload(); + std::vector why; + return search(name, why); } -catch(const std::exception &e) -{ - log::critical("Module @%p unload: %s", (const void *)this, e.what()); - if(!ircd::debugmode) - return; +std::string +ircd::mods::search(const std::string &name, + std::vector &why) +{ + using filesystem::path; + + const path path(postfixed(name)); + if(!path.is_relative()) + { + why.resize(why.size() + 1); + return is_module(path, why.back())? name : std::string{}; + } + else for(const auto &dir : paths) + { + why.resize(why.size() + 1); + if(is_module(dir/path, why.back())) + return (dir/path).string(); + } + + return {}; } bool -ircd::mods::mod::unload() +ircd::mods::is_module(const std::string &fullpath) { - if(!handle.is_loaded()) - return false; + return is_module(filesystem::path(fullpath)); +} - const auto name(this->name()); - log.debug("Attempting unload module '%s' @ `%s'", name, location()); - const size_t erased(loaded.erase(name)); - assert(erased == 1); +bool +ircd::mods::is_module(const std::string &fullpath, + std::nothrow_t) +{ + return is_module(filesystem::path(fullpath), std::nothrow); +} - if(header->fini) - header->fini(); +bool +ircd::mods::is_module(const std::string &fullpath, + std::string &why) +{ + return is_module(filesystem::path(fullpath), why); +} - // Save the children! dlclose() does not like to be called recursively during static - // destruction of a module. The mod ctor recorded all of the modules loaded while this - // module was loading so we can reverse the record and unload them here. - // Note: If the user loaded more modules from inside their module they will have to dtor them - // before 'static destruction' does. They can also do that by adding them to this vector. - std::for_each(rbegin(this->children), rend(this->children), [] - (mod *const &ptr) - { - if(shared_from(*ptr).use_count() <= 2) - ptr->unload(); - }); +bool +ircd::mods::is_module(const filesystem::path &path, + std::nothrow_t) +try +{ + return is_module(path); +} +catch(const std::exception &e) +{ + return false; +} - log.debug("Attempting static unload for '%s' @ `%s'", name, location()); - mapi::static_destruction = false; - handle.unload(); - assert(!handle.is_loaded()); - if(!mapi::static_destruction) - { - log.error("Module \"%s\" is stuck and failing to unload.", name); - log.warning("Module \"%s\" may result in undefined behavior if not fixed.", name); - } else { - log.info("Unloaded '%s'", name); - } +bool +ircd::mods::is_module(const filesystem::path &path, + std::string &why) +try +{ + return is_module(path); +} +catch(const std::exception &e) +{ + why = e.what(); + return false; +} + +bool +ircd::mods::is_module(const filesystem::path &path) +{ + const auto syms(symbols(path)); + const auto &header_name(mapi::header_symbol_name); + const auto it(std::find(begin(syms), end(syms), header_name)); + if(it == end(syms)) + throw error + { + "`%s': has no MAPI header (%s)", path.string(), header_name + }; return true; } -const std::string & -ircd::mods::mod::mangle(const std::string &name) -const +bool +ircd::mods::available(const std::string &name) { - const auto it(mangles.find(name)); - if(it == end(mangles)) - return name; + using filesystem::path; - const auto &mangled(it->second); - return mangled; -} - -template -T * -ircd::mods::mod::ptr(const std::string &name) -{ - return &handle.get(name); -} - -template -const T * -ircd::mods::mod::ptr(const std::string &name) -const -{ - return &handle.get(name); -} - -template -T & -ircd::mods::mod::get(const std::string &name) -{ - handle.get(name); -} - -template -const T & -ircd::mods::mod::get(const std::string &name) -const -{ - handle.get(name); + std::vector why; + return !search(name, why).empty(); } bool -ircd::mods::mod::has(const std::string &name) -const +ircd::mods::loaded(const std::string &name) { - return handle.has(name); + return mod::loaded.count(name); } /////////////////////////////////////////////////////////////////////////////// // -// module +// mods/sym_ptr.h +// + +ircd::mods::sym_ptr::sym_ptr(const std::string &modname, + const std::string &symname) +:sym_ptr +{ + module(modname), symname +} +{ +} + +ircd::mods::sym_ptr::sym_ptr(module module, + const std::string &symname) +:std::weak_ptr +{ + module +} +,ptr{[this, &symname] +{ + const life_guard mod{*this}; + const auto &mangled(mod->mangle(symname)); + if(unlikely(!mod->has(mangled))) + throw undefined_symbol + { + "Could not find symbol '%s' (%s) in module '%s'", + symname, + mangled, + mod->name() + }; + + return mod->ptr(mangled); +}()} +{ +} + +ircd::mods::sym_ptr::~sym_ptr() +noexcept +{ +} + +/////////////////////////////////////////////////////////////////////////////// +// +// mods/module.h // ircd::mods::module::module(const std::string &name) @@ -400,83 +340,11 @@ const return mod.mangle(name); } - /////////////////////////////////////////////////////////////////////////////// // -// sym_ptr +// mods/symbols.h // -ircd::mods::sym_ptr::sym_ptr(const std::string &modname, - const std::string &symname) -:sym_ptr -{ - module(modname), symname -} -{ -} - -ircd::mods::sym_ptr::sym_ptr(module module, - const std::string &symname) -:std::weak_ptr -{ - module -} -,ptr{[this, &symname] -{ - const life_guard mod{*this}; - const auto &mangled(mod->mangle(symname)); - if(unlikely(!mod->has(mangled))) - throw undefined_symbol - { - "Could not find symbol '%s' (%s) in module '%s'", - symname, - mangled, - mod->name() - }; - - return mod->ptr(mangled); -}()} -{ -} - -ircd::mods::sym_ptr::~sym_ptr() -noexcept -{ -} - -/////////////////////////////////////////////////////////////////////////////// -// -// misc -// - -namespace ircd { -namespace mods { - -} // namespace mods -} // namespace ircd - -bool -ircd::mods::loaded(const std::string &name) -{ - return mod::loaded.count(name); -} - -bool -ircd::mods::available(const std::string &name) -{ - using filesystem::path; - - std::vector why; - return !search(name, why).empty(); -} - -std::string -ircd::mods::search(const std::string &name) -{ - std::vector why; - return search(name, why); -} - std::vector ircd::mods::find_symbol(const std::string &symbol) { @@ -503,123 +371,6 @@ ircd::mods::has_symbol(const std::string &name, return std::find(begin(syms), end(syms), symbol) != end(syms); } -filesystem::path -ircd::mods::fullpath(const std::string &name) -{ - std::vector why; - const filesystem::path path(search(name, why)); - if(path.empty()) - { - for(const auto &str : why) - log.error("candidate for module '%s' failed: %s", name, str); - - throw error("No valid module by name `%s'", name); - } - - return path; -} - -std::string -ircd::mods::search(const std::string &name, - std::vector &why) -{ - using filesystem::path; - - const path path(postfixed(name)); - if(!path.is_relative()) - { - why.resize(why.size() + 1); - return is_module(path, why.back())? name : std::string{}; - } - else for(const auto &dir : paths) - { - why.resize(why.size() + 1); - if(is_module(dir/path, why.back())) - return (dir/path).string(); - } - - return {}; -} - -std::forward_list -ircd::mods::available() -{ - using filesystem::path; - using filesystem::directory_iterator; - - std::forward_list ret; - for(const auto &dir : paths) try - { - for(directory_iterator it(dir); it != directory_iterator(); ++it) - if(is_module(it->path(), std::nothrow)) - ret.emplace_front(unpostfixed(relative(it->path(), dir).string())); - } - catch(const filesystem::filesystem_error &e) - { - log.warning("Module path [%s]: %s", dir, e.what()); - continue; - } - - return ret; -} - -bool -ircd::mods::is_module(const std::string &fullpath) -{ - return is_module(filesystem::path(fullpath)); -} - -bool -ircd::mods::is_module(const std::string &fullpath, - std::nothrow_t) -{ - return is_module(filesystem::path(fullpath), std::nothrow); -} - -bool -ircd::mods::is_module(const std::string &fullpath, - std::string &why) -{ - return is_module(filesystem::path(fullpath), why); -} - -bool -ircd::mods::is_module(const filesystem::path &path, - std::nothrow_t) -try -{ - return is_module(path); -} -catch(const std::exception &e) -{ - return false; -} - -bool -ircd::mods::is_module(const filesystem::path &path, - std::string &why) -try -{ - return is_module(path); -} -catch(const std::exception &e) -{ - why = e.what(); - return false; -} - -bool -ircd::mods::is_module(const filesystem::path &path) -{ - const auto syms(symbols(path)); - const auto &header_name(mapi::header_symbol_name); - const auto it(std::find(begin(syms), end(syms), header_name)); - if(it == end(syms)) - throw error("`%s': has no MAPI header (%s)", path.string(), header_name); - - return true; -} - std::unordered_map ircd::mods::mangles(const std::string &fullpath) { @@ -730,73 +481,23 @@ ircd::mods::info(const filesystem::path &path, /////////////////////////////////////////////////////////////////////////////// // -// paths +// mods/paths.h // namespace ircd::mods { - const filesystem::path modroot - { - ircd::fs::get(ircd::fs::MODULES) - }; - - struct paths paths; + extern const filesystem::path modroot; } -ircd::mods::paths::paths() -:std::vector -{{ - modroot.string() -}} +decltype(ircd::mods::modroot) +ircd::mods::modroot { -} + ircd::fs::get(ircd::fs::MODULES) +}; -bool -ircd::mods::paths::add(const std::string &dir) -{ - using filesystem::path; - - const path path(prefix_if_relative(dir)); - - if(!exists(path)) - throw filesystem_error("path `%s' (%s) does not exist", dir, path.string()); - - if(!is_directory(path)) - throw filesystem_error("path `%s' (%s) is not a directory", dir, path.string()); - - if(added(dir)) - return false; - - emplace(begin(), dir); - return true; -} - -bool -ircd::mods::paths::add(const std::string &dir, - std::nothrow_t) -try -{ - return add(dir); -} -catch(const std::exception &e) -{ - log.error("Failed to add path: %s", e.what()); - return false; -} - -bool -ircd::mods::paths::del(const std::string &dir) -{ - std::remove(begin(), end(), prefix_if_relative(dir).string()); - return true; -} - -bool -ircd::mods::paths::added(const std::string &dir) -const -{ - return std::find(begin(), end(), dir) != end(); -} +decltype(ircd::mods::paths) +ircd::mods::paths +{}; std::string ircd::mods::unpostfixed(const std::string &name) @@ -834,3 +535,325 @@ ircd::mods::prefix_if_relative(const filesystem::path &path) { return path.is_relative()? (modroot / path) : path; } + +// +// paths::paths +// + +ircd::mods::paths::paths() +:std::vector +{{ + modroot.string() +}} +{ +} + +bool +ircd::mods::paths::add(const std::string &dir) +{ + using filesystem::path; + + const path path(prefix_if_relative(dir)); + + if(!exists(path)) + throw filesystem_error + { + "path `%s' (%s) does not exist", dir, path.string() + }; + + if(!is_directory(path)) + throw filesystem_error + { + "path `%s' (%s) is not a directory", dir, path.string() + }; + + if(added(dir)) + return false; + + emplace(begin(), dir); + return true; +} + +bool +ircd::mods::paths::add(const std::string &dir, + std::nothrow_t) +try +{ + return add(dir); +} +catch(const std::exception &e) +{ + log.error("Failed to add path: %s", e.what()); + return false; +} + +bool +ircd::mods::paths::del(const std::string &dir) +{ + std::remove(begin(), end(), prefix_if_relative(dir).string()); + return true; +} + +bool +ircd::mods::paths::added(const std::string &dir) +const +{ + return std::find(begin(), end(), dir) != end(); +} + +/////////////////////////////////////////////////////////////////////////////// +// +// mods/mods.h +// + +ircd::string_view +ircd::mods::name(const mod &mod) +{ + return mod.name(); +} + +ircd::string_view +ircd::mods::path(const mod &mod) +{ + return mod.location(); +} + +/////////////////////////////////////////////////////////////////////////////// +// +// (internal) mods.h +// + +ircd::mods::mod::mod(const filesystem::path &path, + const load_mode::type &mode) +try +:path{path} +,mode{mode} +//,mangles{mods::mangles(path)} +,handle{[this, &path, &mode] +{ + const auto theirs + { + std::get_terminate() + }; + + const auto ours([] + { + log.critical("std::terminate() called during the static construction of a module."); + + if(std::current_exception()) try + { + std::rethrow_exception(std::current_exception()); + } + catch(const std::exception &e) + { + log.error("%s", e.what()); + } + }); + + const unwind reset{[this, &theirs] + { + assert(loading.top() == this); + loading.pop(); + std::set_terminate(theirs); + }}; + + loading.push(this); + std::set_terminate(ours); + return boost::dll::shared_library{path, mode}; +}()} +,_name +{ + unpostfixed(handle.location().filename().string()) +} +,_location +{ + handle.location().string() +} +,header +{ + &handle.get(mapi::header_symbol_name) +} +{ + log.debug("Loaded static segment of '%s' @ `%s' with %zu symbols", + name(), + location(), + mangles.size()); + + if(unlikely(!header)) + throw error + { + "Unexpected null header" + }; + + if(header->magic != mapi::MAGIC) + throw error + { + "Bad magic [%04x] need: [%04x]", header->magic, mapi::MAGIC + }; + + // Tell the module where to find us. + header->self = this; + + // Set some basic metadata + auto &meta(header->meta); + meta["name"] = name(); + meta["location"] = location(); + + if(!loading.empty()) + { + const auto &m(mod::loading.top()); + m->children.emplace_back(this); + log.debug("Module '%s' recursively loaded by '%s'", + name(), + m->path.filename().string()); + } + + // If init throws an exception from here the loading process will back out. + if(header->init) + header->init(); + + log.info("Loaded module %s v%u \"%s\"", + name(), + header->version, + description().size()? description() : ""s); + + // Without init exception, the module is now considered loaded. + loaded.emplace(name(), this); +} +catch(const boost::system::system_error &e) +{ + switch(e.code().value()) + { + case boost::system::errc::bad_file_descriptor: + { + const string_view what(e.what()); + const auto pos(what.find("undefined symbol: ")); + if(pos == std::string_view::npos) + break; + + const string_view msg(what.substr(pos)); + const std::string mangled(between(msg, ": ", ")")); + const std::string demangled(demangle(mangled)); + throw error + { + "undefined symbol: '%s' (%s)", demangled, mangled + }; + } + + default: + break; + } + + throw error + { + "%s", string(e) + }; +} + +// Allows module to communicate static destruction is taking place when mapi::header +// destructs. If dlclose() returns without this being set, dlclose() lied about really +// unloading the module. That is considered a "stuck" module. +bool ircd::mapi::static_destruction; + +ircd::mods::mod::~mod() +noexcept try +{ + unload(); +} +catch(const std::exception &e) +{ + log::critical("Module @%p unload: %s", (const void *)this, e.what()); + + if(!ircd::debugmode) + return; +} + +bool +ircd::mods::mod::unload() +{ + if(!handle.is_loaded()) + return false; + + const auto name(this->name()); + log.debug("Attempting unload module '%s' @ `%s'", name, location()); + const size_t erased(loaded.erase(name)); + assert(erased == 1); + + if(header->fini) + header->fini(); + + // Save the children! dlclose() does not like to be called recursively during static + // destruction of a module. The mod ctor recorded all of the modules loaded while this + // module was loading so we can reverse the record and unload them here. + // Note: If the user loaded more modules from inside their module they will have to dtor them + // before 'static destruction' does. They can also do that by adding them to this vector. + std::for_each(rbegin(this->children), rend(this->children), [] + (mod *const &ptr) + { + if(shared_from(*ptr).use_count() <= 2) + ptr->unload(); + }); + + log.debug("Attempting static unload for '%s' @ `%s'", name, location()); + mapi::static_destruction = false; + handle.unload(); + assert(!handle.is_loaded()); + if(!mapi::static_destruction) + { + log.error("Module \"%s\" is stuck and failing to unload.", name); + log.warning("Module \"%s\" may result in undefined behavior if not fixed.", name); + } else { + log.info("Unloaded '%s'", name); + } + + return true; +} + +const std::string & +ircd::mods::mod::mangle(const std::string &name) +const +{ + const auto it(mangles.find(name)); + if(it == end(mangles)) + return name; + + const auto &mangled(it->second); + return mangled; +} + +template +T * +ircd::mods::mod::ptr(const std::string &name) +{ + return &handle.get(name); +} + +template +const T * +ircd::mods::mod::ptr(const std::string &name) +const +{ + return &handle.get(name); +} + +template +T & +ircd::mods::mod::get(const std::string &name) +{ + handle.get(name); +} + +template +const T & +ircd::mods::mod::get(const std::string &name) +const +{ + handle.get(name); +} + +bool +ircd::mods::mod::has(const std::string &name) +const +{ + return handle.has(name); +} diff --git a/ircd/mods.h b/ircd/mods.h index 6523e7935..ef98a0d0c 100644 --- a/ircd/mods.h +++ b/ircd/mods.h @@ -18,8 +18,6 @@ namespace ircd::mods filesystem::path prefix_if_relative(const filesystem::path &path); filesystem::path postfixed(const filesystem::path &path); filesystem::path unpostfixed(const filesystem::path &path); - std::string postfixed(const std::string &name); - std::string unpostfixed(const std::string &name); template R info(const filesystem::path &, F&& closure); std::vector sections(const filesystem::path &path); @@ -36,9 +34,6 @@ namespace ircd::mods bool is_module(const filesystem::path &); bool is_module(const filesystem::path &, std::string &why); bool is_module(const filesystem::path &, std::nothrow_t); - bool is_module(const std::string &fullpath, std::string &why); - bool is_module(const std::string &fullpath, std::nothrow_t); - bool is_module(const std::string &fullpath); } /// Internal module representation