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