diff --git a/lightningd/options.c b/lightningd/options.c index b9afcf6ae..1ff0640b2 100644 --- a/lightningd/options.c +++ b/lightningd/options.c @@ -293,6 +293,11 @@ static char *opt_add_plugin(const char *arg, struct lightningd *ld) return NULL; } +static char *opt_add_plugin_dir(const char *arg, struct lightningd *ld) +{ + return add_plugin_dir(ld->plugins, arg); +} + static void config_register_opts(struct lightningd *ld) { opt_register_early_arg("--conf=", opt_set_talstr, NULL, @@ -302,7 +307,10 @@ static void config_register_opts(struct lightningd *ld) /* Register plugins as an early argc, 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."); + "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_noarg("--daemon", opt_set_bool, &ld->daemon, "Run in the background, suppress stdout/stderr"); @@ -989,6 +997,9 @@ 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) { + /* FIXME: We actually treat it as if they specified + * --plugin for each one, so ignore this */ #if DEVELOPER } else if (strstarts(name, "dev-")) { /* Ignore dev settings */ diff --git a/lightningd/plugin.c b/lightningd/plugin.c index 254c819ea..51a2074d7 100644 --- a/lightningd/plugin.c +++ b/lightningd/plugin.c @@ -5,13 +5,18 @@ #include #include #include +#include #include +#include #include +#include #include #include #include #include #include +#include +#include #include struct plugin { @@ -22,6 +27,7 @@ struct plugin { struct io_conn *stdin_conn, *stdout_conn; bool stop; struct plugins *plugins; + const char **plugin_path; /* Stuff we read */ char *buffer; @@ -602,6 +608,56 @@ static void plugin_manifest_cb(const struct plugin_request *req, struct plugin * plugin_kill(plugin, "Failed to register options or methods"); } +/* If this is a valid plugin return full path name, otherwise NULL */ +static const char *plugin_fullpath(const tal_t *ctx, const char *dir, + const char *basename) +{ + struct stat st; + const char *fullname; + struct utf8_state utf8 = UTF8_STATE_INIT; + + for (size_t i = 0; basename[i]; i++) { + if (!utf8_decode(&utf8, basename[i])) + continue; + /* Not valid UTF8? Let's not go there... */ + if (errno != 0) + return NULL; + if (utf8.used_len != 1) + continue; + if (!cispunct(utf8.c)) + continue; + if (utf8.c != '-' && utf8.c != '_' && utf8.c != '.') + return NULL; + } + + fullname = path_join(ctx, dir, basename); + if (stat(fullname, &st) != 0) + return tal_free(fullname); + if (!(st.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH))) + return tal_free(fullname); + return fullname; +} + +char *add_plugin_dir(struct plugins *plugins, const char *dir) +{ + struct dirent *di; + DIR *d = opendir(dir); + if (!d) + return tal_fmt("Failed to open plugin-dir %s: %s", + dir, strerror(errno)); + + while ((di = readdir(d)) != NULL) { + const char *fullpath; + + if (streq(di->d_name, ".") || streq(di->d_name, "..")) + continue; + fullpath = plugin_fullpath(NULL, dir, di->d_name); + if (fullpath) + plugin_register(plugins, take(fullpath)); + } + return NULL; +} + void plugins_init(struct plugins *plugins) { struct plugin *p; diff --git a/lightningd/plugin.h b/lightningd/plugin.h index dac012ffe..f6ab81f6d 100644 --- a/lightningd/plugin.h +++ b/lightningd/plugin.h @@ -56,4 +56,10 @@ void plugins_config(struct plugins *plugins); void json_add_opt_plugins(struct json_stream *response, const struct plugins *plugins); + +/** + * Add a directory to the plugin path to automatically load plugins. + */ +char *add_plugin_dir(struct plugins *plugins, const char *dir); + #endif /* LIGHTNING_LIGHTNINGD_PLUGIN_H */ diff --git a/tests/test_plugin.py b/tests/test_plugin.py index 1a9f46eaf..4ef11bce8 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -51,3 +51,9 @@ def test_rpc_passthrough(node_factory): assert(greet == "Hello Sun") with pytest.raises(RpcError): n.rpc.fail() + + +def test_plugin_dir(node_factory): + """--plugin-dir works""" + plugin_dir = 'contrib/plugins' + node_factory.get_node(options={'plugin-dir': plugin_dir, 'greeting': 'Mars'})