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