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