+ return rpc_uci_revert_commit(ctx, msg, true);
+}
+
+static int
+rpc_uci_configs(struct ubus_context *ctx, struct ubus_object *obj,
+ struct ubus_request_data *req, const char *method,
+ struct blob_attr *msg)
+{
+ char **configs;
+ void *c;
+ int i;
+
+ if (uci_list_configs(cursor, &configs))
+ goto out;
+
+ blob_buf_init(&buf, 0);
+
+ c = blobmsg_open_array(&buf, "configs");
+
+ for (i = 0; configs[i]; i++)
+ blobmsg_add_string(&buf, NULL, configs[i]);
+
+ blobmsg_close_array(&buf, c);
+
+ ubus_send_reply(ctx, req, buf.head);
+
+out:
+ return rpc_uci_status();
+}
+
+
+/*
+ * Remove given delta save directory (if any).
+ */
+static void
+rpc_uci_purge_dir(const char *path)
+{
+ DIR *d;
+ struct stat s;
+ struct dirent *e;
+ char file[PATH_MAX];
+
+ if (stat(path, &s) || !S_ISDIR(s.st_mode))
+ return;
+
+ if ((d = opendir(path)) != NULL)
+ {
+ while ((e = readdir(d)) != NULL)
+ {
+ snprintf(file, sizeof(file) - 1, "%s/%s", path, e->d_name);
+
+ if (stat(file, &s) || !S_ISREG(s.st_mode))
+ continue;
+
+ unlink(file);
+ }
+
+ closedir(d);
+
+ rmdir(path);
+ }
+}
+
+static int
+rpc_uci_apply_config(struct ubus_context *ctx, char *config)
+{
+ struct uci_package *p = NULL;
+ struct uci_ptr ptr = { 0 };
+
+ ptr.package = config;
+ uci_load(cursor, ptr.package, &p);
+
+ if (p) {
+ uci_commit(cursor, &p, false);
+ uci_unload(cursor, p);
+ }
+ rpc_uci_trigger_event(ctx, config);
+
+ return 0;
+}
+
+static void
+rpc_uci_copy_file(const char *src, const char *target, const char *file)
+{
+ char tmp[256];
+ FILE *in, *out;
+
+ snprintf(tmp, sizeof(tmp), "%s%s", src, file);
+ in = fopen(tmp, "rb");
+ snprintf(tmp, sizeof(tmp), "%s%s", target, file);
+ out = fopen(tmp, "wb+");
+ if (in && out)
+ while (!feof(in)) {
+ int len = fread(tmp, 1, sizeof(tmp), in);
+
+ if(len > 0)
+ fwrite(tmp, 1, len, out);
+ }
+ if(in)
+ fclose(in);
+ if(out)
+ fclose(out);
+}
+
+static void
+rpc_uci_do_rollback(struct ubus_context *ctx, const char *sid, glob_t *gl)
+{
+ int i;
+ char tmp[PATH_MAX];
+
+ if (sid) {
+ snprintf(tmp, sizeof(tmp), RPC_UCI_SAVEDIR_PREFIX "%s/", sid);
+ mkdir(tmp, 0700);
+ }
+
+ for (i = 0; i < gl->gl_pathc; i++) {
+ char *config = basename(gl->gl_pathv[i]);
+
+ if (*config == '.')
+ continue;
+
+ rpc_uci_copy_file(RPC_SNAPSHOT_FILES, RPC_UCI_DIR, config);
+ rpc_uci_apply_config(ctx, config);
+ if (sid)
+ rpc_uci_copy_file(RPC_SNAPSHOT_DELTA, tmp, config);
+ }
+
+ rpc_uci_purge_dir(RPC_SNAPSHOT_FILES);
+ rpc_uci_purge_dir(RPC_SNAPSHOT_DELTA);
+
+ uloop_timeout_cancel(&apply_timer);
+ apply_running = false;
+ apply_ctx = NULL;
+}
+
+static void
+rpc_uci_apply_timeout(struct uloop_timeout *t)
+{
+ glob_t gl;
+ char tmp[PATH_MAX];
+
+ snprintf(tmp, sizeof(tmp), "%s/*", RPC_SNAPSHOT_FILES);
+ if (glob(tmp, GLOB_PERIOD, NULL, &gl) < 0)
+ return;
+
+ rpc_uci_do_rollback(apply_ctx, NULL, &gl);
+}
+
+static int
+rpc_uci_apply_access(const char *sid, glob_t *gl)
+{
+ struct stat s;
+ int i, c = 0;
+
+ if (gl->gl_pathc < 3)
+ return UBUS_STATUS_NO_DATA;
+
+ for (i = 0; i < gl->gl_pathc; i++) {
+ char *config = basename(gl->gl_pathv[i]);
+
+ if (*config == '.')
+ continue;
+ if (stat(gl->gl_pathv[i], &s) || !s.st_size)
+ continue;
+ if (!rpc_session_access(sid, "uci", config, "write"))
+ return UBUS_STATUS_PERMISSION_DENIED;
+ c++;
+ }
+
+ if (!c)
+ return UBUS_STATUS_NO_DATA;
+
+ return 0;