plugins: add and install built-in plugin dir, add clear and disable options.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
Rusty Russell
2018-12-04 13:34:14 +10:30
committed by Christian Decker
parent ac5002a79e
commit eb03b33655
8 changed files with 148 additions and 15 deletions

View File

@@ -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`; \

View File

@@ -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

View File

@@ -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 */

View File

@@ -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;

View File

@@ -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 */

View File

@@ -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(); }

6
plugins/README.md Normal file
View File

@@ -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).

View File

@@ -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')