Add initial plugin api support
authorJo-Philipp Wich <jow@openwrt.org>
Fri, 30 Aug 2013 16:31:15 +0000 (18:31 +0200)
committerJo-Philipp Wich <jow@openwrt.org>
Fri, 30 Aug 2013 18:33:23 +0000 (20:33 +0200)
CMakeLists.txt
main.c
plugin.c [new file with mode: 0644]
plugin.h [new file with mode: 0644]

index 6e7cbc5..8ac4643 100644 (file)
@@ -10,7 +10,7 @@ IF(APPLE)
   LINK_DIRECTORIES(/opt/local/lib)
 ENDIF()
 
-ADD_EXECUTABLE(luci-rpcd main.c exec.c session.c file.c uci.c iwinfo.c luci2.c)
+ADD_EXECUTABLE(luci-rpcd main.c exec.c session.c file.c uci.c iwinfo.c luci2.c plugin.c)
 TARGET_LINK_LIBRARIES(luci-rpcd ubox ubus uci iwinfo blobmsg_json)
 
 SET(CMAKE_INSTALL_PREFIX /usr)
diff --git a/main.c b/main.c
index 11a7cdd..c8b6d41 100644 (file)
--- a/main.c
+++ b/main.c
@@ -28,6 +28,7 @@
 #include "uci.h"
 #include "iwinfo.h"
 #include "luci2.h"
+#include "plugin.h"
 
 static struct ubus_context *ctx;
 
@@ -66,6 +67,7 @@ int main(int argc, char **argv)
        rpc_uci_api_init(ctx);
        rpc_iwinfo_api_init(ctx);
        rpc_luci2_api_init(ctx);
+       rpc_plugin_api_init(ctx);
 
        uloop_run();
        ubus_free(ctx);
