improve validation, accept more characters in the section type
[project/uci.git] / history.c
1 /*
2 * libuci - Library for the Unified Configuration Interface
3 * Copyright (C) 2008 Felix Fietkau <nbd@openwrt.org>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU Lesser General Public License version 2.1
7 * as published by the Free Software Foundation
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 */
14
15 /*
16 * This file contains the code for handling uci config history files
17 */
18
19 #define _GNU_SOURCE
20 #include <sys/types.h>
21 #include <sys/stat.h>
22 #include <sys/file.h>
23 #include <stdbool.h>
24 #include <unistd.h>
25 #include <fcntl.h>
26 #include <stdio.h>
27 #include <ctype.h>
28
29 int uci_set_savedir(struct uci_context *ctx, const char *dir)
30 {
31 char *sdir;
32
33 UCI_HANDLE_ERR(ctx);
34 UCI_ASSERT(ctx, dir != NULL);
35
36 sdir = uci_strdup(ctx, dir);
37 if (ctx->savedir != uci_savedir)
38 free(ctx->savedir);
39 ctx->savedir = sdir;
40 return 0;
41 }
42
43 int uci_add_history_path(struct uci_context *ctx, const char *dir)
44 {
45 struct uci_element *e;
46
47 UCI_HANDLE_ERR(ctx);
48 UCI_ASSERT(ctx, dir != NULL);
49 e = uci_alloc_generic(ctx, UCI_TYPE_PATH, dir, sizeof(struct uci_element));
50 uci_list_add(&ctx->history_path, &e->list);
51
52 return 0;
53 }
54
55 static inline void uci_parse_history_tuple(struct uci_context *ctx, char **buf, char **package, char **section, char **option, char **value, bool *delete, bool *rename)
56 {
57 if (**buf == '-') {
58 if (delete)
59 *delete = true;
60 *buf += 1;
61 } else if (**buf == '@') {
62 if (rename)
63 *rename = true;
64 *buf += 1;
65 }
66
67 UCI_INTERNAL(uci_parse_tuple, ctx, *buf, package, section, option, value);
68 }
69 static void uci_parse_history_line(struct uci_context *ctx, struct uci_package *p, char *buf)
70 {
71 bool delete = false;
72 bool rename = false;
73 char *package = NULL;
74 char *section = NULL;
75 char *option = NULL;
76 char *value = NULL;
77
78 uci_parse_history_tuple(ctx, &buf, &package, &section, &option, &value, &delete, &rename);
79 if (!package || (strcmp(package, p->e.name) != 0))
80 goto error;
81 if (!uci_validate_name(section))
82 goto error;
83 if (option && !uci_validate_name(option))
84 goto error;
85 if (rename && !uci_validate_str(value, (option || delete)))
86 goto error;
87
88 if (rename)
89 UCI_INTERNAL(uci_rename, ctx, p, section, option, value);
90 else if (delete)
91 UCI_INTERNAL(uci_delete, ctx, p, section, option);
92 else
93 UCI_INTERNAL(uci_set, ctx, p, section, option, value);
94
95 return;
96 error:
97 UCI_THROW(ctx, UCI_ERR_PARSE);
98 }
99
100 static void uci_parse_history(struct uci_context *ctx, FILE *stream, struct uci_package *p)
101 {
102 struct uci_parse_context *pctx;
103
104 /* make sure no memory from previous parse attempts is leaked */
105 ctx->internal = true;
106 uci_cleanup(ctx);
107
108 pctx = (struct uci_parse_context *) uci_malloc(ctx, sizeof(struct uci_parse_context));
109 ctx->pctx = pctx;
110 pctx->file = stream;
111
112 while (!feof(pctx->file)) {
113 uci_getln(ctx, 0);
114 if (!pctx->buf[0])
115 continue;
116
117 /*
118 * ignore parse errors in single lines, we want to preserve as much
119 * history as possible
120 */
121 UCI_TRAP_SAVE(ctx, error);
122 uci_parse_history_line(ctx, p, pctx->buf);
123 UCI_TRAP_RESTORE(ctx);
124 error:
125 continue;
126 }
127
128 /* no error happened, we can get rid of the parser context now */
129 ctx->internal = true;
130 uci_cleanup(ctx);
131 }
132
133 static void uci_load_history_file(struct uci_context *ctx, struct uci_package *p, char *filename, FILE **f, bool flush)
134 {
135 FILE *stream = NULL;
136
137 UCI_TRAP_SAVE(ctx, done);
138 stream = uci_open_stream(ctx, filename, SEEK_SET, flush, false);
139 if (p)
140 uci_parse_history(ctx, stream, p);
141 UCI_TRAP_RESTORE(ctx);
142 done:
143 if (f)
144 *f = stream;
145 else if (stream)
146 uci_close_stream(stream);
147 }
148
149 static void uci_load_history(struct uci_context *ctx, struct uci_package *p, bool flush)
150 {
151 struct uci_element *e;
152 char *filename = NULL;
153 FILE *f = NULL;
154
155 if (!p->confdir)
156 return;
157
158 uci_foreach_element(&ctx->history_path, e) {
159 if ((asprintf(&filename, "%s/%s", e->name, p->e.name) < 0) || !filename)
160 UCI_THROW(ctx, UCI_ERR_MEM);
161
162 uci_load_history_file(ctx, p, filename, NULL, false);
163 free(filename);
164 }
165
166 if ((asprintf(&filename, "%s/%s", ctx->savedir, p->e.name) < 0) || !filename)
167 UCI_THROW(ctx, UCI_ERR_MEM);
168
169 uci_load_history_file(ctx, p, filename, &f, flush);
170 if (flush && f) {
171 rewind(f);
172 ftruncate(fileno(f), 0);
173 }
174 if (filename)
175 free(filename);
176 uci_close_stream(f);
177 ctx->errno = 0;
178 }
179
180 static void uci_filter_history(struct uci_context *ctx, const char *name, char *section, char *option)
181 {
182 struct uci_parse_context *pctx;
183 struct uci_element *e, *tmp;
184 struct uci_list list;
185 char *filename = NULL;
186 char *p = NULL;
187 char *s = NULL;
188 char *o = NULL;
189 char *v = NULL;
190 FILE *f = NULL;
191
192 uci_list_init(&list);
193 uci_alloc_parse_context(ctx);
194 pctx = ctx->pctx;
195
196 if ((asprintf(&filename, "%s/%s", ctx->savedir, name) < 0) || !filename)
197 UCI_THROW(ctx, UCI_ERR_MEM);
198
199 UCI_TRAP_SAVE(ctx, done);
200 f = uci_open_stream(ctx, filename, SEEK_SET, true, false);
201 pctx->file = f;
202 while (!feof(f)) {
203 struct uci_element *e;
204 char *buf;
205
206 uci_getln(ctx, 0);
207 buf = pctx->buf;
208 if (!buf[0])
209 continue;
210
211 /* NB: need to allocate the element before the call to
212 * uci_parse_history_tuple, otherwise the original string
213 * gets modified before it is saved */
214 e = uci_alloc_generic(ctx, UCI_TYPE_HISTORY, pctx->buf, sizeof(struct uci_element));
215 uci_list_add(&list, &e->list);
216
217 uci_parse_history_tuple(ctx, &buf, &p, &s, &o, &v, NULL, NULL);
218 if (section) {
219 if (!s || (strcmp(section, s) != 0))
220 continue;
221 }
222 if (option) {
223 if (!o || (strcmp(option, o) != 0))
224 continue;
225 }
226 /* match, drop this element again */
227 uci_free_element(e);
228 }
229
230 /* rebuild the history file */
231 rewind(f);
232 ftruncate(fileno(f), 0);
233 uci_foreach_element_safe(&list, tmp, e) {
234 fprintf(f, "%s\n", e->name);
235 uci_free_element(e);
236 }
237 UCI_TRAP_RESTORE(ctx);
238
239 done:
240 if (filename)
241 free(filename);
242 uci_close_stream(f);
243 uci_foreach_element_safe(&list, tmp, e) {
244 uci_free_element(e);
245 }
246 ctx->internal = true;
247 uci_cleanup(ctx);
248 }
249
250 int uci_revert(struct uci_context *ctx, struct uci_package **pkg, char *section, char *option)
251 {
252 struct uci_package *p;
253 char *name = NULL;
254
255 UCI_HANDLE_ERR(ctx);
256 UCI_ASSERT(ctx, pkg != NULL);
257 p = *pkg;
258 UCI_ASSERT(ctx, p != NULL);
259 UCI_ASSERT(ctx, p->confdir);
260
261 /*
262 * - flush unwritten changes
263 * - save the package name
264 * - unload the package
265 * - filter the history
266 * - reload the package
267 */
268 UCI_TRAP_SAVE(ctx, error);
269 UCI_INTERNAL(uci_save, ctx, p);
270 name = uci_strdup(ctx, p->e.name);
271
272 *pkg = NULL;
273 uci_free_package(&p);
274 uci_filter_history(ctx, name, section, option);
275
276 UCI_INTERNAL(uci_load, ctx, name, &p);
277 UCI_TRAP_RESTORE(ctx);
278 ctx->errno = 0;
279
280 error:
281 if (name)
282 free(name);
283 if (ctx->errno)
284 UCI_THROW(ctx, ctx->errno);
285 return 0;
286 }
287
288 int uci_save(struct uci_context *ctx, struct uci_package *p)
289 {
290 FILE *f = NULL;
291 char *filename = NULL;
292 struct uci_element *e, *tmp;
293
294 UCI_HANDLE_ERR(ctx);
295 UCI_ASSERT(ctx, p != NULL);
296
297 /*
298 * if the config file was outside of the /etc/config path,
299 * don't save the history to a file, update the real file
300 * directly.
301 * does not modify the uci_package pointer
302 */
303 if (!p->confdir)
304 return uci_commit(ctx, &p, false);
305
306 if (uci_list_empty(&p->history))
307 return 0;
308
309 if ((asprintf(&filename, "%s/%s", ctx->savedir, p->e.name) < 0) || !filename)
310 UCI_THROW(ctx, UCI_ERR_MEM);
311
312 ctx->errno = 0;
313 UCI_TRAP_SAVE(ctx, done);
314 f = uci_open_stream(ctx, filename, SEEK_END, true, true);
315 UCI_TRAP_RESTORE(ctx);
316
317 uci_foreach_element_safe(&p->history, tmp, e) {
318 struct uci_history *h = uci_to_history(e);
319
320 if (h->cmd == UCI_CMD_REMOVE)
321 fprintf(f, "-");
322 else if (h->cmd == UCI_CMD_RENAME)
323 fprintf(f, "@");
324
325 fprintf(f, "%s.%s", p->e.name, h->section);
326 if (e->name)
327 fprintf(f, ".%s", e->name);
328
329 if (h->cmd == UCI_CMD_REMOVE)
330 fprintf(f, "\n");
331 else
332 fprintf(f, "=%s\n", h->value);
333 uci_free_history(h);
334 }
335
336 done:
337 uci_close_stream(f);
338 if (filename)
339 free(filename);
340 if (ctx->errno)
341 UCI_THROW(ctx, ctx->errno);
342
343 return 0;
344 }
345
346