diff --git a/CMakeLists.txt b/CMakeLists.txt index 122e850..14289b7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,7 +10,7 @@ IF(APPLE) LINK_DIRECTORIES(/opt/local/lib) ENDIF() -ADD_EXECUTABLE(luci-rpcd main.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) TARGET_LINK_LIBRARIES(luci-rpcd ubox ubus uci iwinfo) SET(CMAKE_INSTALL_PREFIX /usr) diff --git a/exec.c b/exec.c new file mode 100644 index 0000000..5ce7f15 --- /dev/null +++ b/exec.c @@ -0,0 +1,332 @@ +/* + * luci-rpcd - LuCI UBUS RPC server + * + * Copyright (C) 2013 Jo-Philipp Wich + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include "exec.h" + +static int +rpc_errno_status(void) +{ + switch (errno) + { + case EACCES: + return UBUS_STATUS_PERMISSION_DENIED; + + case ENOTDIR: + return UBUS_STATUS_INVALID_ARGUMENT; + + case ENOENT: + return UBUS_STATUS_NOT_FOUND; + + case EINVAL: + return UBUS_STATUS_INVALID_ARGUMENT; + + default: + return UBUS_STATUS_UNKNOWN_ERROR; + } +} + +static const char * +rpc_exec_lookup(const char *cmd) +{ + struct stat s; + int plen = 0, clen = strlen(cmd) + 1; + char *search, *p; + static char path[PATH_MAX]; + + if (!stat(cmd, &s) && S_ISREG(s.st_mode)) + return cmd; + + search = getenv("PATH"); + + if (!search) + search = "/bin:/usr/bin:/sbin:/usr/sbin"; + + p = search; + + do + { + if (*p != ':' && *p != '\0') + continue; + + plen = p - search; + + if ((plen + clen) >= sizeof(path)) + continue; + + strncpy(path, search, plen); + sprintf(path + plen, "/%s", cmd); + + if (!stat(path, &s) && S_ISREG(s.st_mode)) + return path; + + search = p + 1; + } + while (*p++); + + return NULL; +} + + +static void +rpc_ustream_to_blobmsg(struct blob_buf *blob, struct ustream *s, + const char *name) +{ + int len; + char *rbuf, *wbuf; + + if ((len = ustream_pending_data(s, false)) > 0) + { + wbuf = blobmsg_alloc_string_buffer(blob, name, len + 1); + + if (!wbuf) + return; + + ustream_for_each_read_buffer(s, rbuf, len) + { + memcpy(wbuf, rbuf, len); + wbuf += len; + } + + *wbuf = 0; + blobmsg_add_string_buffer(blob); + } +} + +static void +rpc_exec_reply(struct rpc_exec_context *c, int rv) +{ + uloop_timeout_cancel(&c->timeout); + uloop_process_delete(&c->process); + + if (rv == UBUS_STATUS_OK) + { + if (!c->stdout_cb && !c->stderr_cb && !c->finish_cb) + { + blobmsg_add_u32(&c->blob, "code", WEXITSTATUS(c->stat)); + rpc_ustream_to_blobmsg(&c->blob, &c->opipe.stream, "stdout"); + rpc_ustream_to_blobmsg(&c->blob, &c->epipe.stream, "stderr"); + } + + if (c->finish_cb) + c->finish_cb(&c->blob, c->stat, c->priv); + + ubus_send_reply(c->context, &c->request, c->blob.head); + } + + ubus_complete_deferred_request(c->context, &c->request, rv); + + blob_buf_free(&c->blob); + + ustream_free(&c->opipe.stream); + ustream_free(&c->epipe.stream); + + close(c->opipe.fd.fd); + close(c->epipe.fd.fd); + + if (c->priv) + free(c->priv); + + free(c); +} + +static void +rpc_exec_timestdout_cb(struct uloop_timeout *t) +{ + struct rpc_exec_context *c = + container_of(t, struct rpc_exec_context, timeout); + + kill(c->process.pid, SIGKILL); + rpc_exec_reply(c, UBUS_STATUS_TIMEOUT); +} + +static void +rpc_exec_process_cb(struct uloop_process *p, int stat) +{ + struct rpc_exec_context *c = + container_of(p, struct rpc_exec_context, process); + + c->stat = stat; + + ustream_poll(&c->opipe.stream); + ustream_poll(&c->epipe.stream); +} + +static void +rpc_exec_opipe_read_cb(struct ustream *s, int bytes) +{ + int len, rv; + char *buf; + struct rpc_exec_context *c = + container_of(s, struct rpc_exec_context, opipe.stream); + + if (c->stdout_cb) + { + do { + buf = ustream_get_read_buf(s, &len); + + if (!buf || !len) + break; + + rv = c->stdout_cb(&c->blob, buf, len, c->priv); + + if (rv <= 0) + break; + + ustream_consume(s, rv); + } while(1); + } + else if (ustream_read_buf_full(s)) + { + rpc_exec_reply(c, UBUS_STATUS_NOT_SUPPORTED); + } +} + +static void +rpc_exec_epipe_read_cb(struct ustream *s, int bytes) +{ + int len, rv; + char *buf; + struct rpc_exec_context *c = + container_of(s, struct rpc_exec_context, epipe.stream); + + if (c->stderr_cb) + { + do { + buf = ustream_get_read_buf(s, &len); + + if (!buf || !len) + break; + + rv = c->stderr_cb(&c->blob, buf, len, c->priv); + + if (rv <= 0) + break; + + ustream_consume(s, rv); + } while(1); + } + else if (ustream_read_buf_full(s)) + { + rpc_exec_reply(c, UBUS_STATUS_NOT_SUPPORTED); + } +} + +static void +rpc_exec_opipe_state_cb(struct ustream *s) +{ + struct rpc_exec_context *c = + container_of(s, struct rpc_exec_context, opipe.stream); + + if (c->opipe.stream.eof && c->epipe.stream.eof) + rpc_exec_reply(c, UBUS_STATUS_OK); +} + +static void +rpc_exec_epipe_state_cb(struct ustream *s) +{ + struct rpc_exec_context *c = + container_of(s, struct rpc_exec_context, epipe.stream); + + if (c->opipe.stream.eof && c->epipe.stream.eof) + rpc_exec_reply(c, UBUS_STATUS_OK); +} + +int +rpc_exec(const char **args, rpc_exec_read_cb_t out, rpc_exec_read_cb_t err, + rpc_exec_done_cb_t end, void *priv, struct ubus_context *ctx, + struct ubus_request_data *req) +{ + pid_t pid; + + int opipe[2]; + int epipe[2]; + + const char *cmd; + struct rpc_exec_context *c; + + cmd = rpc_exec_lookup(args[0]); + + if (!cmd) + return UBUS_STATUS_NOT_FOUND; + + c = malloc(sizeof(*c)); + + if (!c) + return UBUS_STATUS_UNKNOWN_ERROR; + + if (pipe(opipe) || pipe(epipe)) + return rpc_errno_status(); + + switch ((pid = fork())) + { + case -1: + return rpc_errno_status(); + + case 0: + uloop_done(); + + dup2(opipe[1], 1); + dup2(epipe[1], 2); + + close(0); + close(opipe[0]); + close(opipe[1]); + close(epipe[0]); + close(epipe[1]); + + if (execv(cmd, (char * const *)args)) + return rpc_errno_status(); + + default: + memset(c, 0, sizeof(*c)); + blob_buf_init(&c->blob, 0); + + c->stdout_cb = out; + c->stderr_cb = err; + c->finish_cb = end; + c->priv = priv; + + ustream_declare(c->opipe, opipe[0], opipe); + ustream_declare(c->epipe, epipe[0], epipe); + + c->process.pid = pid; + c->process.cb = rpc_exec_process_cb; + uloop_process_add(&c->process); + + c->timeout.cb = rpc_exec_timestdout_cb; + uloop_timeout_set(&c->timeout, RPC_EXEC_MAX_RUNTIME); + + close(opipe[1]); + close(epipe[1]); + + c->context = ctx; + ubus_defer_request(ctx, req, &c->request); + } + + return UBUS_STATUS_OK; +} diff --git a/exec.h b/exec.h new file mode 100644 index 0000000..0955444 --- /dev/null +++ b/exec.h @@ -0,0 +1,70 @@ +/* + * luci-rpcd - LuCI UBUS RPC server + * + * Copyright (C) 2013 Jo-Philipp Wich + * + * 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_EXEC_H +#define __RPC_EXEC_H + +#include +#include +#include + +#define RPC_EXEC_MAX_SIZE (4096 * 64) +#define RPC_EXEC_MAX_RUNTIME (3 * 1000) + +#define ustream_for_each_read_buffer(stream, ptr, len) \ + for (ptr = ustream_get_read_buf(stream, &len); \ + ptr != NULL && len > 0; \ + ustream_consume(stream, len), ptr = ustream_get_read_buf(stream, &len)) + +#define ustream_declare(us, fd, name) \ + us.stream.string_data = true; \ + us.stream.r.buffer_len = 4096; \ + us.stream.r.max_buffers = RPC_EXEC_MAX_SIZE / 4096; \ + us.stream.notify_read = rpc_exec_##name##_read_cb; \ + us.stream.notify_state = rpc_exec_##name##_state_cb; \ + ustream_fd_init(&us, fd); + +typedef int (*rpc_exec_read_cb_t)(struct blob_buf *, char *, int, void *); +typedef void (*rpc_exec_done_cb_t)(struct blob_buf *, int, void *); + +struct rpc_exec_context { + struct ubus_context *context; + struct ubus_request_data request; + struct uloop_timeout timeout; + struct uloop_process process; + struct ustream_fd opipe; + struct ustream_fd epipe; + int outlen; + char *out; + int errlen; + char *err; + int stat; + void *priv; + bool blob_array; + void *blob_cookie; + struct blob_buf blob; + rpc_exec_read_cb_t stdout_cb; + rpc_exec_read_cb_t stderr_cb; + rpc_exec_done_cb_t finish_cb; +}; + +int rpc_exec(const char **args, rpc_exec_read_cb_t out, rpc_exec_read_cb_t err, + rpc_exec_done_cb_t end, void *priv, struct ubus_context *ctx, + struct ubus_request_data *req); + +#endif