3a8b73e6e44db82092c3b2a424360b3029c1a336
[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 int uci_set_savedir(struct uci_context *ctx, const char *dir)
30 {
31         char *sdir;
32
33         UCI_HANDLE_ERR(ctx);
34         UCI_ASSERT(ctx, dir != NULL);
35
36         sdir = uci_strdup(ctx, dir);
37         if (ctx->savedir != uci_savedir)
38                 free(ctx->savedir);
39         ctx->savedir = sdir;
40         return 0;
41 }
42
43 int uci_add_history_path(struct uci_context *ctx, const char *dir)
44 {
45         struct uci_element *e;
46
47         UCI_HANDLE_ERR(ctx);
48         UCI_ASSERT(ctx, dir != NULL);
49         e = uci_alloc_generic(ctx, UCI_TYPE_PATH, dir, sizeof(struct uci_element));
50         uci_list_add(&ctx->history_path, &e->list);
51
52         return 0;
53 }
54
55 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)
56 {
57         if (**buf == '-') {
58                 if (delete)
59                         *delete = true;
60                 *buf += 1;
61         } else if (**buf == '@') {
62                 if (rename)
63                         *rename = true;
64                 *buf += 1;
65         }
66
67         UCI_INTERNAL(uci_parse_tuple, ctx, *buf, package, section, option, value);
68 }
69 static void uci_parse_history_line(struct uci_context *ctx, struct uci_package *p, char *buf)
70 {
71         bool delete = false;
72         bool rename = false;
73         char *package = NULL;
74         char *section = NULL;
75         char *option = NULL;
76         char *value = NULL;
77
78         uci_parse_history_tuple(ctx, &buf, &package, &section, &option, &value, &delete, &rename);
79         if (!package || (strcmp(package, p->e.name) != 0))
80                 goto error;
81         if (!uci_validate_name(section))
82                 goto error;
83         if (option && !uci_validate_name(option))
84                 goto error;
85         if (rename && !uci_validate_str(value, (option || delete)))
86                 goto error;
87
88         if (rename)
89                 UCI_INTERNAL(uci_rename, ctx, p, section, option, value);
90         else if (delete)
91                 UCI_INTERNAL(uci_delete, ctx, p, section, option);
92         else
93                 UCI_INTERNAL(uci_set, ctx, p, section, option, value);
94
95         return;
96 error:
97         UCI_THROW(ctx, UCI_ERR_PARSE);
98 }
99
100 /* returns the number of changes that were successfully parsed */
101 static int uci_parse_history(struct uci_context *ctx, FILE *stream, struct uci_package *p)
102 {
103         struct uci_parse_context *pctx;
104         int changes = 0;
105
106         /* make sure no memory from previous parse attempts is leaked */
107         ctx->internal = true;
108         uci_cleanup(ctx);
109
110         pctx = (struct uci_parse_context *) uci_malloc(ctx, sizeof(struct uci_parse_context));
111         ctx->pctx = pctx;
112         pctx->file = stream;
113
114         while (!feof(pctx->file)) {
115                 uci_getln(ctx, 0);
116                 if (!pctx->buf[0])
117                         continue;
118
119                 /*
120                  * ignore parse errors in single lines, we want to preserve as much
121                  * history as possible
122                  */
123                 UCI_TRAP_SAVE(ctx, error);
124                 uci_parse_history_line(ctx, p, pctx->buf);
125                 UCI_TRAP_RESTORE(ctx);
126                 changes++;
127 error:
128                 continue;
129         }
130
131         /* no error happened, we can get rid of the parser context now */
132         ctx->internal = true;
133         uci_cleanup(ctx);
134         return changes;
135 }
136
137 /* returns the number of changes that were successfully parsed */
138 static int uci_load_history_file(struct uci_context *ctx, struct uci_package *p, char *filename, FILE **f, bool flush)
139 {
140         FILE *stream = NULL;
141         int changes = 0;
142
143         UCI_TRAP_SAVE(ctx, done);
144         stream = uci_open_stream(ctx, filename, SEEK_SET, flush, false);
145         if (p)
146                 changes = uci_parse_history(ctx, stream, p);
147         UCI_TRAP_RESTORE(ctx);
148 done:
149         if (f)
150                 *f = stream;
151         else if (stream)
152                 uci_close_stream(stream);
153         return changes;
154 }
155
156 /* returns the number of changes that were successfully parsed */
157 static int uci_load_history(struct uci_context *ctx, struct uci_package *p, bool flush)
158 {
159         struct uci_element *e;
160         char *filename = NULL;
161         FILE *f = NULL;
162         int changes = 0;
163
164         if (!p->confdir)
165                 return 0;
166
167         uci_foreach_element(&ctx->history_path, e) {
168                 if ((asprintf(&filename, "%s/%s", e->name, p->e.name) < 0) || !filename)
169                         UCI_THROW(ctx, UCI_ERR_MEM);
170
171                 uci_load_history_file(ctx, p, filename, NULL, false);
172                 free(filename);
173         }
174
175         if ((asprintf(&filename, "%s/%s", ctx->savedir, p->e.name) < 0) || !filename)
176                 UCI_THROW(ctx, UCI_ERR_MEM);
177
178         changes = uci_load_history_file(ctx, p, filename, &f, flush);
179         if (flush && f && (changes > 0)) {
180                 rewind(f);
181                 ftruncate(fileno(f), 0);
182         }
183         if (filename)
184                 free(filename);
185         uci_close_stream(f);
186         ctx->errno = 0;
187         return changes;
188 }
189
190 static void uci_filter_history(struct uci_context *ctx, const char *name, char *section, char *option)
191 {
192         struct uci_parse_context *pctx;
193         struct uci_element *e, *tmp;
194         struct uci_list list;
195         char *filename = NULL;
196         char *p = NULL;
197         char *s = NULL;
198         char *o = NULL;
199         char *v = NULL;
200         FILE *f = NULL;
201
202         uci_list_init(&list);
203         uci_alloc_parse_context(ctx);
204         pctx = ctx->pctx;
205
206         if ((asprintf(&filename, "%s/%s", ctx->savedir, name) < 0) || !filename)
207                 UCI_THROW(ctx, UCI_ERR_MEM);
208
209         UCI_TRAP_SAVE(ctx, done);
210         f = uci_open_stream(ctx, filename, SEEK_SET, true, false);
211         pctx->file = f;
212         while (!feof(f)) {
213                 struct uci_element *e;
214                 char *buf;
215
216                 uci_getln(ctx, 0);
217                 buf = pctx->buf;
218                 if (!buf[0])
219                         continue;
220
221                 /* NB: need to allocate the element before the call to 
222                  * uci_parse_history_tuple, otherwise the original string 
223                  * gets modified before it is saved */
224                 e = uci_alloc_generic(ctx, UCI_TYPE_HISTORY, pctx->buf, sizeof(struct uci_element));
225                 uci_list_add(&list, &e->list);
226
227                 uci_parse_history_tuple(ctx, &buf, &p, &s, &o, &v, NULL, NULL);
228                 if (section) {
229                         if (!s || (strcmp(section, s) != 0))
230                                 continue;
231                 }
232                 if (option) {
233                         if (!o || (strcmp(option, o) != 0))
234                                 continue;
235                 }
236                 /* match, drop this element again */
237                 uci_free_element(e);
238         }
239
240         /* rebuild the history file */
241         rewind(f);
242         ftruncate(fileno(f), 0);
243         uci_foreach_element_safe(&list, tmp, e) {
244                 fprintf(f, "%s\n", e->name);
245                 uci_free_element(e);
246         }
247         UCI_TRAP_RESTORE(ctx);
248
249 done:
250         if (filename)
251                 free(filename);
252         uci_close_stream(f);
253         uci_foreach_element_safe(&list, tmp, e) {
254                 uci_free_element(e);
255         }
256         ctx->internal = true;
257         uci_cleanup(ctx);
258 }
259
260 int uci_revert(struct uci_context *ctx, struct uci_package **pkg, char *section, char *option)
261 {
262         struct uci_package *p;
263         char *name = NULL;
264
265         UCI_HANDLE_ERR(ctx);
266         UCI_ASSERT(ctx, pkg != NULL);
267         p = *pkg;
268         UCI_ASSERT(ctx, p != NULL);
269         UCI_ASSERT(ctx, p->confdir);
270
271         /* 
272          * - flush unwritten changes
273          * - save the package name
274          * - unload the package
275          * - filter the history
276          * - reload the package
277          */
278         UCI_TRAP_SAVE(ctx, error);
279         UCI_INTERNAL(uci_save, ctx, p);
280         name = uci_strdup(ctx, p->e.name);
281
282         *pkg = NULL;
283         uci_free_package(&p);
284         uci_filter_history(ctx, name, section, option);
285
286         UCI_INTERNAL(uci_load, ctx, name, &p);
287         UCI_TRAP_RESTORE(ctx);
288         ctx->errno = 0;
289
290 error:
291         if (name)
292                 free(name);
293         if (ctx->errno)
294                 UCI_THROW(ctx, ctx->errno);
295         return 0;
296 }
297
298 int uci_save(struct uci_context *ctx, struct uci_package *p)
299 {
300         FILE *f = NULL;
301         char *filename = NULL;
302         struct uci_element *e, *tmp;
303
304         UCI_HANDLE_ERR(ctx);
305         UCI_ASSERT(ctx, p != NULL);
306
307         /* 
308          * if the config file was outside of the /etc/config path,
309          * don't save the history to a file, update the real file
310          * directly.
311          * does not modify the uci_package pointer
312          */
313         if (!p->confdir)
314                 return uci_commit(ctx, &p, false);
315
316         if (uci_list_empty(&p->history))
317                 return 0;
318
319         if ((asprintf(&filename, "%s/%s", ctx->savedir, p->e.name) < 0) || !filename)
320                 UCI_THROW(ctx, UCI_ERR_MEM);
321
322         ctx->errno = 0;
323         UCI_TRAP_SAVE(ctx, done);
324         f = uci_open_stream(ctx, filename, SEEK_END, true, true);
325         UCI_TRAP_RESTORE(ctx);
326
327         uci_foreach_element_safe(&p->history, tmp, e) {
328                 struct uci_history *h = uci_to_history(e);
329
330                 if (h->cmd == UCI_CMD_REMOVE)
331                         fprintf(f, "-");
332                 else if (h->cmd == UCI_CMD_RENAME)
333                         fprintf(f, "@");
334
335                 fprintf(f, "%s.%s", p->e.name, h->section);
336                 if (e->name)
337                         fprintf(f, ".%s", e->name);
338
339                 if (h->cmd == UCI_CMD_REMOVE)
340                         fprintf(f, "\n");
341                 else
342                         fprintf(f, "=%s\n", h->value);
343                 uci_free_history(h);
344         }
345
346 done:
347         uci_close_stream(f);
348         if (filename)
349                 free(filename);
350         if (ctx->errno)
351                 UCI_THROW(ctx, ctx->errno);
352
353         return 0;
354 }
355
356