Skip to content

Commit

Permalink
Plugin: support extra args to "start".
Browse files Browse the repository at this point in the history
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
Changelog-Added: Plugins: `start` command can now take plugin-specific parameters.
  • Loading branch information
rustyrussell committed Dec 14, 2020
1 parent 8a9976c commit d971e3d
Show file tree
Hide file tree
Showing 8 changed files with 117 additions and 18 deletions.
5 changes: 3 additions & 2 deletions contrib/pyln-client/pyln/client/lightning.py
Original file line number Diff line number Diff line change
Expand Up @@ -1024,14 +1024,15 @@ def ping(self, peer_id, length=128, pongbytes=128):
}
return self.call("ping", payload)

def plugin_start(self, plugin):
def plugin_start(self, plugin, **kwargs):
"""
Adds a plugin to lightningd.
"""
payload = {
"subcommand": "start",
"plugin": plugin
"plugin": plugin,
}
payload.update({k: v for k, v in kwargs.items()})
return self.call("plugin", payload)

def plugin_startdir(self, directory):
Expand Down
9 changes: 5 additions & 4 deletions doc/lightning-plugin.7

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions doc/lightning-plugin.7.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@ restart lightningd. It takes 1 to 3 parameters: a command
optionally one or two parameters which describes the plugin on which the
action has to be taken.

The *start* command takes a path as the first parameter and will load the
plugin available from this path. It will wait for the plugin to complete
the handshake with `lightningd` for 20 seconds at the most.
The *start* command takes a path as the first parameter and will load
the plugin available from this path. Any additional parameters are
passed to the plugin. It will wait for the plugin to complete the
handshake with `lightningd` for 20 seconds at the most.

The *stop* command takes a plugin name as parameter. It will kill and
unload the specified plugin.
Expand Down
4 changes: 2 additions & 2 deletions lightningd/options.c
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ static char *opt_add_plugin(const char *arg, struct lightningd *ld)
log_info(ld->log, "%s: disabled via disable-plugin", arg);
return NULL;
}
plugin_register(ld->plugins, arg, NULL, false);
plugin_register(ld->plugins, arg, NULL, false, NULL, NULL);
return NULL;
}

Expand All @@ -375,7 +375,7 @@ static char *opt_important_plugin(const char *arg, struct lightningd *ld)
log_info(ld->log, "%s: disabled via disable-plugin", arg);
return NULL;
}
plugin_register(ld->plugins, arg, NULL, true);
plugin_register(ld->plugins, arg, NULL, true, NULL, NULL);
return NULL;
}

Expand Down
57 changes: 54 additions & 3 deletions lightningd/plugin.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include <ccan/array_size/array_size.h>
#include <ccan/list/list.h>
#include <ccan/mem/mem.h>
#include <ccan/opt/opt.h>
#include <ccan/tal/str/str.h>
#include <ccan/utf8/utf8.h>
Expand Down Expand Up @@ -174,7 +175,9 @@ static void destroy_plugin(struct plugin *p)
}

