diff --git a/file.c b/file.c index 11a8123..d419846 100644 --- a/file.c +++ b/file.c @@ -19,8 +19,12 @@ #include #include #include +#include +#include +#include #include #include +#include #include "file.h" @@ -32,11 +36,26 @@ enum { __RPC_F_MAX, }; -static const struct blobmsg_policy file_policy[__RPC_F_MAX] = { +static const struct blobmsg_policy rpc_file_policy[__RPC_F_MAX] = { [RPC_F_PATH] = { .name = "path", .type = BLOBMSG_TYPE_STRING }, [RPC_F_DATA] = { .name = "data", .type = BLOBMSG_TYPE_STRING }, }; +enum { + RPC_E_CMD, + RPC_E_PARM, + RPC_E_ENV, + RPC_E_MODE, + __RPC_E_MAX, +}; + +static const struct blobmsg_policy rpc_exec_policy[__RPC_E_MAX] = { + [RPC_E_CMD] = { .name = "command", .type = BLOBMSG_TYPE_STRING }, + [RPC_E_PARM] = { .name = "params", .type = BLOBMSG_TYPE_ARRAY }, + [RPC_E_ENV] = { .name = "env", .type = BLOBMSG_TYPE_TABLE }, + [RPC_E_MODE] = { .name = "mode", .type = BLOBMSG_TYPE_STRING }, +}; + static const char *d_types[] = { [DT_BLK] = "block", [DT_CHR] = "char", @@ -76,7 +95,7 @@ rpc_check_path(struct blob_attr *msg, char **path, struct stat *s) { static struct blob_attr *tb[__RPC_F_MAX]; - blobmsg_parse(file_policy, __RPC_F_MAX, tb, blob_data(msg), blob_len(msg)); + blobmsg_parse(rpc_file_policy, __RPC_F_MAX, tb, blob_data(msg), blob_len(msg)); if (!tb[RPC_F_PATH]) { @@ -93,14 +112,14 @@ rpc_check_path(struct blob_attr *msg, char **path, struct stat *s) } static int -rpc_handle_read(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) +rpc_file_read(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) { - int fd, rlen; + int fd, rv, len; char *path; - char buffer[RPC_FILE_MAX_SIZE]; struct stat s; + char *wbuf; if (!rpc_check_path(msg, &path, &s)) return rpc_errno_status(); @@ -111,27 +130,43 @@ rpc_handle_read(struct ubus_context *ctx, struct ubus_object *obj, if ((fd = open(path, O_RDONLY)) < 0) return rpc_errno_status(); - if ((rlen = read(fd, buffer, RPC_FILE_MAX_SIZE-1)) > 0) - buffer[rlen] = 0; - - close(fd); - - if (rlen <= 0) - return UBUS_STATUS_NO_DATA; + /* some sysfs files do not report a length */ + if (s.st_size == 0) + s.st_size = RPC_FILE_MIN_SIZE; blob_buf_init(&buf, 0); - blobmsg_add_string(&buf, "data", buffer); - ubus_send_reply(ctx, req, buf.head); - return 0; + wbuf = blobmsg_alloc_string_buffer(&buf, "data", s.st_size + 1); + + if (!wbuf) + { + rv = UBUS_STATUS_UNKNOWN_ERROR; + goto out; + } + + if ((len = read(fd, wbuf, s.st_size)) <= 0) + { + rv = UBUS_STATUS_NO_DATA; + goto out; + } + + *(wbuf + len) = 0; + blobmsg_add_string_buffer(&buf); + + ubus_send_reply(ctx, req, buf.head); + rv = UBUS_STATUS_OK; + +out: + close(fd); + return rv; } static int -rpc_handle_write(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) +rpc_file_write(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) { - int fd, rv; + int fd; char *path; struct stat s; struct blob_attr **tb; @@ -145,20 +180,16 @@ rpc_handle_write(struct ubus_context *ctx, struct ubus_object *obj, if ((fd = open(path, O_WRONLY)) < 0) return rpc_errno_status(); - rv = write(fd, blobmsg_data(tb[RPC_F_DATA]), blobmsg_data_len(tb[RPC_F_DATA])); - + write(fd, blobmsg_data(tb[RPC_F_DATA]), blobmsg_data_len(tb[RPC_F_DATA])); close(fd); - if (rv <= 0) - return UBUS_STATUS_NO_DATA; - return 0; } static int -rpc_handle_list(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) +rpc_file_list(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) { DIR *fd; void *c, *d; @@ -193,9 +224,9 @@ rpc_handle_list(struct ubus_context *ctx, struct ubus_object *obj, } static int -rpc_handle_stat(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) +rpc_file_stat(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) { int type; char *path; @@ -208,12 +239,12 @@ rpc_handle_stat(struct ubus_context *ctx, struct ubus_object *obj, type = S_ISREG(s.st_mode) ? DT_REG : S_ISDIR(s.st_mode) ? DT_DIR : - S_ISCHR(s.st_mode) ? DT_CHR : - S_ISBLK(s.st_mode) ? DT_BLK : - S_ISFIFO(s.st_mode) ? DT_FIFO : - S_ISLNK(s.st_mode) ? DT_LNK : - S_ISSOCK(s.st_mode) ? DT_SOCK : - DT_UNKNOWN; + S_ISCHR(s.st_mode) ? DT_CHR : + S_ISBLK(s.st_mode) ? DT_BLK : + S_ISFIFO(s.st_mode) ? DT_FIFO : + S_ISLNK(s.st_mode) ? DT_LNK : + S_ISSOCK(s.st_mode) ? DT_SOCK : + DT_UNKNOWN; blobmsg_add_string(&buf, "path", path); blobmsg_add_string(&buf, "type", d_types[type]); @@ -231,14 +262,300 @@ rpc_handle_stat(struct ubus_context *ctx, struct ubus_object *obj, return 0; } +static const char * +rpc_file_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 ustream *s, const char *name) +{ + int len; + char *rbuf, *wbuf; + + if ((len = ustream_pending_data(s, false)) > 0) + { + wbuf = blobmsg_alloc_string_buffer(&buf, 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(&buf); + } +} + +static void +rpc_file_exec_reply(struct rpc_file_exec_context *c, int rv) +{ + uloop_timeout_cancel(&c->timeout); + uloop_process_delete(&c->process); + + if (rv == UBUS_STATUS_OK) + { + blob_buf_init(&buf, 0); + + blobmsg_add_u32(&buf, "code", WEXITSTATUS(c->stat)); + + rpc_ustream_to_blobmsg(&c->opipe.stream, "stdout"); + rpc_ustream_to_blobmsg(&c->epipe.stream, "stderr"); + + ubus_send_reply(c->context, &c->request, buf.head); + } + + ubus_complete_deferred_request(c->context, &c->request, rv); + + ustream_free(&c->opipe.stream); + ustream_free(&c->epipe.stream); + + close(c->opipe.fd.fd); + close(c->epipe.fd.fd); + + free(c); +} + +static void +rpc_file_exec_timeout_cb(struct uloop_timeout *t) +{ + struct rpc_file_exec_context *c = + container_of(t, struct rpc_file_exec_context, timeout); + + kill(c->process.pid, SIGKILL); + rpc_file_exec_reply(c, UBUS_STATUS_TIMEOUT); +} + +static void +rpc_file_exec_process_cb(struct uloop_process *p, int stat) +{ + struct rpc_file_exec_context *c = + container_of(p, struct rpc_file_exec_context, process); + + c->stat = stat; + + ustream_poll(&c->opipe.stream); + ustream_poll(&c->epipe.stream); +} + +static void +rpc_file_exec_opipe_read_cb(struct ustream *s, int bytes) +{ + struct rpc_file_exec_context *c = + container_of(s, struct rpc_file_exec_context, opipe); + + if (ustream_read_buf_full(s)) + rpc_file_exec_reply(c, UBUS_STATUS_NOT_SUPPORTED); +} + +static void +rpc_file_exec_epipe_read_cb(struct ustream *s, int bytes) +{ + struct rpc_file_exec_context *c = + container_of(s, struct rpc_file_exec_context, epipe); + + if (ustream_read_buf_full(s)) + rpc_file_exec_reply(c, UBUS_STATUS_NOT_SUPPORTED); +} + +static void +rpc_file_exec_opipe_state_cb(struct ustream *s) +{ + struct rpc_file_exec_context *c = + container_of(s, struct rpc_file_exec_context, opipe); + + if (c->opipe.stream.eof && c->epipe.stream.eof) + rpc_file_exec_reply(c, UBUS_STATUS_OK); +} + +static void +rpc_file_exec_epipe_state_cb(struct ustream *s) +{ + struct rpc_file_exec_context *c = + container_of(s, struct rpc_file_exec_context, epipe); + + if (c->opipe.stream.eof && c->epipe.stream.eof) + rpc_file_exec_reply(c, UBUS_STATUS_OK); +} + +static int +rpc_file_exec_run(const char *cmd, + const struct blob_attr *arg, const struct blob_attr *env, + struct ubus_context *ctx, struct ubus_request_data *req) +{ + pid_t pid; + + int opipe[2]; + int epipe[2]; + + int rem; + struct blob_attr *cur; + + char arglen; + char **args; + + struct rpc_file_exec_context *c; + + cmd = rpc_file_exec_lookup(cmd); + + 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]); + + arglen = 2; + args = malloc(sizeof(char *) * arglen); + + if (!args) + return UBUS_STATUS_UNKNOWN_ERROR; + + args[0] = (char *)cmd; + args[1] = NULL; + + if (arg) + { + blobmsg_for_each_attr(cur, arg, rem) + { + if (blobmsg_type(cur) != BLOBMSG_TYPE_STRING) + continue; + + arglen++; + + if (!(args = realloc(args, sizeof(char *) * arglen))) + return UBUS_STATUS_UNKNOWN_ERROR; + + args[arglen-2] = blobmsg_data(cur); + args[arglen-1] = NULL; + } + } + + if (env) + { + blobmsg_for_each_attr(cur, env, rem) + { + if (blobmsg_type(cur) != BLOBMSG_TYPE_STRING) + continue; + + setenv(blobmsg_name(cur), blobmsg_data(cur), 1); + } + } + + if (execv(cmd, args)) + return rpc_errno_status(); + + default: + memset(c, 0, sizeof(*c)); + + ustream_declare(c->opipe, opipe[0], exec_opipe); + ustream_declare(c->epipe, epipe[0], exec_epipe); + + c->process.pid = pid; + c->process.cb = rpc_file_exec_process_cb; + uloop_process_add(&c->process); + + c->timeout.cb = rpc_file_exec_timeout_cb; + uloop_timeout_set(&c->timeout, RPC_FILE_MAX_RUNTIME); + + close(opipe[1]); + close(epipe[1]); + + c->context = ctx; + ubus_defer_request(ctx, req, &c->request); + } + + return UBUS_STATUS_OK; +} + +static int +rpc_file_exec(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + struct blob_attr *tb[__RPC_E_MAX]; + + blobmsg_parse(rpc_exec_policy, __RPC_E_MAX, tb, + blob_data(msg), blob_len(msg)); + + if (!tb[RPC_E_CMD]) + return UBUS_STATUS_INVALID_ARGUMENT; + + return rpc_file_exec_run(blobmsg_data(tb[RPC_E_CMD]), + tb[RPC_E_PARM], tb[RPC_E_ENV], ctx, req); +} + int rpc_file_api_init(struct ubus_context *ctx) { static const struct ubus_method file_methods[] = { - UBUS_METHOD("read", rpc_handle_read, file_policy), - UBUS_METHOD("write", rpc_handle_write, file_policy), - UBUS_METHOD("list", rpc_handle_list, file_policy), - UBUS_METHOD("stat", rpc_handle_stat, file_policy), + UBUS_METHOD("read", rpc_file_read, rpc_file_policy), + UBUS_METHOD("write", rpc_file_write, rpc_file_policy), + UBUS_METHOD("list", rpc_file_list, rpc_file_policy), + UBUS_METHOD("stat", rpc_file_stat, rpc_file_policy), + UBUS_METHOD("exec", rpc_file_exec, rpc_exec_policy), }; static struct ubus_object_type file_type = diff --git a/file.h b/file.h index a00f2b1..c4cb0d8 100644 --- a/file.h +++ b/file.h @@ -21,8 +21,41 @@ #include #include +#include -#define RPC_FILE_MAX_SIZE (1024 * 256) +/* limit of sys & proc files */ +#define RPC_FILE_MIN_SIZE (128) + +/* limit of regular files and command output data */ +#define RPC_FILE_MAX_SIZE (4096 * 64) +#define RPC_FILE_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_FILE_MAX_SIZE / 4096; \ + us.stream.notify_read = rpc_file_##name##_read_cb; \ + us.stream.notify_state = rpc_file_##name##_state_cb; \ + ustream_fd_init(&us, fd); + +struct rpc_file_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; +}; int rpc_file_api_init(struct ubus_context *ctx); diff --git a/main.c b/main.c index 52725cd..2fce5b5 100644 --- a/main.c +++ b/main.c @@ -21,6 +21,7 @@ #include #include +#include #include "session.h" #include "file.h" @@ -42,6 +43,8 @@ int main(int argc, char **argv) } } + signal(SIGPIPE, SIG_IGN); + argc -= optind; argv += optind;