diff --git a/plugin.c b/plugin.c
new file mode 100644 (file)
index 0000000..26dfda4
--- /dev/null
+++ b/plugin.c
@@ -0,0 +1,357 @@
+/*
+ * luci-rpcd - LuCI UBUS RPC server
+ *
+ *   Copyright (C) 2013 Jo-Philipp Wich <jow@openwrt.org>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "plugin.h"
+
+static struct blob_buf buf;
+
+struct rpc_plugin_lookup_context {
+       uint32_t id;
+       char *name;
+       bool found;
+};
+
+static void
+rpc_plugin_lookup_plugin_cb(struct ubus_context *ctx,
+                            struct ubus_object_data *obj, void *priv)
+{
+       struct rpc_plugin_lookup_context *c = priv;
+
+       if (c->id == obj->id)
+       {
+               c->found = true;
+               sprintf(c->name, "%s", obj->path);
+       }
+}
+
+static bool
+rpc_plugin_lookup_plugin(struct ubus_context *ctx, struct ubus_object *obj,
+                         char *strptr)
+{
+       struct rpc_plugin_lookup_context c = { .id = obj->id, .name = strptr };
+
+       if (ubus_lookup(ctx, NULL, rpc_plugin_lookup_plugin_cb, &c))
+               return false;
+
+       return c.found;
+}
+
+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)
+{
+       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);
+
+       if (!rpc_plugin_lookup_plugin(ctx, obj, plugin))
+               return UBUS_STATUS_NOT_FOUND;
+
+       if (stat(path, &s) || !(s.st_mode & S_IXUSR))
+               return UBUS_STATUS_NOT_FOUND;
+
+       if (pipe(in_fds) || pipe(out_fds))
+               return UBUS_STATUS_UNKNOWN_ERROR;
+
+       switch ((pid = fork()))
+       {
+       case -1:
+               return UBUS_STATUS_UNKNOWN_ERROR;
+
+       case 0:
+               uloop_done();
+
+               fd = open("/dev/null", O_RDWR);
+
+               if (fd > -1)
+               {
+                       dup2(fd, 2);
+
+                       if (fd > 2)
+                               close(fd);
+               }
+
+               dup2(in_fds[0], 0);
+               dup2(out_fds[1], 1);
+
+               close(in_fds[0]);
+               close(in_fds[1]);
+               close(out_fds[0]);
+               close(out_fds[1]);
+
+               if (execl(path, plugin, "call", meth, NULL))
+                       return UBUS_STATUS_UNKNOWN_ERROR;
+
+       default:
+               rv = UBUS_STATUS_NO_DATA;
+
+               if (input)
+               {
+                       write(in_fds[1], input, strlen(input));
+                       free(input);
+               }
+
+               close(in_fds[0]);
+               close(in_fds[1]);
+
+               if (read(out_fds[0], output, sizeof(output) - 1) > 0)
+               {
+                       blob_buf_init(&buf, 0);
+
+                       if (!blobmsg_add_json_from_string(&buf, output))
+                               rv = UBUS_STATUS_INVALID_ARGUMENT;
+
+                       rv = UBUS_STATUS_OK;
+               }
+
+               close(out_fds[0]);
+               close(out_fds[1]);
+
+               waitpid(pid, NULL, 0);
+
+               if (!rv)
+                       ubus_send_reply(ctx, req, buf.head);
+
+               free(meth);
+
+               return rv;
+       }
+}
+
+static bool
+rpc_plugin_parse_signature(struct blob_attr *sig, struct ubus_method *method)
+{
+       int rem, n_attr;
+       enum blobmsg_type type;
+       struct blob_attr *attr;
+       struct blobmsg_policy *policy = NULL;
+
+       if (!sig || blob_id(sig) != BLOBMSG_TYPE_TABLE)
+               return false;
+
+       n_attr = 0;
+
+       blobmsg_for_each_attr(attr, sig, rem)
+               n_attr++;
+
+       if (n_attr)
+       {
+               policy = calloc(n_attr, sizeof(*policy));
+
+               if (!policy)
+                       return false;
+
+               n_attr = 0;
+
+               blobmsg_for_each_attr(attr, sig, rem)
+               {
+                       type = blob_id(attr);
+
+                       if (type == BLOBMSG_TYPE_INT32)
+                       {
+                               switch (blobmsg_get_u32(attr))
+                               {
+                               case 8:
+                                       type = BLOBMSG_TYPE_INT8;
+                                       break;
+
+                               case 16:
+                                       type = BLOBMSG_TYPE_INT16;
+                                       break;
+
+                               case 64:
+                                       type = BLOBMSG_TYPE_INT64;
+                                       break;
+
+                               default:
+                                       type = BLOBMSG_TYPE_INT32;
+                                       break;
+                               }
+                       }
+
+                       policy[n_attr].name = strdup(blobmsg_name(attr));
+                       policy[n_attr].type = type;
+
+                       n_attr++;
+               }
+       }
+
+       method->name = strdup(blobmsg_name(sig));
+       method->handler = rpc_plugin_call;
+       method->policy = policy;
+       method->n_policy = n_attr;
+
+       return true;
+}
+
+static struct ubus_object *
+rpc_plugin_parse_plugin(const char *name, const char *listbuf)
+{
+       int rem, n_method;
+       struct blob_attr *cur;
+       struct ubus_method *methods;
+       struct ubus_object_type *obj_type;
+       struct ubus_object *obj;
+
+       blob_buf_init(&buf, 0);
+
+       if (!blobmsg_add_json_from_string(&buf, listbuf))
+               return NULL;
+
+       n_method = 0;
+
+       blob_for_each_attr(cur, buf.head, rem)
+               n_method++;
+
+       if (!n_method)
+               return NULL;
+
+       methods = calloc(n_method, sizeof(*methods));
+
+       if (!methods)
+               return NULL;
+
+       n_method = 0;
+
+       blob_for_each_attr(cur, buf.head, rem)
+       {
+               if (!rpc_plugin_parse_signature(cur, &methods[n_method]))
+                       continue;
+
+               n_method++;
+       }
+
+       obj = calloc(1, sizeof(*obj));
+
+       if (!obj)
+               return NULL;
+
+       obj_type = calloc(1, sizeof(*obj_type));
+
+       if (!obj_type)
+               return NULL;
+
+       asprintf((char **)&obj_type->name, "luci-rpc-plugin-%s", name);
+       obj_type->methods = methods;
+       obj_type->n_methods = n_method;
+
+       obj->name = strdup(name);
+       obj->type = obj_type;
+       obj->methods = methods;
+       obj->n_methods = n_method;
+
+       return obj;
+}
+
+static int
+rpc_plugin_register(struct ubus_context *ctx, const char *path)
+{
+       pid_t pid;
+       int rv, fd, fds[2];
+       const char *name;
+       char listbuf[4096] = { 0 };
+       struct ubus_object *plugin;
+
+       name = strrchr(path, '/');
+
+       if (!name)
+               return UBUS_STATUS_INVALID_ARGUMENT;
+
+       if (pipe(fds))
+               return UBUS_STATUS_UNKNOWN_ERROR;
+
+       switch ((pid = fork()))
+       {
+       case -1:
+               return UBUS_STATUS_UNKNOWN_ERROR;
+
+       case 0:
+               fd = open("/dev/null", O_RDWR);
+
+               if (fd > -1)
+               {
+                       dup2(fd, 0);
+                       dup2(fd, 2);
+
+                       if (fd > 2)
+                               close(fd);
+               }
+
+               dup2(fds[1], 1);
+
+               close(fds[0]);
+               close(fds[1]);
+
+               if (execl(path, path, "list", NULL))
+                       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);
+
+               if (!plugin)
+                       goto out;
+
+               rv = ubus_add_object(ctx, plugin);
+
+out:
+               close(fds[0]);
+               close(fds[1]);
+               waitpid(pid, NULL, 0);
+
+               return rv;
+       }
+}
+
+int rpc_plugin_api_init(struct ubus_context *ctx)
+{
+       DIR *d;
+       int rv = 0;
+       struct stat s;
+       struct dirent *e;
+       char path[PATH_MAX];
+
+       d = opendir(RPC_PLUGIN_DIRECTORY);
+
+       if (!d)
+               return UBUS_STATUS_NOT_FOUND;
+
+       while ((e = readdir(d)) != NULL)
+       {
+               snprintf(path, sizeof(path) - 1, RPC_PLUGIN_DIRECTORY "/%s", e->d_name);
+
+               if (stat(path, &s) || !S_ISREG(s.st_mode) || !(s.st_mode & S_IXUSR))
+                       continue;
+
+               rv |= rpc_plugin_register(ctx, path);
+       }
+
+       closedir(d);
+
+       return rv;
+}
diff --git a/plugin.h b/plugin.h
new file mode 100644 (file)
index 0000000..932a5cf
--- /dev/null
+++ b/plugin.h
@@ -0,0 +1,41 @@
+/*
+ * luci-rpcd - LuCI UBUS RPC server
+ *
+ *   Copyright (C) 2013 Jo-Philipp Wich <jow@openwrt.org>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef __RPC_PLUGIN_H
+#define __RPC_PLUGIN_H
+
+#define _GNU_SOURCE /* asprintf() */
+
+#include <stdio.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <limits.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/types.h>
+
+#include <libubox/blobmsg_json.h>
+#include <libubus.h>
+
+/* location of plugin executables */
+#define RPC_PLUGIN_DIRECTORY        "/usr/lib/luci-rpcd/plugins"
+
+int rpc_plugin_api_init(struct ubus_context *ctx);
+
+#endif