struct plugin *plugin_register(struct plugins *plugins, const char* path TAKES,
struct command *start_cmd, bool important)
struct command *start_cmd, bool important,
const char *parambuf STEALS,
const jsmntok_t *params STEALS)
{
struct plugin *p, *p_temp;

Expand Down Expand Up @@ -212,6 +215,8 @@ struct plugin *plugin_register(struct plugins *plugins, const char* path TAKES,
list_head_init(&p->pending_rpccalls);

p->important = important;
p->parambuf = tal_steal(p, parambuf);
p->params = tal_steal(p, params);
return p;
}

Expand Down Expand Up @@ -1154,6 +1159,48 @@ static const char *plugin_hooks_add(struct plugin *plugin, const char *buffer,
return NULL;
}

static struct plugin_opt *plugin_opt_find(struct plugin *plugin,
const char *name, size_t namelen)
{
struct plugin_opt *opt;

list_for_each(&plugin->plugin_opts, opt, list) {
/* Trim the `--` that we added before */
if (memeqstr(name, namelen, opt->name + 2))
return opt;
}
return NULL;
}

/* start command might have included plugin-specific parameters */
static const char *plugin_add_params(struct plugin *plugin)
{
size_t i;
const jsmntok_t *t;

if (!plugin->params)
return NULL;

json_for_each_obj(i, t, plugin->params) {
struct plugin_opt *popt;
char *err;

popt = plugin_opt_find(plugin,
plugin->parambuf + t->start,
t->end - t->start);
if (!popt) {
return tal_fmt(plugin, "unknown parameter %.*s",
json_tok_full_len(t),
json_tok_full(plugin->parambuf, t));
}
err = plugin_opt_set(json_strdup(tmpctx, plugin->parambuf,
t + 1), popt);
if (err)
return err;
}
return NULL;
}

static void plugin_manifest_timeout(struct plugin *plugin)
{
bool startup = plugin->plugins->startup;
Expand Down Expand Up @@ -1243,6 +1290,8 @@ static const char *plugin_parse_getmanifest_response(const char *buffer,
err = plugin_subscriptions_add(plugin, buffer, resulttok);
if (!err)
err = plugin_hooks_add(plugin, buffer, resulttok);
if (!err)
err = plugin_add_params(plugin);

plugin->plugin_state = NEEDS_INIT;
return err;
Expand Down Expand Up @@ -1360,7 +1409,8 @@ char *add_plugin_dir(struct plugins *plugins, const char *dir, bool error_ok)
log_info(plugins->log, "%s: disabled via disable-plugin",
fullpath);
} else {
p = plugin_register(plugins, fullpath, NULL, false);
p = plugin_register(plugins, fullpath, NULL, false,
NULL, NULL);
if (!p && !error_ok)
return tal_fmt(NULL, "Failed to register %s: %s",
fullpath, strerror(errno));
Expand Down Expand Up @@ -1807,7 +1857,8 @@ void plugins_set_builtin_plugins_dir(struct plugins *plugins,
take(path_join(NULL, dir,
list_of_builtin_plugins[i])),
NULL,
/* important = */ true);
/* important = */ true,
NULL, NULL);
}

struct plugin_destroyed {
Expand Down
11 changes: 10 additions & 1 deletion lightningd/plugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ struct plugin {
/* If set, the plugin is so important that if it terminates early,
* C-lightning should terminate as well. */
bool important;

/* Parameters for dynamically-started plugins. */
const char *parambuf;
const jsmntok_t *params;
};

/**
Expand Down Expand Up @@ -200,14 +204,19 @@ void plugins_free(struct plugins *plugins);
* @param path: The path of the executable for this plugin
* @param start_cmd: The optional JSON command which caused this.
* @param important: The plugin is important.
* @param parambuf: NULL, or the JSON buffer for extra parameters.
* @param params: NULL, or the tokens for extra parameters.
*
* If @start_cmd, then plugin_cmd_killed or plugin_cmd_succeeded will be called
* on it eventually.
*/
struct plugin *plugin_register(struct plugins *plugins,
const char* path TAKES,
struct command *start_cmd,
bool important);
bool important,
const char *parambuf STEALS,
const jsmntok_t *params STEALS);


/**
* Returns true if the provided name matches a plugin command
Expand Down
27 changes: 24 additions & 3 deletions lightningd/plugin_control.c
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,10 @@ struct command_result *plugin_cmd_all_complete(struct plugins *plugins,
* will give a result 60 seconds later at the most (once init completes).
*/
static struct command_result *
plugin_dynamic_start(struct command *cmd, const char *plugin_path)
plugin_dynamic_start(struct command *cmd, const char *plugin_path,
const char *buffer, const jsmntok_t *params)
{
struct plugin *p = plugin_register(cmd->ld->plugins, plugin_path, cmd, false);
struct plugin *p = plugin_register(cmd->ld->plugins, plugin_path, cmd, false, buffer, params);
const char *err;

if (!p)
Expand Down Expand Up @@ -173,15 +174,35 @@ static struct command_result *json_plugin_control(struct command *cmd,
return plugin_dynamic_stop(cmd, plugin_name);
} else if (streq(subcmd, "start")) {
const char *plugin_path;
jsmntok_t *mod_params;

if (!param(cmd, buffer, params,
p_req("subcommand", param_ignore, cmd),
p_req("plugin", param_string, &plugin_path),
p_opt_any(),
NULL))
return command_param_failed();

/* Manually parse any remaining options (only for objects,
* since plugin options must be explicitly named!). */
if (params->type == JSMN_ARRAY) {
if (params->size != 2)
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Extra parameters must be in object");
mod_params = NULL;
} else {
mod_params = json_tok_copy(cmd, params);

json_tok_remove(&mod_params, mod_params,
json_get_member(buffer, mod_params,
"subcommand") - 1, 1);
json_tok_remove(&mod_params, mod_params,
json_get_member(buffer, mod_params,
"plugin") - 1, 1);
}
if (access(plugin_path, X_OK) == 0)
return plugin_dynamic_start(cmd, plugin_path);
return plugin_dynamic_start(cmd, plugin_path,
buffer, mod_params);
else
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"%s is not executable: %s",
Expand Down
15 changes: 15 additions & 0 deletions tests/test_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -2182,3 +2182,18 @@ def test_htlc_accepted_hook_failonion(node_factory):
inv = l2.rpc.invoice(42, 'failonion000', '')['bolt11']
with pytest.raises(RpcError):
l1.rpc.pay(inv)


def test_dynamic_args(node_factory):
plugin_path = os.path.join(os.getcwd(), 'contrib/plugins/helloworld.py')

l1 = node_factory.get_node()
l1.rpc.plugin_start(plugin_path, greeting='Test arg parsing')

assert l1.rpc.call("hello") == "Test arg parsing world"
plugin = only_one([p for p in l1.rpc.listconfigs()['plugins'] if p['path'] == plugin_path])
assert plugin['options']['greeting'] == 'Test arg parsing'

l1.rpc.plugin_stop(plugin_path)

assert [p for p in l1.rpc.listconfigs()['plugins'] if p['path'] == plugin_path] == []

0 comments on commit d971e3d

Please sign in to comment.