459d2c7ddfd5d4443c24c02a76952d40319bb871
[project/uci.git] / delta.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 Lesser General Public License for more details.
13  */
14
15 /*
16  * This file contains the code for handling uci config delta 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 #include <string.h>
29 #include <stdlib.h>
30
31 #include "uci.h"
32 #include "uci_internal.h"
33
34 /* record a change that was done to a package */
35 void
36 uci_add_delta(struct uci_context *ctx, struct uci_list *list, int cmd, const char *section, const char *option, const char *value)
37 {
38         struct uci_delta *h;
39         int size = strlen(section) + 1;
40         char *ptr;
41
42         if (value)
43                 size += strlen(value) + 1;
44
45         h = uci_alloc_element(ctx, delta, option, size);
46         ptr = uci_dataptr(h);
47         h->cmd = cmd;
48         h->section = strcpy(ptr, section);
49         if (value) {
50                 ptr += strlen(ptr) + 1;
51                 h->value = strcpy(ptr, value);
52         }
53         uci_list_add(list, &h->e.list);
54 }
55
56 void
57 uci_free_delta(struct uci_delta *h)
58 {
59         if (!h)
60                 return;
61         if ((h->section != NULL) &&
62                 (h->section != uci_dataptr(h))) {
63                 free(h->section);
64                 free(h->value);
65         }
66         uci_free_element(&h->e);
67 }
68
69 static void uci_delta_save(struct uci_context *ctx, FILE *f,
70                         const char *name, const struct uci_delta *h)
71 {
72         const struct uci_element *e = &h->e;
73         char prefix[2] = {0, 0};
74
75         if (h->cmd <= __UCI_CMD_LAST)
76                 prefix[0] = uci_command_char[h->cmd];
77
78         fprintf(f, "%s%s.%s", prefix, name, h->section);
79         if (e->name)
80                 fprintf(f, ".%s", e->name);
81
82         if (h->cmd == UCI_CMD_REMOVE && !h->value)
83                 fprintf(f, "\n");
84         else {
85                 int i;
86
87                 fprintf(f, "='");
88                 for (i = 0; h->value[i]; i++) {
89                         unsigned char c = h->value[i];
90                         if (c != '\'')
91                                 fputc(c, f);
92                         else
93                                 fprintf(f, "'\\''");
94                 }
95                 fprintf(f, "'\n");
96         }
97 }
98
99 int uci_set_savedir(struct uci_context *ctx, const char *dir)
100 {
101         char *sdir;
102         struct uci_element *e, *tmp;
103         bool exists = false;
104
105         UCI_HANDLE_ERR(ctx);
106         UCI_ASSERT(ctx, dir != NULL);
107
108         /* Move dir to the end of ctx->delta_path */
109         uci_foreach_element_safe(&ctx->delta_path, tmp, e) {
110                 if (!strcmp(e->name, dir)) {
111                         exists = true;
112                         uci_list_del(&e->list);
113                         break;
114                 }
115         }
116         if (!exists)
117                 e = uci_alloc_generic(ctx, UCI_TYPE_PATH, dir, sizeof(struct uci_element));
118         uci_list_add(&ctx->delta_path, &e->list);
119
120         sdir = uci_strdup(ctx, dir);
121         if (ctx->savedir != uci_savedir)
122                 free(ctx->savedir);
123         ctx->savedir = sdir;
124         return 0;
125 }
126
127 int uci_add_delta_path(struct uci_context *ctx, const char *dir)
128 {
129         struct uci_element *e;
130         struct uci_list *savedir;
131
132         UCI_HANDLE_ERR(ctx);
133         UCI_ASSERT(ctx, dir != NULL);
134
135         /* Duplicate delta path is not allowed */
136         uci_foreach_element(&ctx->delta_path, e) {
137                 if (!strcmp(e->name, dir))
138                         UCI_THROW(ctx, UCI_ERR_DUPLICATE);
139         }
140
141         e = uci_alloc_generic(ctx, UCI_TYPE_PATH, dir, sizeof(struct uci_element));
142         /* Keep savedir at the end of ctx->delta_path list */
143         savedir = ctx->delta_path.prev;
144         uci_list_insert(savedir->prev, &e->list);
145
146         return 0;
147 }
148
149 char const uci_command_char[] = {
150         [UCI_CMD_ADD] = '+',
151         [UCI_CMD_REMOVE] = '-',
152         [UCI_CMD_CHANGE] = 0,
153         [UCI_CMD_RENAME] = '@',
154         [UCI_CMD_REORDER] = '^',
155         [UCI_CMD_LIST_ADD] = '|',
156         [UCI_CMD_LIST_DEL] = '~'
157 };
158
159 static inline int uci_parse_delta_tuple(struct uci_context *ctx, struct uci_ptr *ptr)
160 {
161         struct uci_parse_context *pctx = ctx->pctx;
162         char *str = pctx_cur_str(pctx), *arg;
163         int c;
164
165         UCI_INTERNAL(uci_parse_argument, ctx, ctx->pctx->file, &str, &arg);
166         for (c = 0; c <= __UCI_CMD_LAST; c++) {
167                 if (uci_command_char[c] == *arg)
168                         break;
169         }
170         if (c > __UCI_CMD_LAST)
171                 c = UCI_CMD_CHANGE;
172
173         if (c != UCI_CMD_CHANGE)
174                 arg += 1;
175
176         UCI_INTERNAL(uci_parse_ptr, ctx, ptr, arg);
177
178         if (!ptr->section)
179                 goto error;
180         if (ptr->flags & UCI_LOOKUP_EXTENDED)
181                 goto error;
182
183         switch(c) {
184         case UCI_CMD_REORDER:
185                 if (!ptr->value || ptr->option)
186                         goto error;
187                 break;
188         case UCI_CMD_RENAME:
189                 if (!ptr->value || !uci_validate_name(ptr->value))
190                         goto error;
191                 break;
192         case UCI_CMD_LIST_ADD:
193                 if (!ptr->option)
194                         goto error;
195         case UCI_CMD_LIST_DEL:
196                 if (!ptr->option)
197                         goto error;
198         }
199
200         return c;
201
202 error:
203         UCI_THROW(ctx, UCI_ERR_INVAL);
204         return 0;
205 }
206
207 static void uci_parse_delta_line(struct uci_context *ctx, struct uci_package *p)
208 {
209         struct uci_element *e = NULL;
210         struct uci_ptr ptr;
211         int cmd;
212
213         cmd = uci_parse_delta_tuple(ctx, &ptr);
214         if (strcmp(ptr.package, p->e.name) != 0)
215                 goto error;
216
217         if (ctx->flags & UCI_FLAG_SAVED_DELTA)
218                 uci_add_delta(ctx, &p->saved_delta, cmd, ptr.section, ptr.option, ptr.value);
219
220         switch(cmd) {
221         case UCI_CMD_REORDER:
222                 uci_expand_ptr(ctx, &ptr, true);
223                 if (!ptr.s)
224                         UCI_THROW(ctx, UCI_ERR_NOTFOUND);
225                 UCI_INTERNAL(uci_reorder_section, ctx, ptr.s, strtoul(ptr.value, NULL, 10));
226                 break;
227         case UCI_CMD_RENAME:
228                 UCI_INTERNAL(uci_rename, ctx, &ptr);
229                 break;
230         case UCI_CMD_REMOVE:
231                 UCI_INTERNAL(uci_delete, ctx, &ptr);
232                 break;
233         case UCI_CMD_LIST_ADD:
234                 UCI_INTERNAL(uci_add_list, ctx, &ptr);
235                 break;
236         case UCI_CMD_LIST_DEL:
237                 UCI_INTERNAL(uci_del_list, ctx, &ptr);
238                 break;
239         case UCI_CMD_ADD:
240         case UCI_CMD_CHANGE:
241                 UCI_INTERNAL(uci_set, ctx, &ptr);
242                 e = ptr.last;
243                 if (!ptr.option && e && (cmd == UCI_CMD_ADD))
244                         uci_to_section(e)->anonymous = true;
245                 break;
246         }
247         return;
248 error:
249         UCI_THROW(ctx, UCI_ERR_PARSE);
250 }
251
252 /* returns the number of changes that were successfully parsed */
253 static int uci_parse_delta(struct uci_context *ctx, FILE *stream, struct uci_package *p)
254 {
255         struct uci_parse_context *pctx;
256         int changes = 0;
257
258         /* make sure no memory from previous parse attempts is leaked */
259         uci_cleanup(ctx);
260
261         pctx = (struct uci_parse_context *) uci_malloc(ctx, sizeof(struct uci_parse_context));
262         ctx->pctx = pctx;
263         pctx->file = stream;
264
265         while (!feof(pctx->file)) {
266                 pctx->pos = 0;
267                 uci_getln(ctx, 0);
268                 if (!pctx->buf[0])
269                         continue;
270
271                 /*
272                  * ignore parse errors in single lines, we want to preserve as much
273                  * delta as possible
274                  */
275                 UCI_TRAP_SAVE(ctx, error);
276                 uci_parse_delta_line(ctx, p);
277                 UCI_TRAP_RESTORE(ctx);
278                 changes++;
279 error:
280                 continue;
281         }
282
283         /* no error happened, we can get rid of the parser context now */
284         uci_cleanup(ctx);
285         return changes;
286 }
287
288 /* returns the number of changes that were successfully parsed */
289 static int uci_load_delta_file(struct uci_context *ctx, struct uci_package *p, char *filename, FILE **f, bool flush)
290 {
291         FILE *stream = NULL;
292         int changes = 0;
293
294         UCI_TRAP_SAVE(ctx, done);
295         stream = uci_open_stream(ctx, filename, NULL, SEEK_SET, flush, false);
296         if (p)
297                 changes = uci_parse_delta(ctx, stream, p);
298         UCI_TRAP_RESTORE(ctx);
299 done:
300         if (f)
301                 *f = stream;
302         else if (stream)
303                 uci_close_stream(stream);
304         return changes;
305 }
306
307 /* returns the number of changes that were successfully parsed */
308 __private int uci_load_delta(struct uci_context *ctx, struct uci_package *p, bool flush)
309 {
310         struct uci_element *e;
311         char *filename = NULL;
312         FILE *f = NULL;
313         int changes = 0;
314
315         if (!p->has_delta)
316                 return 0;
317
318         uci_foreach_element(&ctx->delta_path, e) {
319                 if ((asprintf(&filename, "%s/%s", e->name, p->e.name) < 0) || !filename)
320                         UCI_THROW(ctx, UCI_ERR_MEM);
321
322                 changes += uci_load_delta_file(ctx, p, filename, NULL, false);
323                 free(filename);
324         }
325
326         if ((asprintf(&filename, "%s/%s", ctx->savedir, p->e.name) < 0) || !filename)
327                 UCI_THROW(ctx, UCI_ERR_MEM);
328         UCI_TRAP_SAVE(ctx, done);
329         f = uci_open_stream(ctx, filename, NULL, SEEK_SET, flush, false);
330         UCI_TRAP_RESTORE(ctx);
331
332         if (flush && f && (changes > 0)) {
333                 if (ftruncate(fileno(f), 0) < 0) {
334                         free(filename);
335                         uci_close_stream(f);
336                         UCI_THROW(ctx, UCI_ERR_IO);
337                 }
338         }
339
340 done:
341         free(filename);
342         uci_close_stream(f);
343         ctx->err = 0;
344         return changes;
345 }
346
347 static void uci_filter_delta(struct uci_context *ctx, const char *name, const char *section, const char *option)
348 {
349         struct uci_parse_context *pctx;
350         struct uci_element *e, *tmp;
351         struct uci_list list;
352         char *filename = NULL;
353         struct uci_ptr ptr;
354         FILE *f = NULL;
355
356         uci_list_init(&list);
357         uci_alloc_parse_context(ctx);
358         pctx = ctx->pctx;
359
360         if ((asprintf(&filename, "%s/%s", ctx->savedir, name) < 0) || !filename)
361                 UCI_THROW(ctx, UCI_ERR_MEM);
362
363         UCI_TRAP_SAVE(ctx, done);
364         f = uci_open_stream(ctx, filename, NULL, SEEK_SET, true, false);
365         pctx->file = f;
366         while (!feof(f)) {
367                 enum uci_command c;
368                 bool match;
369
370                 pctx->pos = 0;
371                 uci_getln(ctx, 0);
372                 if (!pctx->buf[0])
373                         continue;
374
375                 c = uci_parse_delta_tuple(ctx, &ptr);
376                 match = true;
377                 if (section) {
378                         if (!ptr.section || (strcmp(section, ptr.section) != 0))
379                                 match = false;
380                 }
381                 if (match && option) {
382                         if (!ptr.option || (strcmp(option, ptr.option) != 0))
383                                 match = false;
384                 }
385
386                 if (!match) {
387                         uci_add_delta(ctx, &list, c,
388                                 ptr.section, ptr.option, ptr.value);
389                 }
390         }
391
392         /* rebuild the delta file */
393         rewind(f);
394         if (ftruncate(fileno(f), 0) < 0)
395                 UCI_THROW(ctx, UCI_ERR_IO);
396         uci_foreach_element_safe(&list, tmp, e) {
397                 struct uci_delta *h = uci_to_delta(e);
398                 uci_delta_save(ctx, f, name, h);
399                 uci_free_delta(h);
400         }
401         UCI_TRAP_RESTORE(ctx);
402
403 done:
404         free(filename);
405         uci_close_stream(pctx->file);
406         uci_foreach_element_safe(&list, tmp, e) {
407                 uci_free_delta(uci_to_delta(e));
408         }
409         uci_cleanup(ctx);
410 }
411
412 int uci_revert(struct uci_context *ctx, struct uci_ptr *ptr)
413 {
414         char *package = NULL;
415         char *section = NULL;
416         char *option = NULL;
417
418         UCI_HANDLE_ERR(ctx);
419         uci_expand_ptr(ctx, ptr, false);
420         UCI_ASSERT(ctx, ptr->p->has_delta);
421
422         /*
423          * - flush unwritten changes
424          * - save the package name
425          * - unload the package
426          * - filter the delta
427          * - reload the package
428          */
429         UCI_TRAP_SAVE(ctx, error);
430         UCI_INTERNAL(uci_save, ctx, ptr->p);
431
432         /* NB: need to clone package, section and option names,
433          * as they may get freed on uci_free_package() */
434         package = uci_strdup(ctx, ptr->p->e.name);
435         if (ptr->section)
436                 section = uci_strdup(ctx, ptr->section);
437         if (ptr->option)
438                 option = uci_strdup(ctx, ptr->option);
439
440         uci_free_package(&ptr->p);
441         uci_filter_delta(ctx, package, section, option);
442
443         UCI_INTERNAL(uci_load, ctx, package, &ptr->p);
444         UCI_TRAP_RESTORE(ctx);
445         ctx->err = 0;
446
447 error:
448         free(package);
449         free(section);
450         free(option);
451         if (ctx->err)
452                 UCI_THROW(ctx, ctx->err);
453         return 0;
454 }
455
456 int uci_save(struct uci_context *ctx, struct uci_package *p)
457 {
458         FILE *f = NULL;
459         char *filename = NULL;
460         struct uci_element *e, *tmp;
461         struct stat statbuf;
462
463         UCI_HANDLE_ERR(ctx);
464         UCI_ASSERT(ctx, p != NULL);
465
466         /*
467          * if the config file was outside of the /etc/config path,
468          * don't save the delta to a file, update the real file
469          * directly.
470          * does not modify the uci_package pointer
471          */
472         if (!p->has_delta)
473                 return uci_commit(ctx, &p, false);
474
475         if (uci_list_empty(&p->delta))
476                 return 0;
477
478         if (stat(ctx->savedir, &statbuf) < 0) {
479                 if (stat(ctx->confdir, &statbuf) == 0) {
480                         mkdir(ctx->savedir, statbuf.st_mode);
481                 } else {
482                         mkdir(ctx->savedir, UCI_DIRMODE);
483                 }
484         } else if ((statbuf.st_mode & S_IFMT) != S_IFDIR) {
485                 UCI_THROW(ctx, UCI_ERR_IO);
486         }
487
488         if ((asprintf(&filename, "%s/%s", ctx->savedir, p->e.name) < 0) || !filename)
489                 UCI_THROW(ctx, UCI_ERR_MEM);
490
491         ctx->err = 0;
492         UCI_TRAP_SAVE(ctx, done);
493         f = uci_open_stream(ctx, filename, NULL, SEEK_END, true, true);
494         UCI_TRAP_RESTORE(ctx);
495
496         uci_foreach_element_safe(&p->delta, tmp, e) {
497                 struct uci_delta *h = uci_to_delta(e);
498                 uci_delta_save(ctx, f, p->e.name, h);
499                 uci_free_delta(h);
500         }
501
502 done:
503         uci_close_stream(f);
504         free(filename);
505         if (ctx->err)
506                 UCI_THROW(ctx, ctx->err);
507
508         return 0;
509 }
510
511