From eb03b33655ec4f2b736d4cc7e1ff915395cb77be Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Tue, 4 Dec 2018 13:34:14 +1030 Subject: [PATCH] plugins: add and install built-in plugin dir, add clear and disable options. Signed-off-by: Rusty Russell --- Makefile | 8 +++++ lightningd/lightningd.c | 25 +++++++++---- lightningd/options.c | 32 ++++++++++++++--- lightningd/plugin.c | 51 +++++++++++++++++++++++++-- lightningd/plugin.h | 17 ++++++++- lightningd/test/run-find_my_abspath.c | 4 +++ plugins/README.md | 6 ++++ tests/test_plugin.py | 20 +++++++++++ 8 files changed, 148 insertions(+), 15 deletions(-) create mode 100644 plugins/README.md diff --git a/Makefile b/Makefile index a7ea37758..3e74d4bc7 100644 --- a/Makefile +++ b/Makefile @@ -401,6 +401,7 @@ exec_prefix = $(prefix) bindir = $(exec_prefix)/bin libexecdir = $(exec_prefix)/libexec pkglibexecdir = $(libexecdir)/$(PKGNAME) +plugindir = $(pkglibexecdir)/plugins datadir = $(prefix)/share docdir = $(datadir)/doc/$(PKGNAME) mandir = $(datadir)/man @@ -427,6 +428,7 @@ installdirs: @$(NORMAL_INSTALL) $(MKDIR_P) $(DESTDIR)$(bindir) $(MKDIR_P) $(DESTDIR)$(pkglibexecdir) + $(MKDIR_P) $(DESTDIR)$(plugindir) $(MKDIR_P) $(DESTDIR)$(man1dir) $(MKDIR_P) $(DESTDIR)$(man5dir) $(MKDIR_P) $(DESTDIR)$(man7dir) @@ -446,11 +448,13 @@ PKGLIBEXEC_PROGRAMS = \ lightningd/lightning_hsmd \ lightningd/lightning_onchaind \ lightningd/lightning_openingd +PLUGINS= install-program: installdirs $(BIN_PROGRAMS) $(PKGLIBEXEC_PROGRAMS) @$(NORMAL_INSTALL) $(INSTALL_PROGRAM) $(BIN_PROGRAMS) $(DESTDIR)$(bindir) $(INSTALL_PROGRAM) $(PKGLIBEXEC_PROGRAMS) $(DESTDIR)$(pkglibexecdir) + [ -z "$(PLUGINS)" ] || $(INSTALL_PROGRAM) $(PLUGINS) $(DESTDIR)$(plugindir) MAN1PAGES = $(filter %.1,$(MANPAGES)) MAN5PAGES = $(filter %.5,$(MANPAGES)) @@ -472,6 +476,10 @@ uninstall: echo rm -f $(DESTDIR)$(bindir)/`basename $$f`; \ rm -f $(DESTDIR)$(bindir)/`basename $$f`; \ done + @for f in $(PLUGINS); do \ + echo rm -f $(DESTDIR)$(plugindir)/`basename $$f`; \ + rm -f $(DESTDIR)$(plugindir)/`basename $$f`; \ + done @for f in $(PKGLIBEXEC_PROGRAMS); do \ echo rm -f $(DESTDIR)$(pkglibexecdir)/`basename $$f`; \ rm -f $(DESTDIR)$(pkglibexecdir)/`basename $$f`; \ diff --git a/lightningd/lightningd.c b/lightningd/lightningd.c index 15f205550..9e9fcb972 100644 --- a/lightningd/lightningd.c +++ b/lightningd/lightningd.c @@ -339,7 +339,7 @@ static const char *find_my_directory(const tal_t *ctx, const char *argv0) * * TAKES is only a convention unfortunately, and ignored by the compiler. */ -static const char *find_my_pkglibexec_path(const tal_t *ctx, +static const char *find_my_pkglibexec_path(struct lightningd *ld, const char *my_path TAKES) { const char *pkglibexecdir; @@ -352,23 +352,34 @@ static const char *find_my_pkglibexec_path(const tal_t *ctx, * So, as we promised with 'TAKES' in our own declaration, if the * caller has called `take()` the `my_path` parameter, path_join() * will free it. */ - pkglibexecdir = path_join(ctx, my_path, BINTOPKGLIBEXECDIR); + pkglibexecdir = path_join(NULL, my_path, BINTOPKGLIBEXECDIR); + + /*~ The plugin dir is in ../libexec/c-lightning/plugins, which (unlike + * those given on the command line) does not need to exist. */ + add_plugin_dir(ld->plugins, + path_join(tmpctx, pkglibexecdir, "plugins"), + true); /*~ Sometimes take() can be more efficient, since the routine can * manipulate the string in place. This is the case here. */ - return path_simplify(ctx, take(pkglibexecdir)); + return path_simplify(ld, take(pkglibexecdir)); } /* Determine the correct daemon dir. */ -static const char *find_daemon_dir(const tal_t *ctx, const char *argv0) +static const char *find_daemon_dir(struct lightningd *ld, const char *argv0) { - const char *my_path = find_my_directory(ctx, argv0); + const char *my_path = find_my_directory(ld, argv0); /* If we're running in-tree, all the subdaemons are with lightningd. */ - if (has_all_subdaemons(my_path)) + if (has_all_subdaemons(my_path)) { + /* In this case, look in ../plugins */ + add_plugin_dir(ld->plugins, + path_join(tmpctx, my_path, "../plugins"), + true); return my_path; + } /* Otherwise we assume they're in the installed dir. */ - return find_my_pkglibexec_path(ctx, take(my_path)); + return find_my_pkglibexec_path(ld, take(my_path)); } /*~ We like to free everything on exit, so valgrind doesn't complain (valgrind diff --git a/lightningd/options.c b/lightningd/options.c index 1ff0640b2..6569157ca 100644 --- a/lightningd/options.c +++ b/lightningd/options.c @@ -293,9 +293,22 @@ static char *opt_add_plugin(const char *arg, struct lightningd *ld) return NULL; } +static char *opt_disable_plugin(const char *arg, struct lightningd *ld) +{ + if (plugin_remove(ld->plugins, arg)) + return NULL; + return tal_fmt(NULL, "Could not find plugin %s", arg); +} + static char *opt_add_plugin_dir(const char *arg, struct lightningd *ld) { - return add_plugin_dir(ld->plugins, arg); + return add_plugin_dir(ld->plugins, arg, false); +} + +static char *opt_clear_plugins(struct lightningd *ld) +{ + clear_plugins(ld->plugins); + return NULL; } static void config_register_opts(struct lightningd *ld) @@ -304,13 +317,19 @@ static void config_register_opts(struct lightningd *ld) &ld->config_filename, "Specify configuration file. Relative paths will be prefixed by lightning-dir location. (default: config)"); - /* Register plugins as an early argc, so we can initialize them and have + /* Register plugins as an early args, so we can initialize them and have * them register more command line options */ opt_register_early_arg("--plugin", opt_add_plugin, NULL, ld, "Add a plugin to be run (can be used multiple times)"); opt_register_early_arg("--plugin-dir", opt_add_plugin_dir, NULL, ld, "Add a directory to load plugins from (can be used multiple times)"); + opt_register_early_noarg("--clear-plugins", opt_clear_plugins, + ld, + "Remove all plugins added before this option"); + opt_register_early_arg("--disable-plugin", opt_disable_plugin, + NULL, ld, + "Disable a particular plugin by filename/name"); opt_register_noarg("--daemon", opt_set_bool, &ld->daemon, "Run in the background, suppress stdout/stderr"); @@ -924,7 +943,9 @@ static void add_config(struct lightningd *ld, || opt->cb == (void *)opt_set_testnet || opt->cb == (void *)opt_set_mainnet || opt->cb == (void *)opt_lightningd_usage - || opt->cb == (void *)test_subdaemons_and_exit) { + || opt->cb == (void *)test_subdaemons_and_exit + /* FIXME: we can't recover this. */ + || opt->cb == (void *)opt_clear_plugins) { /* These are not important */ } else if (opt->cb == (void *)opt_set_bool) { const bool *b = opt->u.carg; @@ -997,9 +1018,10 @@ static void add_config(struct lightningd *ld, answer = fmt_wireaddr(name0, ld->proxyaddr); } else if (opt->cb_arg == (void *)opt_add_plugin) { json_add_opt_plugins(response, ld->plugins); - } else if (opt->cb_arg == (void *)opt_add_plugin_dir) { + } else if (opt->cb_arg == (void *)opt_add_plugin_dir + || opt->cb_arg == (void *)opt_disable_plugin) { /* FIXME: We actually treat it as if they specified - * --plugin for each one, so ignore this */ + * --plugin for each one, so ignore these */ #if DEVELOPER } else if (strstarts(name, "dev-")) { /* Ignore dev settings */ diff --git a/lightningd/plugin.c b/lightningd/plugin.c index 51a2074d7..d98288e6b 100644 --- a/lightningd/plugin.c +++ b/lightningd/plugin.c @@ -134,6 +134,41 @@ void plugin_register(struct plugins *plugins, const char* path TAKES) list_head_init(&p->plugin_opts); } +static bool paths_match(const char *cmd, const char *name) +{ + if (strchr(name, PATH_SEP)) { + const char *cmd_canon, *name_canon; + + if (streq(cmd, name)) + return true; + + /* These return NULL path doesn't exist */ + cmd_canon = path_canon(tmpctx, cmd); + name_canon = path_canon(tmpctx, name); + return cmd_canon && name_canon && streq(name_canon, cmd_canon); + } else { + /* No path separator means a basename match. */ + const char *base = path_basename(tmpctx, cmd); + + return streq(base, name); + } +} + +bool plugin_remove(struct plugins *plugins, const char *name) +{ + struct plugin *p, *next; + bool removed = false; + + list_for_each_safe(&plugins->plugins, p, next, list) { + if (paths_match(p->cmd, name)) { + list_del_from(&plugins->plugins, &p->list); + tal_free(p); + removed = true; + } + } + return removed; +} + /** * Kill a plugin process, with an error message. */ @@ -638,13 +673,16 @@ static const char *plugin_fullpath(const tal_t *ctx, const char *dir, return fullname; } -char *add_plugin_dir(struct plugins *plugins, const char *dir) +char *add_plugin_dir(struct plugins *plugins, const char *dir, bool nonexist_ok) { struct dirent *di; DIR *d = opendir(dir); - if (!d) + if (!d) { + if (nonexist_ok && errno == ENOENT) + return NULL; return tal_fmt("Failed to open plugin-dir %s: %s", dir, strerror(errno)); + } while ((di = readdir(d)) != NULL) { const char *fullpath; @@ -658,6 +696,15 @@ char *add_plugin_dir(struct plugins *plugins, const char *dir) return NULL; } +void clear_plugins(struct plugins *plugins) +{ + struct plugin *p; + + log_info(plugins->log, "clear-plugins removing all plugins"); + while ((p = list_pop(&plugins->plugins, struct plugin, list)) != NULL) + tal_free(p); +} + void plugins_init(struct plugins *plugins) { struct plugin *p; diff --git a/lightningd/plugin.h b/lightningd/plugin.h index f6ab81f6d..73317ec91 100644 --- a/lightningd/plugin.h +++ b/lightningd/plugin.h @@ -38,6 +38,15 @@ void plugins_init(struct plugins *plugins); */ void plugin_register(struct plugins *plugins, const char* path TAKES); + +/** + * Remove a plugin registered for initialization. + * + * @param plugins: Plugin context + * @param arg: The basename or fullname of the executable for this plugin + */ +bool plugin_remove(struct plugins *plugins, const char *name); + /** * Send the configure message to all plugins. * @@ -60,6 +69,12 @@ void json_add_opt_plugins(struct json_stream *response, /** * Add a directory to the plugin path to automatically load plugins. */ -char *add_plugin_dir(struct plugins *plugins, const char *dir); +char *add_plugin_dir(struct plugins *plugins, const char *dir, + bool nonexist_ok); + +/** + * Clear all plugins registered so far. + */ +void clear_plugins(struct plugins *plugins); #endif /* LIGHTNING_LIGHTNINGD_PLUGIN_H */ diff --git a/lightningd/test/run-find_my_abspath.c b/lightningd/test/run-find_my_abspath.c index 9c0fd559f..31e160052 100644 --- a/lightningd/test/run-find_my_abspath.c +++ b/lightningd/test/run-find_my_abspath.c @@ -9,6 +9,10 @@ int unused_main(int argc, char *argv[]); /* Generated stub for activate_peers */ void activate_peers(struct lightningd *ld UNNEEDED) { fprintf(stderr, "activate_peers called!\n"); abort(); } +/* Generated stub for add_plugin_dir */ +char *add_plugin_dir(struct plugins *plugins UNNEEDED, const char *dir UNNEEDED, + bool nonexist_ok UNNEEDED) +{ fprintf(stderr, "add_plugin_dir called!\n"); abort(); } /* Generated stub for begin_topology */ void begin_topology(struct chain_topology *topo UNNEEDED) { fprintf(stderr, "begin_topology called!\n"); abort(); } diff --git a/plugins/README.md b/plugins/README.md new file mode 100644 index 000000000..a022265b3 --- /dev/null +++ b/plugins/README.md @@ -0,0 +1,6 @@ +# Plugin Directory + +Anything file in this directory which is executable and doesn't start +and doesn't contain ASCII symbols other than '.', '-' or '_' will be +automatically loaded when lightningd starts (unless suppressed with +commandline options). diff --git a/tests/test_plugin.py b/tests/test_plugin.py index 4ef11bce8..3aa1ccb41 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -1,3 +1,4 @@ +from collections import OrderedDict from fixtures import * # noqa: F401,F403 from lightning import RpcError @@ -57,3 +58,22 @@ def test_plugin_dir(node_factory): """--plugin-dir works""" plugin_dir = 'contrib/plugins' node_factory.get_node(options={'plugin-dir': plugin_dir, 'greeting': 'Mars'}) + + +def test_plugin_disable(node_factory): + """--disable-plugin works""" + plugin_dir = 'contrib/plugins' + # We need plugin-dir before disable-plugin! + n = node_factory.get_node(options=OrderedDict([('plugin-dir', plugin_dir), + ('disable-plugin', + '{}/helloworld.py' + .format(plugin_dir))])) + with pytest.raises(RpcError): + n.rpc.hello(name='Sun') + + # Also works by basename. + n = node_factory.get_node(options=OrderedDict([('plugin-dir', plugin_dir), + ('disable-plugin', + 'helloworld.py')])) + with pytest.raises(RpcError): + n.rpc.hello(name='Sun')