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