/* * 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; }