Mercurial > irccd
changeset 136:01df93b56dde
Irccd: implement native plugins, #502
- Add brand new DynlibPlugin to load shared libraries as plugins,
- Add a template as documentation.
author | David Demelier <markand@malikania.fr> |
---|---|
date | Wed, 18 May 2016 22:31:24 +0200 |
parents | 6be066ad2329 |
children | bc291b131f6a |
files | doc/examples/template-plugin.cpp lib/CMakeLists.txt lib/irccd/CMakeSources.cmake lib/irccd/dynlib.hpp lib/irccd/plugin-dynlib.cpp lib/irccd/plugin-dynlib.hpp lib/irccd/service-plugin.cpp lib/irccd/service-plugin.hpp |
diffstat | 8 files changed, 1080 insertions(+), 46 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/examples/template-plugin.cpp Wed May 18 22:31:24 2016 +0200 @@ -0,0 +1,229 @@ +/* + * This example file show how to fill irccd callbacks for a native plugin. + * + * All of the defined callbacks are optional and may be removed. + */ + +#include <iostream> + +#include <irccd/plugin-dynlib.hpp> // (in irccd_onReload, irccd_onLoad and irccd_onUnload) +#include <irccd/server.hpp> +#include <irccd/util.hpp> // for util::join (in irccd_onNames) + +using namespace irccd; + +extern "C" { + +/* --- onCommand ---------------------------------------------------- */ + +void irccd_onCommand(Irccd &, + const std::shared_ptr<Server> &server, + const std::string &origin, + const std::string &channel, + const std::string &message) +{ + std::cout << "onCommand: server=" << server->name() + << ", origin=" << origin + << ", channel=" << channel + << ", message=" << message << std::endl; +} + +/* --- onConnect ---------------------------------------------------- */ + +void irccd_onConnect(Irccd &, const std::shared_ptr<Server> &server) +{ + std::cout << "onConnect: server=" << server->name() << std::endl; +} + +/* --- onChannelMode ------------------------------------------------ */ + +void irccd_onChannelMode(Irccd &, + const std::shared_ptr<Server> &server, + const std::string &origin, + const std::string &channel, + const std::string &mode, + const std::string &arg) +{ + std::cout << "onChannelMode: server=" << server->name() + << ", origin=" << origin + << ", channel=" << channel + << ", mode=" << mode + << ", arg=" << arg << std::endl; +} + +/* --- onChannelNotice ---------------------------------------------- */ + +void irccd_onChannelNotice(Irccd &irccd, + const std::shared_ptr<Server> &server, + const std::string &origin, + const std::string &channel, + const std::string ¬ice) +{ + std::cout << "onChannelNotice: server=" << server->name() + << ", origin=" << origin + << ", channel=" << channel + << ", notice=" << notice << std::endl; +} + +/* --- onInvite ----------------------------------------------------- */ + +void irccd_onInvite(Irccd &irccd, const std::shared_ptr<Server> &server, const std::string &origin, const std::string &channel) +{ + std::cout << "onInvite: server=" << server->name() << ", origin=" << origin << ", channel=" << channel << std::endl; +} + +/* --- onJoin ------------------------------------------------------- */ + +void irccd_onJoin(Irccd &irccd, const std::shared_ptr<Server> &server, const std::string &origin, const std::string &channel) +{ + std::cout << "onJoin: server=" << server->name() << ", origin=" << origin << ", channel=" << channel << std::endl; +} + +/* --- onKick ------------------------------------------------------- */ + +void irccd_onKick(Irccd &irccd, + const std::shared_ptr<Server> &server, + const std::string &origin, + const std::string &channel, + const std::string &target, + const std::string &reason) +{ + std::cout << "onKick: server=" << server->name() + << ", origin=" << origin + << ", channel=" << channel + << ", target=" << channel + << ", reason=" << reason << std::endl; +} + +/* --- onLoad ------------------------------------------------------- */ + +void irccd_onLoad(Irccd &, DynlibPlugin &plugin) +{ + std::cout << "onLoad: plugin=" << plugin.name() << std::endl; +} + +/* --- onMessage ---------------------------------------------------- */ + +void irccd_onMessage(Irccd &irccd, + const std::shared_ptr<Server> &server, + const std::string &origin, + const std::string &channel, + const std::string &message) +{ + std::cout << "onMessage: server=" << server->name() + << ", origin=" << origin + << ", channel=" << channel + << ", message=" << message << std::endl; +} + +/* --- onMe --------------------------------------------------------- */ + +void irccd_onMe(Irccd &irccd, + const std::shared_ptr<Server> &server, + const std::string &origin, + const std::string &channel, + const std::string &message) +{ + std::cout << "onMe: server=" << server->name() + << ", origin=" << origin + << ", channel=" << channel + << ", message=" << message << std::endl; +} + +/* --- onMode ------------------------------------------------------- */ + +void irccd_onMode(Irccd &irccd, const std::shared_ptr<Server> &server, const std::string &origin, const std::string &mode) +{ + std::cout << "onMode: server=" << server->name() << ", origin=" << origin << ", mode=" << mode << std::endl; +} + +/* --- onNames ------------------------------------------------------ */ + +void irccd_onNames(Irccd &irccd, + const std::shared_ptr<Server> &server, + const std::string &channel, + const std::vector<std::string> &list) +{ + std::cout << "onNames: server=" << server->name() + << ", channel=" << channel + << ", list=" << util::join(list.begin(), list.end(), ", ") << std::endl; +} + +/* --- onNick ------------------------------------------------------- */ + +void irccd_onNick(Irccd &irccd, const std::shared_ptr<Server> &server, const std::string &origin, const std::string &nick) +{ + std::cout << "onNick: server=" << server->name() << ", origin=" << origin << ", nick=" << nick << std::endl; +} + +/* --- onNotice ----------------------------------------------------- */ + +void irccd_onNotice(Irccd &irccd, const std::shared_ptr<Server> &server, const std::string &origin, const std::string ¬ice) +{ + std::cout << "onNotice: server=" << server->name() << ", origin=" << origin << ", notice=" << notice << std::endl; +} + +/* --- onPart ------------------------------------------------------- */ + +void irccd_onPart(Irccd &irccd, + const std::shared_ptr<Server> &server, + const std::string &origin, + const std::string &channel, + const std::string &reason) +{ + std::cout << "onPart: server=" << server->name() + << ", origin=" << origin + << ", channel=" << channel + << ", reason=" << reason << std::endl; +} + +/* --- onQuery ------------------------------------------------------ */ + +void irccd_onQuery(Irccd &, const std::shared_ptr<Server> &server, const std::string &origin, const std::string &message) +{ + std::cout << "onQuery: server=" << server->name() << ", origin=" << origin << ", message=" << message << std::endl; +} + +/* --- onQueryCommand ----------------------------------------------- */ + +void irccd_onQueryCommand(Irccd &, const std::shared_ptr<Server> &server, const std::string &origin, const std::string &message) +{ + std::cout << "onQueryCommand: server=" << server->name() << ", origin=" << origin << ", message=" << message << std::endl; +} + +/* --- onReload ----------------------------------------------------- */ + +void irccd_onReload(Irccd &irccd, DynlibPlugin &plugin) +{ + std::cout << "onReload: plugin=" << plugin.name() << std::endl; +} + +/* --- onTopic ------------------------------------------------------ */ + +void irccd_onTopic(Irccd &irccd, + const std::shared_ptr<Server> &server, + const std::string &origin, + const std::string &channel, + const std::string &topic) +{ + std::cout << "onTopic: server=" << server->name() + << ", origin=" << origin + << ", channel=" << channel + << ", topic=" << topic << std::endl; +} + +/* --- onUnload ----------------------------------------------------- */ + +void irccd_onUnload(Irccd &irccd, DynlibPlugin &plugin) +{ + std::cout << "onUnload: plugin=" << plugin.name() << std::endl; +} + +/* --- onWhois ------------------------------------------------------ */ + +void irccd_onWhois(Irccd &irccd, const std::shared_ptr<Server> &server, const ServerWhois &info) +{ + std::cout << "onWhois: server=" << server->name() << ", info-for=" << info.nick << std::endl; +} + +} // !C
--- a/lib/CMakeLists.txt Wed May 18 13:24:50 2016 +0200 +++ b/lib/CMakeLists.txt Wed May 18 22:31:24 2016 +0200 @@ -38,6 +38,8 @@ list(APPEND LIBRARIES ws2_32 shlwapi) elseif (IRCCD_SYSTEM_MAC) list(APPEND LIBRARIES resolv) +elseif (IRCCD_SYSTEM_LINUX) + list(APPEND LIBRARIES dl) endif () target_link_libraries(libirccd extern-duktape extern-ircclient extern-jansson cppformat ${LIBRARIES})
--- a/lib/irccd/CMakeSources.cmake Wed May 18 13:24:50 2016 +0200 +++ b/lib/irccd/CMakeSources.cmake Wed May 18 22:31:24 2016 +0200 @@ -52,6 +52,7 @@ ${CMAKE_CURRENT_LIST_DIR}/options.hpp ${CMAKE_CURRENT_LIST_DIR}/path.hpp ${CMAKE_CURRENT_LIST_DIR}/plugin.hpp + ${CMAKE_CURRENT_LIST_DIR}/plugin-dynlib.hpp ${CMAKE_CURRENT_LIST_DIR}/plugin-js.hpp ${CMAKE_CURRENT_LIST_DIR}/rule.hpp ${CMAKE_CURRENT_LIST_DIR}/server.hpp @@ -128,6 +129,7 @@ ${CMAKE_CURRENT_LIST_DIR}/logger.cpp ${CMAKE_CURRENT_LIST_DIR}/options.cpp ${CMAKE_CURRENT_LIST_DIR}/path.cpp + ${CMAKE_CURRENT_LIST_DIR}/plugin-dynlib.cpp ${CMAKE_CURRENT_LIST_DIR}/plugin-js.cpp ${CMAKE_CURRENT_LIST_DIR}/rule.cpp ${CMAKE_CURRENT_LIST_DIR}/server.cpp
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/irccd/dynlib.hpp Wed May 18 22:31:24 2016 +0200 @@ -0,0 +1,338 @@ +/* + * dynlib.hpp -- portable shared library loader + * + * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> + * + * 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 appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef IRCCD_DYNLIB_HPP +#define IRCCD_DYNLIB_HPP + +/** + * \file dynlib.hpp + * \brief Portable shared library loader. + * \author David Demelier <markand@malikania.fr> + */ + +/** + * \page Dynlib Dynlib + * \brief Portable shared library loader. + * + * The dynlib module let you open shared libraries dynamically at runtime. + * + * ## Operating system support + * + * | System | Support | Remarks | + * |---------|---------|--------------------| + * | Apple | Ok | | + * | FreeBSD | Ok | | + * | Linux | Ok | Needs -ldl library | + * | Windows | Ok | | + * + * ## How to export symbols + * + * When you want to dynamically load symbols from your shared library, make sure they are in a `extern "C"` block, if + * not they will be [mangled][name-mangling]. + * + * Note, this does not mean that you can't write C++ code, it just mean that you can't use namespaces and function + * overloading. + * + * Example of **plugin.cpp**: + * + * ````cpp + * #include <iostream> + * + * #include "dynlib.hpp" + * + * extern "C" { + * + * DYNLIB_EXPORT void plugin_load() + * { + * std::cout << "Loading plugin" << std::endl; + * } + * + * DYNLIB_EXPORT void plugin_unload() + * { + * std::cout << "Unloading plugin" << std::endl; + * } + * + * } + * ```` + * + * The \ref DYNLIB_EXPORT macro is necessary on some platforms to be sure that symbol will be visible. Make sure you always + * add it before any function. + * + * To compile, see your compiler documentation or build system. For gcc you can use the following: + * + * ```` + * gcc -std=c++14 -shared plugin.cpp -o plugin.so + * ```` + * + * ## How to load the library + * + * The dynlib module will search for the library in various places, thus you can use relative paths names but be sure + * that the library can be found. Otherwise, just use an absolute path to the file. + * + * ````cpp + * #include <iostream> + * + * #include "dynlib.hpp" + * + * int main() + * { + * try { + * Dynlib dso("./plugin" DYNLIB_SUFFIX); + * } catch (const std::exception &ex) { + * std::cerr << ex.what() << std::endl; + * } + * + * return 0; + * } + * ```` + * + * ## How to load symbol + * + * The last part is symbol loading, you muse use raw C function pointer and the Dynlib::sym function. + * + * ````cpp + * #include <iostream> + * + * #include "dynlib.hpp" + * + * using PluginLoad = void (*)(); + * using PluginUnload = void (*)(); + * + * int main() + * { + * try { + * Dynlib dso("./plugin" DYNLIB_SUFFIX); + * + * dso.sym<PluginLoad>("plugin_load")(); + * dso.sym<PluginUnload>("plugin_unload")(); + * } catch (const std::exception &ex) { + * std::cerr << ex.what() << std::endl; + * } + * + * return 0; + * } + * ```` + * + * [name-mangling]: https://en.wikipedia.org/wiki/Name_mangling + */ + +#include <stdexcept> +#include <string> + +#if defined(_WIN32) +# include <Windows.h> +#else +# include <dlfcn.h> +#endif + +/** + * \brief Export the symbol. + * + * This is required on some platforms and you should put it before your function signature. + * + * \code{.cpp} + * extern "C" { + * + * DYNLIB_EXPORT void my_function() + * { + * } + * + * } + * \endcode + */ +#if defined(_WIN32) +# define DYNLIB_EXPORT __declspec(dllexport) +#else +# define DYNLIB_EXPORT +#endif + +/** + * \brief Usual suffix for the library. + * + * This macro expands to the suffix convention for this platform. + * + * \code{.cpp} + * Dynlib library("./myplugin" DYNLIB_SUFFIX); + * \endcode + * + * \note Don't use the suffix expanded value shown in Doxygen as it may be wrong. + */ +#if defined(_WIN32) +# define DYNLIB_SUFFIX ".dll" +#elif defined(__APPLE__) +# define DYNLIB_SUFFIX ".dylib" +#else +# define DYNLIB_SUFFIX ".so" +#endif + +namespace irccd { + +/** + * \class Dynlib + * \brief Load a dynamic module. + * + * This class is a portable wrapper to load shared libraries on supported systems. + */ +class Dynlib { +private: +#if defined(_WIN32) + using Handle = HMODULE; + using Sym = FARPROC; +#else + using Handle = void *; + using Sym = void *; +#endif + +public: + /** + * \brief Policy for symbol resolution. + */ + enum Policy { + Immediately, //!< load symbols immediately + Lazy //!< load symbols when needed + }; + +private: + Handle m_handle; + + Dynlib(const Dynlib &) = delete; + Dynlib &operator=(const Dynlib &) = delete; + + Dynlib(Dynlib &&) = delete; + Dynlib &operator=(Dynlib &&) = delete; + +#if defined(_WIN32) + std::string error() + { + LPSTR error = nullptr; + std::string errmsg; + + FormatMessageA( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + NULL, + GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPSTR)&error, 0, NULL); + + if (error) { + errmsg = std::string(error); + LocalFree(error); + } + + return errmsg; + } +#endif + +public: + /** + * Constructor to load a shared module. + * + * \param path the absolute path + * \param policy the policy to load + * \throw std::runtime_error on error + */ + inline Dynlib(const std::string &path, Policy policy = Immediately); + + /** + * Close the library automatically. + */ + inline ~Dynlib(); + + /** + * Get a symbol from the library. + * + * On some platforms the symbol must be manually exported. + * + * \param name the symbol + * \return the symbol + * \throw std::runtime_error on error + * \see DYNLIB_EXPORT + */ + template <typename T> + inline T sym(const std::string &name); +}; + +#if defined(_WIN32) + +/* + * Windows implementation + * ------------------------------------------------------------------ + */ + +Dynlib::Dynlib(const std::string &path, Policy) +{ + m_handle = LoadLibraryA(path.c_str()); + + if (m_handle == nullptr) + throw std::runtime_error(error()); +} + +Dynlib::~Dynlib() +{ + FreeLibrary(m_handle); + m_handle = nullptr; +} + +template <typename T> +T Dynlib::sym(const std::string &name) +{ + Sym sym = GetProcAddress(m_handle, name.c_str()); + + if (sym == nullptr) + throw std::runtime_error(error()); + + return reinterpret_cast<T>(sym); +} + +#else + +/* + * Unix implementation + * ------------------------------------------------------------------ + */ + +Dynlib::Dynlib(const std::string &path, Policy policy) +{ + m_handle = dlopen(path.c_str(), policy == Immediately ? RTLD_NOW : RTLD_LAZY); + + if (m_handle == nullptr) + throw std::runtime_error(dlerror()); +} + +Dynlib::~Dynlib() +{ + dlclose(m_handle); + m_handle = nullptr; +} + +template <typename T> +T Dynlib::sym(const std::string &name) +{ + Sym sym = dlsym(m_handle, name.c_str()); + + if (sym == nullptr) + throw std::runtime_error(dlerror()); + + return reinterpret_cast<T>(sym); +} + +#endif + +#endif // !IRCCD_DYNLIB_HPP + +} // !irccd
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/irccd/plugin-dynlib.cpp Wed May 18 22:31:24 2016 +0200 @@ -0,0 +1,216 @@ +/* + * plugin-dynlib.cpp -- native plugin implementation + * + * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> + * + * 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 appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "plugin-dynlib.hpp" + +namespace irccd { + +namespace { + +template <typename Sym> +inline Sym sym(Dynlib &dynlib, const std::string &name) +{ + try { + return dynlib.sym<Sym>(name); + } catch (...) { + return nullptr; + } +} + +template <typename Sym, typename... Args> +inline void call(Sym sym, Args&&... args) +{ + if (sym) + sym(std::forward<Args>(args)...); +} + +} // !namespace + +DynlibPlugin::DynlibPlugin(std::string name, std::string path, PluginConfig config) + : Plugin(name, path, std::move(config)) + , m_dso(std::move(path)) + , m_onCommand(sym<OnCommand>(m_dso, "irccd_onCommand")) + , m_onConnect(sym<OnConnect>(m_dso, "irccd_onConnect")) + , m_onChannelMode(sym<OnChannelMode>(m_dso, "irccd_onChannelMode")) + , m_onChannelNotice(sym<OnChannelNotice>(m_dso, "irccd_onChannelNotice")) + , m_onInvite(sym<OnInvite>(m_dso, "irccd_onInvite")) + , m_onJoin(sym<OnJoin>(m_dso, "irccd_onJoin")) + , m_onKick(sym<OnKick>(m_dso, "irccd_onKick")) + , m_onLoad(sym<OnLoad>(m_dso, "irccd_onLoad")) + , m_onMessage(sym<OnMessage>(m_dso, "irccd_onMessage")) + , m_onMe(sym<OnMe>(m_dso, "irccd_onMe")) + , m_onMode(sym<OnMode>(m_dso, "irccd_onMode")) + , m_onNames(sym<OnNames>(m_dso, "irccd_onNames")) + , m_onNick(sym<OnNick>(m_dso, "irccd_onNick")) + , m_onNotice(sym<OnNotice>(m_dso, "irccd_onNotice")) + , m_onPart(sym<OnPart>(m_dso, "irccd_onPart")) + , m_onQuery(sym<OnQuery>(m_dso, "irccd_onQuery")) + , m_onQueryCommand(sym<OnQueryCommand>(m_dso, "irccd_onQueryCommand")) + , m_onReload(sym<OnReload>(m_dso, "irccd_onReload")) + , m_onTopic(sym<OnTopic>(m_dso, "irccd_onTopic")) + , m_onUnload(sym<OnUnload>(m_dso, "irccd_onUnload")) + , m_onWhois(sym<OnWhois>(m_dso, "irccd_onWhois")) +{ +} + +void DynlibPlugin::onCommand(Irccd &irccd, + const std::shared_ptr<Server> &server, + const std::string &origin, + const std::string &channel, + const std::string &message) +{ + call(m_onCommand, irccd, server, origin, channel, message); +} + +void DynlibPlugin::onConnect(Irccd &irccd, const std::shared_ptr<Server> &server) +{ + call(m_onConnect, irccd, server); +} + +void DynlibPlugin::onChannelMode(Irccd &irccd, + const std::shared_ptr<Server> &server, + const std::string &origin, + const std::string &channel, + const std::string &mode, + const std::string &arg) +{ + call(m_onChannelMode, irccd, server, origin, channel, mode, arg); +} + +void DynlibPlugin::onChannelNotice(Irccd &irccd, + const std::shared_ptr<Server> &server, + const std::string &origin, + const std::string &channel, + const std::string ¬ice) +{ + call(m_onChannelNotice, irccd, server, origin, channel, notice); +} + +void DynlibPlugin::onInvite(Irccd &irccd, const std::shared_ptr<Server> &server, const std::string &origin, const std::string &channel) +{ + call(m_onInvite, irccd, server, origin, channel); +} + +void DynlibPlugin::onJoin(Irccd &irccd, const std::shared_ptr<Server> &server, const std::string &origin, const std::string &channel) +{ + call(m_onJoin, irccd, server, origin, channel); +} + +void DynlibPlugin::onKick(Irccd &irccd, + const std::shared_ptr<Server> &server, + const std::string &origin, + const std::string &channel, + const std::string &target, + const std::string &reason) +{ + call(m_onKick, irccd, server, origin, channel, target, reason); +} + +void DynlibPlugin::onLoad(Irccd &irccd) +{ + call(m_onLoad, irccd, *this); +} + +void DynlibPlugin::onMessage(Irccd &irccd, + const std::shared_ptr<Server> &server, + const std::string &origin, + const std::string &channel, + const std::string &message) +{ + call(m_onMessage, irccd, server, origin, channel, message); +} + +void DynlibPlugin::onMe(Irccd &irccd, + const std::shared_ptr<Server> &server, + const std::string &origin, + const std::string &channel, + const std::string &message) +{ + call(m_onMe, irccd, server, origin, channel, message); +} + +void DynlibPlugin::onMode(Irccd &irccd, const std::shared_ptr<Server> &server, const std::string &origin, const std::string &mode) +{ + call(m_onMode, irccd, server, origin, mode); +} + +void DynlibPlugin::onNames(Irccd &irccd, + const std::shared_ptr<Server> &server, + const std::string &channel, + const std::vector<std::string> &list) +{ + call(m_onNames, irccd, server, channel, list); +} + +void DynlibPlugin::onNick(Irccd &irccd, const std::shared_ptr<Server> &server, const std::string &origin, const std::string &nick) +{ + call(m_onNick, irccd, server, origin, nick); +} + +void DynlibPlugin::onNotice(Irccd &irccd, const std::shared_ptr<Server> &server, const std::string &origin, const std::string ¬ice) +{ + call(m_onNotice, irccd, server, origin, notice); +} + +void DynlibPlugin::onPart(Irccd &irccd, + const std::shared_ptr<Server> &server, + const std::string &origin, + const std::string &channel, + const std::string &reason) +{ + call(m_onPart, irccd, server, origin, channel, reason); +} + +void DynlibPlugin::onQuery(Irccd &irccd, const std::shared_ptr<Server> &server, const std::string &origin, const std::string &message) +{ + call(m_onQuery, irccd, server, origin, message); +} + +void DynlibPlugin::onQueryCommand(Irccd &irccd, + const std::shared_ptr<Server> &server, + const std::string &origin, + const std::string &message) +{ + call(m_onQueryCommand, irccd, server, origin, message); +} + +void DynlibPlugin::onReload(Irccd &irccd) +{ + call(m_onReload, irccd, *this); +} + +void DynlibPlugin::onTopic(Irccd &irccd, + const std::shared_ptr<Server> &server, + const std::string &origin, + const std::string &channel, + const std::string &topic) +{ + call(m_onTopic, irccd, server, origin, channel, topic); +} + +void DynlibPlugin::onUnload(Irccd &irccd) +{ + call(m_onUnload, irccd, *this); +} + +void DynlibPlugin::onWhois(Irccd &irccd, const std::shared_ptr<Server> &server, const ServerWhois &info) +{ + call(m_onWhois, irccd, server, info); +} + +} // !irccd
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/irccd/plugin-dynlib.hpp Wed May 18 22:31:24 2016 +0200 @@ -0,0 +1,241 @@ +/* + * plugin-dynlib.hpp -- native plugin implementation + * + * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> + * + * 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 appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef IRCCD_PLUGIN_DYNLIB_HPP +#define IRCCD_PLUGIN_DYNLIB_HPP + +/** + * \file plugin-dynlib.hpp + * \brief Native plugin implementation. + */ + +#include "dynlib.hpp" +#include "plugin.hpp" + +namespace irccd { + +/** + * \brief Dynlib based plugin. + */ +class DynlibPlugin : public Plugin { +private: + using OnCommand = void (*)(Irccd &, const std::shared_ptr<Server> &, const std::string &, const std::string &, const std::string &); + using OnConnect = void (*)(Irccd &, const std::shared_ptr<Server> &); + using OnChannelMode = void (*)(Irccd &, const std::shared_ptr<Server> &, const std::string &, const std::string &, const std::string &, const std::string &); + using OnChannelNotice = void (*)(Irccd &, const std::shared_ptr<Server> &, const std::string &, const std::string &, const std::string &); + using OnInvite = void (*)(Irccd &, const std::shared_ptr<Server> &, const std::string &, const std::string &); + using OnJoin = void (*)(Irccd &, const std::shared_ptr<Server> &, const std::string &, const std::string &); + using OnKick = void (*)(Irccd &, const std::shared_ptr<Server> &, const std::string &, const std::string &, const std::string &, const std::string &); + using OnLoad = void (*)(Irccd &, DynlibPlugin &); + using OnMessage = void (*)(Irccd &, const std::shared_ptr<Server> &, const std::string &, const std::string &, const std::string &); + using OnMe = void (*)(Irccd &, const std::shared_ptr<Server> &, const std::string &, const std::string &, const std::string &); + using OnMode = void (*)(Irccd &, const std::shared_ptr<Server> &, const std::string &, const std::string &); + using OnNames = void (*)(Irccd &, const std::shared_ptr<Server> &, const std::string &, const std::vector<std::string> &); + using OnNick = void (*)(Irccd &, const std::shared_ptr<Server> &, const std::string &, const std::string &); + using OnNotice = void (*)(Irccd &, const std::shared_ptr<Server> &, const std::string &, const std::string &); + using OnPart = void (*)(Irccd &, const std::shared_ptr<Server> &, const std::string &, const std::string &, const std::string &); + using OnQuery = void (*)(Irccd &, const std::shared_ptr<Server> &, const std::string &, const std::string &); + using OnQueryCommand = void (*)(Irccd &, const std::shared_ptr<Server> &, const std::string &, const std::string &); + using OnReload = void (*)(Irccd &, DynlibPlugin &); + using OnTopic = void (*)(Irccd &, const std::shared_ptr<Server> &, const std::string &, const std::string &, const std::string &); + using OnUnload = void (*)(Irccd &, DynlibPlugin &); + using OnWhois = void (*)(Irccd &, const std::shared_ptr<Server> &, const ServerWhois &); + + Dynlib m_dso; + OnCommand m_onCommand; + OnConnect m_onConnect; + OnChannelMode m_onChannelMode; + OnChannelNotice m_onChannelNotice; + OnInvite m_onInvite; + OnJoin m_onJoin; + OnKick m_onKick; + OnLoad m_onLoad; + OnMessage m_onMessage; + OnMe m_onMe; + OnMode m_onMode; + OnNames m_onNames; + OnNick m_onNick; + OnNotice m_onNotice; + OnPart m_onPart; + OnQuery m_onQuery; + OnQueryCommand m_onQueryCommand; + OnReload m_onReload; + OnTopic m_onTopic; + OnUnload m_onUnload; + OnWhois m_onWhois; + +public: + /** + * Construct the plugin. + * + * \param name the name + * \param path the fully resolved path (must be absolute) + * \param config the optional configuration + * \throw std::exception on failures + */ + DynlibPlugin(std::string name, std::string path, PluginConfig config = PluginConfig()); + + /** + * \copydoc Plugin::onCommand + */ + void onCommand(Irccd &irccd, + const std::shared_ptr<Server> &server, + const std::string &origin, + const std::string &channel, + const std::string &message) override; + + /** + * \copydoc Plugin::onConnect + */ + void onConnect(Irccd &irccd, const std::shared_ptr<Server> &server) override; + + /** + * \copydoc Plugin::onChannelMode + */ + void onChannelMode(Irccd &irccd, + const std::shared_ptr<Server> &server, + const std::string &origin, + const std::string &channel, + const std::string &mode, + const std::string &arg) override; + + /** + * \copydoc Plugin::onChannelNotice + */ + void onChannelNotice(Irccd &irccd, + const std::shared_ptr<Server> &server, + const std::string &origin, + const std::string &channel, + const std::string ¬ice) override; + + /** + * \copydoc Plugin::onInvite + */ + void onInvite(Irccd &irccd, const std::shared_ptr<Server> &server, const std::string &origin, const std::string &channel) override; + + /** + * \copydoc Plugin::onJoin + */ + void onJoin(Irccd &irccd, const std::shared_ptr<Server> &server, const std::string &origin, const std::string &channel) override; + + /** + * \copydoc Plugin::onKick + */ + void onKick(Irccd &irccd, + const std::shared_ptr<Server> &server, + const std::string &origin, + const std::string &channel, + const std::string &target, + const std::string &reason) override; + + /** + * \copydoc Plugin::onLoad + */ + void onLoad(Irccd &irccd) override; + + /** + * \copydoc Plugin::onMessage + */ + void onMessage(Irccd &irccd, + const std::shared_ptr<Server> &server, + const std::string &origin, + const std::string &channel, + const std::string &message) override; + + /** + * \copydoc Plugin::onMe + */ + void onMe(Irccd &irccd, + const std::shared_ptr<Server> &server, + const std::string &origin, + const std::string &channel, + const std::string &message) override; + + /** + * \copydoc Plugin::onMode + */ + void onMode(Irccd &irccd, const std::shared_ptr<Server> &server, const std::string &origin, const std::string &mode) override; + + /** + * \copydoc Plugin::onNames + */ + void onNames(Irccd &irccd, + const std::shared_ptr<Server> &server, + const std::string &channel, + const std::vector<std::string> &list) override; + + /** + * \copydoc Plugin::onNick + */ + void onNick(Irccd &irccd, const std::shared_ptr<Server> &server, const std::string &origin, const std::string &nick) override; + + /** + * \copydoc Plugin::onNotice + */ + void onNotice(Irccd &irccd, const std::shared_ptr<Server> &server, const std::string &origin, const std::string ¬ice) override; + + /** + * \copydoc Plugin::onPart + */ + void onPart(Irccd &irccd, + const std::shared_ptr<Server> &server, + const std::string &origin, + const std::string &channel, + const std::string &reason) override; + + /** + * \copydoc Plugin::onQuery + */ + void onQuery(Irccd &irccd, const std::shared_ptr<Server> &server, const std::string &origin, const std::string &message) override; + + /** + * \copydoc Plugin::onQueryCommand + */ + void onQueryCommand(Irccd &irccd, + const std::shared_ptr<Server> &server, + const std::string &origin, + const std::string &message) override; + + /** + * \copydoc Plugin::onReload + */ + void onReload(Irccd &irccd) override; + + /** + * \copydoc Plugin::onTopic + */ + void onTopic(Irccd &irccd, + const std::shared_ptr<Server> &server, + const std::string &origin, + const std::string &channel, + const std::string &topic) override; + + /** + * \copydoc Plugin::onUnload + */ + void onUnload(Irccd &irccd) override; + + /** + * \copydoc Plugin::onWhois + */ + void onWhois(Irccd &irccd, const std::shared_ptr<Server> &server, const ServerWhois &info) override; +}; + +} // !irccd + +#endif // !IRCCD_PLUGIN_DYNLIB_HPP
--- a/lib/irccd/service-plugin.cpp Wed May 18 13:24:50 2016 +0200 +++ b/lib/irccd/service-plugin.cpp Wed May 18 22:31:24 2016 +0200 @@ -18,6 +18,7 @@ #include <algorithm> #include <functional> +#include <regex> #include <stdexcept> #include <format.h> @@ -25,6 +26,7 @@ #include "fs.hpp" #include "irccd.hpp" #include "logger.hpp" +#include "plugin-dynlib.hpp" #include "plugin-js.hpp" #include "service-plugin.hpp" @@ -32,6 +34,46 @@ namespace irccd { +namespace { + +std::shared_ptr<Plugin> find(std::unordered_map<std::string, PluginConfig> &configs, std::string name) +{ + PluginConfig config = configs[name]; + + for (const auto &path : path::list(path::PathPlugins)) { + std::string jspath = path + name + ".js"; + std::string dynlibpath = path + name + DYNLIB_SUFFIX; + + if (fs::isReadable(jspath)) + return std::make_shared<JsPlugin>(std::move(name), std::move(jspath), std::move(config)); + if (fs::isReadable(dynlibpath)) + return std::make_shared<DynlibPlugin>(std::move(name), std::move(dynlibpath)); + } + + throw std::runtime_error("no suitable plugin found"); +} + +std::shared_ptr<Plugin> open(std::string name, std::string path) +{ + std::regex regex(".*(\\..*)$"); + std::smatch match; + std::shared_ptr<Plugin> plugin; + + if (std::regex_match(path, match, regex)) { + std::string extension = match[1]; + + if (extension == DYNLIB_SUFFIX) + plugin = std::make_shared<DynlibPlugin>(name, path); + else + plugin = std::make_shared<JsPlugin>(name, path); + } else + throw std::runtime_error("could not deduce plugin type from {}"_format(path)); + + return plugin; +} + +} // !namespace + PluginService::PluginService(Irccd &irccd) noexcept : m_irccd(irccd) { @@ -39,9 +81,8 @@ PluginService::~PluginService() { - for (const auto &plugin : m_plugins) { + for (const auto &plugin : m_plugins) plugin->onUnload(m_irccd); - } } bool PluginService::has(const std::string &name) const noexcept @@ -57,9 +98,8 @@ return plugin->name() == name; }); - if (it == m_plugins.end()) { + if (it == m_plugins.end()) return nullptr; - } return *it; } @@ -68,9 +108,8 @@ { auto plugin = get(name); - if (!plugin) { + if (!plugin) throw std::invalid_argument("plugin {} not found"_format(name)); - } return plugin; } @@ -80,25 +119,6 @@ m_plugins.push_back(std::move(plugin)); } -std::shared_ptr<Plugin> PluginService::find(std::string name) -{ - PluginConfig config = m_config[name]; - - for (const auto &path : path::list(path::PathPlugins)) { - std::string fullpath = path + name + ".js"; - - if (!fs::isReadable(fullpath)) { - continue; - } - - log::info("plugin {}: trying {}"_format(name, fullpath)); - - return std::make_shared<JsPlugin>(std::move(name), std::move(fullpath), std::move(config)); - } - - throw std::runtime_error("no suitable plugin found"); -} - void PluginService::configure(const std::string &name, PluginConfig config) { m_config.emplace(name, std::move(config)); @@ -108,9 +128,8 @@ { auto it = m_config.find(name); - if (it != m_config.end()) { + if (it != m_config.end()) return it->second; - } return PluginConfig(); } @@ -121,19 +140,16 @@ return plugin->name() == name; }); - if (it != m_plugins.end()) { + if (it != m_plugins.end()) return; - } try { std::shared_ptr<Plugin> plugin; - if (path.empty()) { - plugin = find(std::move(name)); - } else { - // TODO: DYNLIB BASED PLUGINS - plugin = std::make_shared<JsPlugin>(std::move(name), std::move(path)); - } + if (path.empty()) + plugin = find(m_config, std::move(name)); + else + plugin = open(std::move(name), std::move(path)); plugin->onLoad(m_irccd); add(std::move(plugin)); @@ -146,9 +162,8 @@ { auto plugin = get(name); - if (plugin) { + if (plugin) plugin->onReload(m_irccd); - } } void PluginService::unload(const std::string &name)
--- a/lib/irccd/service-plugin.hpp Wed May 18 13:24:50 2016 +0200 +++ b/lib/irccd/service-plugin.hpp Wed May 18 22:31:24 2016 +0200 @@ -101,15 +101,6 @@ void add(std::shared_ptr<Plugin> plugin); /** - * Search a plugin through the predefined directories. - * - * \param name the plugin name (without any extension) - * \return the plugin if found - * \throw std::exception on any errors - */ - std::shared_ptr<Plugin> find(std::string name); - - /** * Configure a plugin. * * If the plugin is already loaded, its configuration is updated.