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