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