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