14c650ab30383fa5fee6d9ef1d4a33122a882342
[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 /* record a change that was done to a package */
30 static void
31 uci_add_history(struct uci_context *ctx, struct uci_list *list, int cmd, char *section, char *option, char *value)
32 {
33 struct uci_history *h;
34 int size = strlen(section) + 1;
35 char *ptr;
36
37 if (value)
38 size += strlen(value) + 1;
39
40 h = uci_alloc_element(ctx, history, option, size);
41 ptr = uci_dataptr(h);
42 h->cmd = cmd;
43 h->section = strcpy(ptr, section);
44 if (value) {
45 ptr += strlen(ptr) + 1;
46 h->value = strcpy(ptr, value);
47 }
48 uci_list_add(list, &h->e.list);
49 }
50
51 static void
52 uci_free_history(struct uci_history *h)
53 {
54 if (!h)
55 return;
56 if ((h->section != NULL) &&
57 (h->section != uci_dataptr(h))) {
58 free(h->section);
59 free(h->value);
60 }
61 uci_free_element(&h->e);
62 }
63
64
65 int uci_set_savedir(struct uci_context *ctx, const char *dir)
66 {
67 char *sdir;
68
69 UCI_HANDLE_ERR(ctx);
70 UCI_ASSERT(ctx, dir != NULL);
71
72 sdir = uci_strdup(ctx, dir);
73 if (ctx->savedir != uci_savedir)
74 free(ctx->savedir);
75 ctx->savedir = sdir;
76 return 0;
77 }
78
79 int uci_add_history_path(struct uci_context *ctx, const char *dir)
80 {
81 struct uci_element *e;
82
83 UCI_HANDLE_ERR(ctx);
84 UCI_ASSERT(ctx, dir != NULL);
85 e = uci_alloc_generic(ctx, UCI_TYPE_PATH, dir, sizeof(struct uci_element));
86 uci_list_add(&ctx->history_path, &e->list);
87
88 return 0;
89 }
90
91 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)
92 {
93 if (**buf == '-') {
94 if (delete)
95 *delete = true;
96 *buf += 1;
97 } else if (**buf == '@') {
98 if (rename)
99 *rename = true;
100 *buf += 1;
101 }
102
103 UCI_INTERNAL(uci_parse_tuple, ctx, *buf, package, section, option, value);
104 }
105 static void uci_parse_history_line(struct uci_context *ctx, struct uci_package *p, char *buf)
106 {
107 bool delete = false;
108 bool rename = false;
109 char *package = NULL;
110 char *section = NULL;
111 char *option = NULL;
112 char *value = NULL;
113
114 uci_parse_history_tuple(ctx, &buf, &package, &section, &option, &value, &delete, &rename);
115 if (!package || (strcmp(package, p->e.name) != 0))
116 goto error;
117 if (!uci_validate_name(section))
118 goto error;
119 if (option && !uci_validate_name(option))
120 goto error;
121 if (rename && !uci_validate_str(value, (option || delete)))
122 goto error;
123
124 if (ctx->flags & UCI_FLAG_SAVED_HISTORY) {
125 int cmd;
126
127 /* NB: no distinction between CMD_CHANGE and CMD_ADD possible at this point */
128 if(delete)
129 cmd = UCI_CMD_REMOVE;
130 else if (rename)
131 cmd = UCI_CMD_RENAME;
132 else
133 cmd = UCI_CMD_CHANGE;
134
135 uci_add_history(ctx, &p->saved_history, cmd, section, option, value);
136 }
137
138 if (rename)
139 UCI_INTERNAL(uci_rename, ctx, p, section, option, value);
140 else if (delete)
141 UCI_INTERNAL(uci_delete, ctx, p, section, option);
142 else
143 UCI_INTERNAL(uci_set, ctx, p, section, option, value, NULL);
144
145 return;
146 error:
147 UCI_THROW(ctx, UCI_ERR_PARSE);
148 }
149
150 /* returns the number of changes that were successfully parsed */
151 static int uci_parse_history(struct uci_context *ctx, FILE *stream, struct uci_package *p)
152 {
153 struct uci_parse_context *pctx;
154 int changes = 0;
155
156 /* make sure no memory from previous parse attempts is leaked */
157 ctx->internal = true;
158 uci_cleanup(ctx);
159
160 pctx = (struct uci_parse_context *) uci_malloc(ctx, sizeof(struct uci_parse_context));
161 ctx->pctx = pctx;
162 pctx->file = stream;
163
164 while (!feof(pctx->file)) {
165 uci_getln(ctx, 0);
166 if (!pctx->buf[0])
167 continue;
168
169 /*
170 * ignore parse errors in single lines, we want to preserve as much
171 * history as possible
172 */
173 UCI_TRAP_SAVE(ctx, error);
174 uci_parse_history_line(ctx, p, pctx->buf);
175 UCI_TRAP_RESTORE(ctx);
176 changes++;
177 error:
178 continue;
179 }
180
181 /* no error happened, we can get rid of the parser context now */
182 ctx->internal = true;
183 uci_cleanup(ctx);
184 return changes;
185 }
186
187 /* returns the number of changes that were successfully parsed */
188 static int uci_load_history_file(struct uci_context *ctx, struct uci_package *p, char *filename, FILE **f, bool flush)
189 {
190 FILE *stream = NULL;
191 int changes = 0;
192
193 UCI_TRAP_SAVE(ctx, done);
194 stream = uci_open_stream(ctx, filename, SEEK_SET, flush, false);
195 if (p)
196 changes = uci_parse_history(ctx, stream, p);
197 UCI_TRAP_RESTORE(ctx);
198 done:
199 if (f)
200 *f = stream;
201 else if (stream)
202 uci_close_stream(stream);
203 return changes;
204 }
205
206 /* returns the number of changes that were successfully parsed */
207 static int uci_load_history(struct uci_context *ctx, struct uci_package *p, bool flush)
208 {
209 struct uci_element *e;
210 char *filename = NULL;
211 FILE *f = NULL;
212 int changes = 0;
213
214 if (!p->confdir)
215 return 0;
216
217 uci_foreach_element(&ctx->history_path, e) {
218 if ((asprintf(&filename, "%s/%s", e->name, p->e.name) < 0) || !filename)
219 UCI_THROW(ctx, UCI_ERR_MEM);
220
221 uci_load_history_file(ctx, p, filename, NULL, false);
222 free(filename);
223 }
224
225 if ((asprintf(&filename, "%s/%s", ctx->savedir, p->e.name) < 0) || !filename)
226 UCI_THROW(ctx, UCI_ERR_MEM);
227
228 changes = uci_load_history_file(ctx, p, filename, &f, flush);
229 if (flush && f && (changes > 0)) {
230 rewind(f);
231 ftruncate(fileno(f), 0);
232 }
233 if (filename)
234 free(filename);
235 uci_close_stream(f);
236 ctx->errno = 0;
237 return changes;
238 }
239
240 static void uci_filter_history(struct uci_context *ctx, const char *name, char *section, char *option)
241 {
242 struct uci_parse_context *pctx;
243 struct uci_element *e, *tmp;
244 struct uci_list list;
245 char *filename = NULL;
246 char *p = NULL;
247 char *s = NULL;
248 char *o = NULL;
249 char *v = NULL;
250 FILE *f = NULL;
251
252 uci_list_init(&list);
253 uci_alloc_parse_context(ctx);
254 pctx = ctx->pctx;
255
256 if ((asprintf(&filename, "%s/%s", ctx->savedir, name) < 0) || !filename)
257 UCI_THROW(ctx, UCI_ERR_MEM);
258
259 UCI_TRAP_SAVE(ctx, done);
260 f = uci_open_stream(ctx, filename, SEEK_SET, true, false);
261 pctx->file = f;
262 while (!feof(f)) {
263 struct uci_element *e;
264 char *buf;
265
266 uci_getln(ctx, 0);
267 buf = pctx->buf;
268 if (!buf[0])
269 continue;
270
271 /* NB: need to allocate the element before the call to
272 * uci_parse_history_tuple, otherwise the original string
273 * gets modified before it is saved */
274 e = uci_alloc_generic(ctx, UCI_TYPE_HISTORY, pctx->buf, sizeof(struct uci_element));
275 uci_list_add(&list, &e->list);
276
277 uci_parse_history_tuple(ctx, &buf, &p, &s, &o, &v, NULL, NULL);
278 if (section) {
279 if (!s || (strcmp(section, s) != 0))
280 continue;
281 }
282 if (option) {
283 if (!o || (strcmp(option, o) != 0))
284 continue;
285 }
286 /* match, drop this element again */
287 uci_free_element(e);
288 }
289
290 /* rebuild the history file */
291 rewind(f);
292 ftruncate(fileno(f), 0);
293 uci_foreach_element_safe(&list, tmp, e) {
294 fprintf(f, "%s\n", e->name);
295 uci_free_element(e);
296 }
297 UCI_TRAP_RESTORE(ctx);
298
299 done:
300 if (filename)
301 free(filename);
302 uci_close_stream(f);
303 uci_foreach_element_safe(&list, tmp, e) {
304 uci_free_element(e);
305 }
306 ctx->internal = true;
307 uci_cleanup(ctx);
308 }
309
310 int uci_revert(struct uci_context *ctx, struct uci_package **pkg, char *section, char *option)
311 {
312 struct uci_package *p;
313 char *name = NULL;
314
315 UCI_HANDLE_ERR(ctx);
316 UCI_ASSERT(ctx, pkg != NULL);
317 p = *pkg;
318 UCI_ASSERT(ctx, p != NULL);
319 UCI_ASSERT(ctx, p->confdir);
320
321 /*
322 * - flush unwritten changes
323 * - save the package name
324 * - unload the package
325 * - filter the history
326 * - reload the package
327 */
328 UCI_TRAP_SAVE(ctx, error);
329 UCI_INTERNAL(uci_save, ctx, p);
330 name = uci_strdup(ctx, p->e.name);
331
332 *pkg = NULL;
333 uci_free_package(&p);
334 uci_filter_history(ctx, name, section, option);
335
336 UCI_INTERNAL(uci_load, ctx, name, &p);
337 UCI_TRAP_RESTORE(ctx);
338 ctx->errno = 0;
339
340 error:
341 if (name)
342 free(name);
343 if (ctx->errno)
344 UCI_THROW(ctx, ctx->errno);
345 return 0;
346 }
347
348 int uci_save(struct uci_context *ctx, struct uci_package *p)
349 {
350 FILE *f = NULL;
351 char *filename = NULL;
352 struct uci_element *e, *tmp;
353
354 UCI_HANDLE_ERR(ctx);
355 UCI_ASSERT(ctx, p != NULL);
356
357 /*
358 * if the config file was outside of the /etc/config path,
359 * don't save the history to a file, update the real file
360 * directly.
361 * does not modify the uci_package pointer
362 */
363 if (!p->confdir)
364 return uci_commit(ctx, &p, false);
365
366 if (uci_list_empty(&p->history))
367 return 0;
368
369 if ((asprintf(&filename, "%s/%s", ctx->savedir, p->e.name) < 0) || !filename)
370 UCI_THROW(ctx, UCI_ERR_MEM);
371
372 ctx->errno = 0;
373 UCI_TRAP_SAVE(ctx, done);
374 f = uci_open_stream(ctx, filename, SEEK_END, true, true);
375 UCI_TRAP_RESTORE(ctx);
376
377 uci_foreach_element_safe(&p->history, tmp, e) {
378 struct uci_history *h = uci_to_history(e);
379
380 if (h->cmd == UCI_CMD_REMOVE)
381 fprintf(f, "-");
382 else if (h->cmd == UCI_CMD_RENAME)
383 fprintf(f, "@");
384
385 fprintf(f, "%s.%s", p->e.name, h->section);
386 if (e->name)
387 fprintf(f, ".%s", e->name);
388
389 if (h->cmd == UCI_CMD_REMOVE)
390 fprintf(f, "\n");
391 else
392 fprintf(f, "=%s\n", h->value);
393 uci_free_history(h);
394 }
395
396 done:
397 uci_close_stream(f);
398 if (filename)
399 free(filename);
400 if (ctx->errno)
401 UCI_THROW(ctx, ctx->errno);
402
403 return 0;
404 }
405
406