Rename from "luci-rpcd" to "rpcd"
[project/rpcd.git] / plugin.c
index 26dfda4..33266b3 100644 (file)
--- a/plugin.c
+++ b/plugin.c
@@ -1,5 +1,5 @@
 /*
- * luci-rpcd - LuCI UBUS RPC server
+ * rpcd - UBUS RPC server
  *
  *   Copyright (C) 2013 Jo-Philipp Wich <jow@openwrt.org>
  *
@@ -51,92 +51,139 @@ rpc_plugin_lookup_plugin(struct ubus_context *ctx, struct ubus_object *obj,
        return c.found;
 }
 
+struct call_context {
+       char path[PATH_MAX];
+       const char *argv[4];
+       char *method;
+       char *input;
+       json_tokener *tok;
+       json_object *obj;
+       bool input_done;
+       bool output_done;
+};
+
 static int
-rpc_plugin_call(struct ubus_context *ctx, struct ubus_object *obj,
-                struct ubus_request_data *req, const char *method,
-                struct blob_attr *msg)
+rpc_plugin_call_stdin_cb(struct ustream *s, void *priv)
 {
-       pid_t pid;
-       struct stat s;
-       int rv, fd, in_fds[2], out_fds[2];
-       char *input, *plugin, *meth, output[4096] = { 0 }, path[PATH_MAX] = { 0 };
-
-       meth = strdup(method);
-       input = blobmsg_format_json(msg, true);
-       plugin = path + sprintf(path, "%s/", RPC_PLUGIN_DIRECTORY);
+       struct call_context *c = priv;
 
-       if (!rpc_plugin_lookup_plugin(ctx, obj, plugin))
-               return UBUS_STATUS_NOT_FOUND;
+       if (!c->input_done)
+       {
+               ustream_write(s, c->input, strlen(c->input), false);
+               c->input_done = true;
+       }
 
-       if (stat(path, &s) || !(s.st_mode & S_IXUSR))
-               return UBUS_STATUS_NOT_FOUND;
+       return 0;
+}
 
-       if (pipe(in_fds) || pipe(out_fds))
-               return UBUS_STATUS_UNKNOWN_ERROR;
+static int
+rpc_plugin_call_stdout_cb(struct blob_buf *blob, char *buf, int len, void *priv)
+{
+       struct call_context *c = priv;
 
-       switch ((pid = fork()))
+       if (!c->output_done)
        {
-       case -1:
-               return UBUS_STATUS_UNKNOWN_ERROR;
+               c->obj = json_tokener_parse_ex(c->tok, buf, len);
 
-       case 0:
-               uloop_done();
+               if (json_tokener_get_error(c->tok) != json_tokener_continue)
+                       c->output_done = true;
+       }
 
-               fd = open("/dev/null", O_RDWR);
+       return len;
+}
 
-               if (fd > -1)
+static int
+rpc_plugin_call_stderr_cb(struct blob_buf *blob, char *buf, int len, void *priv)
+{
+       return len;
+}
+
+static int
+rpc_plugin_call_finish_cb(struct blob_buf *blob, int stat, void *priv)
+{
+       struct call_context *c = priv;
+       int rv = UBUS_STATUS_INVALID_ARGUMENT;
+
+       if (json_tokener_get_error(c->tok) == json_tokener_success)
+       {
+               if (c->obj)
                {
-                       dup2(fd, 2);
+                       if (json_object_get_type(c->obj) == json_type_object ||
+                           json_object_get_type(c->obj) == json_type_array)
+                       {
+                               blobmsg_add_json_element(blob, NULL, c->obj);
+                               rv = UBUS_STATUS_OK;
+                       }
 
-                       if (fd > 2)
-                               close(fd);
+                       json_object_put(c->obj);
                }
+               else
+               {
+                       rv = UBUS_STATUS_NO_DATA;
+               }
+       }
 
-               dup2(in_fds[0], 0);
-               dup2(out_fds[1], 1);
+       json_tokener_free(c->tok);
 
-               close(in_fds[0]);
-               close(in_fds[1]);
-               close(out_fds[0]);
-               close(out_fds[1]);
+       free(c->input);
+       free(c->method);
 
-               if (execl(path, plugin, "call", meth, NULL))
-                       return UBUS_STATUS_UNKNOWN_ERROR;
+       return rv;
+}
 
-       default:
-               rv = UBUS_STATUS_NO_DATA;
+static int
+rpc_plugin_call(struct ubus_context *ctx, struct ubus_object *obj,
+                struct ubus_request_data *req, const char *method,
+                struct blob_attr *msg)
+{
+       int rv = UBUS_STATUS_UNKNOWN_ERROR;
+       struct call_context *c;
+       char *plugin;
 
-               if (input)
-               {
-                       write(in_fds[1], input, strlen(input));
-                       free(input);
-               }
+       c = calloc(1, sizeof(*c));
 
-               close(in_fds[0]);
-               close(in_fds[1]);
+       if (!c)
+               goto fail;
 
-               if (read(out_fds[0], output, sizeof(output) - 1) > 0)
-               {
-                       blob_buf_init(&buf, 0);
+       c->method = strdup(method);
+       c->input = blobmsg_format_json(msg, true);
+       c->tok = json_tokener_new();
 
-                       if (!blobmsg_add_json_from_string(&buf, output))
-                               rv = UBUS_STATUS_INVALID_ARGUMENT;
+       if (!c->method || !c->input || !c->tok)
+               goto fail;
 
-                       rv = UBUS_STATUS_OK;
-               }
+       plugin = c->path + sprintf(c->path, "%s/", RPC_PLUGIN_DIRECTORY);
 
-               close(out_fds[0]);
-               close(out_fds[1]);
+       if (!rpc_plugin_lookup_plugin(ctx, obj, plugin))
+       {
+               rv = UBUS_STATUS_NOT_FOUND;
+               goto fail;
+       }
 
-               waitpid(pid, NULL, 0);
+       c->argv[0] = c->path;
+       c->argv[1] = "call";
+       c->argv[2] = c->method;
 
-               if (!rv)
-                       ubus_send_reply(ctx, req, buf.head);
+       return rpc_exec(c->argv, rpc_plugin_call_stdin_cb,
+                       rpc_plugin_call_stdout_cb, rpc_plugin_call_stderr_cb,
+                       rpc_plugin_call_finish_cb, c, ctx, req);
 
-               free(meth);
+fail:
+       if (c)
+       {
+               if (c->method)
+                       free(c->method);
 
-               return rv;
+               if (c->input)
+                       free(c->input);
+
+               if (c->tok)
+                       json_tokener_free(c->tok);
+
+               free(c);
        }
+
+       return rv;
 }
 
 static bool
@@ -206,19 +253,47 @@ rpc_plugin_parse_signature(struct blob_attr *sig, struct ubus_method *method)
 }
 
 static struct ubus_object *
-rpc_plugin_parse_plugin(const char *name, const char *listbuf)
+rpc_plugin_parse_exec(const char *name, int fd)
 {
-       int rem, n_method;
+       int len, rem, n_method;
        struct blob_attr *cur;
        struct ubus_method *methods;
        struct ubus_object_type *obj_type;
        struct ubus_object *obj;
+       char outbuf[1024];
+
+       json_tokener *tok;
+       json_object *jsobj;
 
        blob_buf_init(&buf, 0);
 
-       if (!blobmsg_add_json_from_string(&buf, listbuf))
+       tok = json_tokener_new();
+
+       if (!tok)
                return NULL;
 
+       while ((len = read(fd, outbuf, sizeof(outbuf))) > 0)
+       {
+               jsobj = json_tokener_parse_ex(tok, outbuf, len);
+
+               if (json_tokener_get_error(tok) == json_tokener_continue)
+                       continue;
+
+               if (json_tokener_get_error(tok) != json_tokener_success)
+                       break;
+
+               if (jsobj)
+               {
+                       if (json_object_get_type(jsobj) == json_type_object)
+                               blobmsg_add_object(&buf, jsobj);
+
+                       json_object_put(jsobj);
+                       break;
+               }
+       }
+
+       json_tokener_free(tok);
+
        n_method = 0;
 
        blob_for_each_attr(cur, buf.head, rem)
@@ -265,12 +340,11 @@ rpc_plugin_parse_plugin(const char *name, const char *listbuf)
 }
 
 static int
-rpc_plugin_register(struct ubus_context *ctx, const char *path)
+rpc_plugin_register_exec(struct ubus_context *ctx, const char *path)
 {
        pid_t pid;
-       int rv, fd, fds[2];
+       int rv = UBUS_STATUS_NO_DATA, fd, fds[2];
        const char *name;
-       char listbuf[4096] = { 0 };
        struct ubus_object *plugin;
 
        name = strrchr(path, '/');
@@ -307,12 +381,7 @@ rpc_plugin_register(struct ubus_context *ctx, const char *path)
                        return UBUS_STATUS_UNKNOWN_ERROR;
 
        default:
-               rv = 0;
-
-               if (read(fds[0], listbuf, sizeof(listbuf) - 1) <= 0)
-                       goto out;
-
-               plugin = rpc_plugin_parse_plugin(name + 1, listbuf);
+               plugin = rpc_plugin_parse_exec(name + 1, fds[0]);
 
                if (!plugin)
                        goto out;
@@ -328,6 +397,35 @@ out:
        }
 }
 
+
+static LIST_HEAD(plugins);
+
+static const struct rpc_daemon_ops ops = {
+       .access = rpc_session_access,
+       .exec   = rpc_exec,
+};
+
+static int
+rpc_plugin_register_library(struct ubus_context *ctx, const char *path)
+{
+       struct rpc_plugin *p;
+       void *dlh;
+
+       dlh = dlopen(path, RTLD_LAZY | RTLD_GLOBAL);
+
+       if (!dlh)
+               return UBUS_STATUS_UNKNOWN_ERROR;
+
+       p = dlsym(dlh, "rpc_plugin");
+
+       if (!p)
+               return UBUS_STATUS_NOT_FOUND;
+
+       list_add(&p->list, &plugins);
+
+       return p->init(&ops, ctx);
+}
+
 int rpc_plugin_api_init(struct ubus_context *ctx)
 {
        DIR *d;
@@ -336,22 +434,37 @@ int rpc_plugin_api_init(struct ubus_context *ctx)
        struct dirent *e;
        char path[PATH_MAX];
 
-       d = opendir(RPC_PLUGIN_DIRECTORY);
+       if ((d = opendir(RPC_PLUGIN_DIRECTORY)) != NULL)
+       {
+               while ((e = readdir(d)) != NULL)
+               {
+                       snprintf(path, sizeof(path) - 1,
+                                RPC_PLUGIN_DIRECTORY "/%s", e->d_name);
 
-       if (!d)
-               return UBUS_STATUS_NOT_FOUND;
+                       if (stat(path, &s) || !S_ISREG(s.st_mode) || !(s.st_mode & S_IXUSR))
+                               continue;
 
-       while ((e = readdir(d)) != NULL)
+                       rv |= rpc_plugin_register_exec(ctx, path);
+               }
+
+               closedir(d);
+       }
+
+       if ((d = opendir(RPC_LIBRARY_DIRECTORY)) != NULL)
        {
-               snprintf(path, sizeof(path) - 1, RPC_PLUGIN_DIRECTORY "/%s", e->d_name);
+               while ((e = readdir(d)) != NULL)
+               {
+                       snprintf(path, sizeof(path) - 1,
+                                RPC_LIBRARY_DIRECTORY "/%s", e->d_name);
 
-               if (stat(path, &s) || !S_ISREG(s.st_mode) || !(s.st_mode & S_IXUSR))
-                       continue;
+                       if (stat(path, &s) || !S_ISREG(s.st_mode))
+                               continue;
 
-               rv |= rpc_plugin_register(ctx, path);
-       }
+                       rv |= rpc_plugin_register_library(ctx, path);
+               }
 
-       closedir(d);
+               closedir(d);
+       }
 
        return rv;
 }