another one
[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 void
31 uci_add_history(struct uci_context *ctx, struct uci_list *list, int cmd, const char *section, const char *option, const 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 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 uci_cleanup(ctx);
166
167 pctx = (struct uci_parse_context *) uci_malloc(ctx, sizeof(struct uci_parse_context));
168 ctx->pctx = pctx;
169 pctx->file = stream;
170
171 while (!feof(pctx->file)) {
172 uci_getln(ctx, 0);
173 if (!pctx->buf[0])
174 continue;
175
176 /*
177 * ignore parse errors in single lines, we want to preserve as much
178 * history as possible
179 */
180 UCI_TRAP_SAVE(ctx, error);
181 uci_parse_history_line(ctx, p, pctx->buf);
182 UCI_TRAP_RESTORE(ctx);
183 changes++;
184 error:
185 continue;
186 }
187
188 /* no error happened, we can get rid of the parser context now */
189 uci_cleanup(ctx);
190 return changes;
191 }
192
193 /* returns the number of changes that were successfully parsed */
194 static int uci_load_history_file(struct uci_context *ctx, struct uci_package *p, char *filename, FILE **f, bool flush)
195 {
196 FILE *stream = NULL;
197 int changes = 0;
198
199 UCI_TRAP_SAVE(ctx, done);
200 stream = uci_open_stream(ctx, filename, SEEK_SET, flush, false);
201 if (p)
202 changes = uci_parse_history(ctx, stream, p);
203 UCI_TRAP_RESTORE(ctx);
204 done:
205 if (f)
206 *f = stream;
207 else if (stream)
208 uci_close_stream(stream);
209 return changes;
210 }
211
212 /* returns the number of changes that were successfully parsed */
213 static int uci_load_history(struct uci_context *ctx, struct uci_package *p, bool flush)
214 {
215 struct uci_element *e;
216 char *filename = NULL;
217 FILE *f = NULL;
218 int changes = 0;
219
220 if (!p->has_history)
221 return 0;
222
223 uci_foreach_element(&ctx->history_path, e) {
224 if ((asprintf(&filename, "%s/%s", e->name, p->e.name) < 0) || !filename)
225 UCI_THROW(ctx, UCI_ERR_MEM);
226
227 uci_load_history_file(ctx, p, filename, NULL, false);
228 free(filename);
229 }
230
231 if ((asprintf(&filename, "%s/%s", ctx->savedir, p->e.name) < 0) || !filename)
232 UCI_THROW(ctx, UCI_ERR_MEM);
233
234 changes = uci_load_history_file(ctx, p, filename, &f, flush);
235 if (flush && f && (changes > 0)) {
236 rewind(f);
237 ftruncate(fileno(f), 0);
238 }
239 if (filename)
240 free(filename);
241 uci_close_stream(f);
242 ctx->err = 0;
243 return changes;
244 }
245
246 static void uci_filter_history(struct uci_context *ctx, const char *name, const char *section, const char *option)
247 {
248 struct uci_parse_context *pctx;
249 struct uci_element *e, *tmp;
250 struct uci_list list;
251 char *filename = NULL;
252 char *p = NULL;
253 char *s = NULL;
254 char *o = NULL;
255 char *v = NULL;
256 FILE *f = NULL;
257
258 uci_list_init(&list);
259 uci_alloc_parse_context(ctx);
260 pctx = ctx->pctx;
261
262 if ((asprintf(&filename, "%s/%s", ctx->savedir, name) < 0) || !filename)
263 UCI_THROW(ctx, UCI_ERR_MEM);
264
265 UCI_TRAP_SAVE(ctx, done);
266 f = uci_open_stream(ctx, filename, SEEK_SET, true, false);
267 pctx->file = f;
268 while (!feof(f)) {
269 struct uci_element *e;
270 char *buf;
271
272 uci_getln(ctx, 0);
273 buf = pctx->buf;
274 if (!buf[0])
275 continue;
276
277 /* NB: need to allocate the element before the call to
278 * uci_parse_history_tuple, otherwise the original string
279 * gets modified before it is saved */
280 e = uci_alloc_generic(ctx, UCI_TYPE_HISTORY, pctx->buf, sizeof(struct uci_element));
281 uci_list_add(&list, &e->list);
282
283 uci_parse_history_tuple(ctx, &buf, &p, &s, &o, &v, NULL);
284 if (section) {
285 if (!s || (strcmp(section, s) != 0))
286 continue;
287 }
288 if (option) {
289 if (!o || (strcmp(option, o) != 0))
290 continue;
291 }
292 /* match, drop this element again */
293 uci_free_element(e);
294 }
295
296 /* rebuild the history file */
297 rewind(f);
298 ftruncate(fileno(f), 0);
299 uci_foreach_element_safe(&list, tmp, e) {
300 fprintf(f, "%s\n", e->name);
301 uci_free_element(e);
302 }
303 UCI_TRAP_RESTORE(ctx);
304
305 done:
306 if (filename)
307 free(filename);
308 uci_close_stream(f);
309 uci_foreach_element_safe(&list, tmp, e) {
310 uci_free_element(e);
311 }
312 uci_cleanup(ctx);
313 }
314
315 int uci_revert(struct uci_context *ctx, struct uci_package **pkg, const char *section, const char *option)
316 {
317 struct uci_package *p;
318 char *name = NULL;
319
320 UCI_HANDLE_ERR(ctx);
321 UCI_ASSERT(ctx, pkg != NULL);
322 p = *pkg;
323 UCI_ASSERT(ctx, p != NULL);
324 UCI_ASSERT(ctx, p->has_history);
325
326 /*
327 * - flush unwritten changes
328 * - save the package name
329 * - unload the package
330 * - filter the history
331 * - reload the package
332 */
333 UCI_TRAP_SAVE(ctx, error);
334 UCI_INTERNAL(uci_save, ctx, p);
335 name = uci_strdup(ctx, p->e.name);
336
337 *pkg = NULL;
338 uci_free_package(&p);
339 uci_filter_history(ctx, name, section, option);
340
341 UCI_INTERNAL(uci_load, ctx, name, &p);
342 UCI_TRAP_RESTORE(ctx);
343 ctx->err = 0;
344
345 error:
346 if (name)
347 free(name);
348 if (ctx->err)
349 UCI_THROW(ctx, ctx->err);
350 return 0;
351 }
352
353 int uci_save(struct uci_context *ctx, struct uci_package *p)
354 {
355 FILE *f = NULL;
356 char *filename = NULL;
357 struct uci_element *e, *tmp;
358 struct stat statbuf;
359
360 UCI_HANDLE_ERR(ctx);
361 UCI_ASSERT(ctx, p != NULL);
362
363 /*
364 * if the config file was outside of the /etc/config path,
365 * don't save the history to a file, update the real file
366 * directly.
367 * does not modify the uci_package pointer
368 */
369 if (!p->has_history)
370 return uci_commit(ctx, &p, false);
371
372 if (uci_list_empty(&p->history))
373 return 0;
374
375 if (stat(ctx->savedir, &statbuf) < 0)
376 mkdir(ctx->savedir, UCI_DIRMODE);
377 else if ((statbuf.st_mode & S_IFMT) != S_IFDIR)
378 UCI_THROW(ctx, UCI_ERR_IO);
379
380 if ((asprintf(&filename, "%s/%s", ctx->savedir, p->e.name) < 0) || !filename)
381 UCI_THROW(ctx, UCI_ERR_MEM);
382
383 ctx->err = 0;
384 UCI_TRAP_SAVE(ctx, done);
385 f = uci_open_stream(ctx, filename, SEEK_END, true, true);
386 UCI_TRAP_RESTORE(ctx);
387
388 uci_foreach_element_safe(&p->history, tmp, e) {
389 struct uci_history *h = uci_to_history(e);
390
391 switch(h->cmd) {
392 case UCI_CMD_REMOVE:
393 fprintf(f, "-");
394 break;
395 case UCI_CMD_RENAME:
396 fprintf(f, "@");
397 break;
398 case UCI_CMD_ADD:
399 fprintf(f, "+");
400 break;
401 default:
402 break;
403 }
404
405 fprintf(f, "%s.%s", p->e.name, h->section);
406 if (e->name)
407 fprintf(f, ".%s", e->name);
408
409 if (h->cmd == UCI_CMD_REMOVE)
410 fprintf(f, "\n");
411 else
412 fprintf(f, "=%s\n", h->value);
413 uci_free_history(h);
414 }
415
416 done:
417 uci_close_stream(f);
418 if (filename)
419 free(filename);
420 if (ctx->err)
421 UCI_THROW(ctx, ctx->err);
422
423 return 0;
424 }
425
426