fix missing api change
[project/uci.git] / util.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 misc utility functions and wrappers to standard
17  * functions, which throw exceptions upon failure.
18  */
19 #include <sys/types.h>
20 #include <sys/stat.h>
21 #include <sys/file.h>
22 #include <stdbool.h>
23 #include <unistd.h>
24 #include <ctype.h>
25 #include <fcntl.h>
26 #include <errno.h>
27
28 #define LINEBUF 32
29 #define LINEBUF_MAX     4096
30
31 __plugin void *uci_malloc(struct uci_context *ctx, size_t size)
32 {
33         void *ptr;
34
35         ptr = malloc(size);
36         if (!ptr)
37                 UCI_THROW(ctx, UCI_ERR_MEM);
38         memset(ptr, 0, size);
39
40         return ptr;
41 }
42
43 __plugin void *uci_realloc(struct uci_context *ctx, void *ptr, size_t size)
44 {
45         ptr = realloc(ptr, size);
46         if (!ptr)
47                 UCI_THROW(ctx, UCI_ERR_MEM);
48
49         return ptr;
50 }
51
52 __plugin char *uci_strdup(struct uci_context *ctx, const char *str)
53 {
54         char *ptr;
55
56         ptr = strdup(str);
57         if (!ptr)
58                 UCI_THROW(ctx, UCI_ERR_MEM);
59
60         return ptr;
61 }
62
63 /* Based on an efficient hash function published by D. J. Bernstein */
64 static unsigned int djbhash(unsigned int hash, char *str)
65 {
66         int len = strlen(str);
67         int i;
68
69         /* initial value */
70         if (hash == ~0)
71                 hash = 5381;
72
73         for(i = 0; i < len; i++) {
74                 hash = ((hash << 5) + hash) + str[i];
75         }
76         return (hash & 0x7FFFFFFF);
77 }
78
79 /*
80  * validate strings for names and types, reject special characters
81  * for names, only alphanum and _ is allowed (shell compatibility)
82  * for types, we allow more characters
83  */
84 __plugin bool uci_validate_str(const char *str, bool name)
85 {
86         if (!*str)
87                 return false;
88
89         while (*str) {
90                 unsigned char c = *str;
91                 if (!isalnum(c) && c != '_') {
92                         if (name || (c < 33) || (c > 126))
93                                 return false;
94                 }
95                 str++;
96         }
97         return true;
98 }
99
100 static inline bool uci_validate_package(const char *str)
101 {
102         return uci_validate_str(str, false);
103 }
104
105 static inline bool uci_validate_type(const char *str)
106 {
107         return uci_validate_str(str, false);
108 }
109
110 static inline bool uci_validate_name(const char *str)
111 {
112         return uci_validate_str(str, true);
113 }
114
115 bool uci_validate_text(const char *str)
116 {
117         while (*str) {
118                 unsigned char c = *str;
119                 if ((c == '\r') || (c == '\n') ||
120                         ((c < 32) && (c != '\t')))
121                         return false;
122                 str++;
123         }
124         return true;
125 }
126
127 static void uci_alloc_parse_context(struct uci_context *ctx)
128 {
129         ctx->pctx = (struct uci_parse_context *) uci_malloc(ctx, sizeof(struct uci_parse_context));
130 }
131
132 int uci_parse_ptr(struct uci_context *ctx, struct uci_ptr *ptr, char *str)
133 {
134         char *last = NULL;
135         char *tmp;
136
137         UCI_HANDLE_ERR(ctx);
138         UCI_ASSERT(ctx, str);
139         UCI_ASSERT(ctx, ptr);
140
141         memset(ptr, 0, sizeof(struct uci_ptr));
142
143         /* value */
144         last = strchr(str, '=');
145         if (last) {
146                 *last = 0;
147                 last++;
148                 ptr->value = last;
149         }
150
151         ptr->package = strsep(&str, ".");
152         if (!ptr->package)
153                 goto error;
154
155         ptr->section = strsep(&str, ".");
156         if (!ptr->section) {
157                 ptr->target = UCI_TYPE_PACKAGE;
158                 goto lastval;
159         }
160
161         ptr->option = strsep(&str, ".");
162         if (!ptr->option) {
163                 ptr->target = UCI_TYPE_SECTION;
164                 goto lastval;
165         } else {
166                 ptr->target = UCI_TYPE_OPTION;
167         }
168
169         tmp = strsep(&str, ".");
170         if (tmp)
171                 goto error;
172
173 lastval:
174         if (ptr->package && !uci_validate_package(ptr->package))
175                 goto error;
176         if (ptr->section && !uci_validate_name(ptr->section))
177                 ptr->flags |= UCI_LOOKUP_EXTENDED;
178         if (ptr->option && !uci_validate_name(ptr->option))
179                 goto error;
180         if (ptr->value && !uci_validate_text(ptr->value))
181                 goto error;
182
183         return 0;
184
185 error:
186         memset(ptr, 0, sizeof(struct uci_ptr));
187         UCI_THROW(ctx, UCI_ERR_PARSE);
188 }
189
190
191 static void uci_parse_error(struct uci_context *ctx, char *pos, char *reason)
192 {
193         struct uci_parse_context *pctx = ctx->pctx;
194
195         pctx->reason = reason;
196         pctx->byte = pos - pctx->buf;
197         UCI_THROW(ctx, UCI_ERR_PARSE);
198 }
199
200
201 /*
202  * Fetch a new line from the input stream and resize buffer if necessary
203  */
204 static void uci_getln(struct uci_context *ctx, int offset)
205 {
206         struct uci_parse_context *pctx = ctx->pctx;
207         char *p;
208         int ofs;
209
210         if (pctx->buf == NULL) {
211                 pctx->buf = uci_malloc(ctx, LINEBUF);
212                 pctx->bufsz = LINEBUF;
213         }
214
215         ofs = offset;
216         do {
217                 p = &pctx->buf[ofs];
218                 p[ofs] = 0;
219
220                 p = fgets(p, pctx->bufsz - ofs, pctx->file);
221                 if (!p || !*p)
222                         return;
223
224                 ofs += strlen(p);
225                 if (pctx->buf[ofs - 1] == '\n') {
226                         pctx->line++;
227                         pctx->buf[ofs - 1] = 0;
228                         return;
229                 }
230
231                 if (pctx->bufsz > LINEBUF_MAX/2)
232                         uci_parse_error(ctx, p, "line too long");
233
234                 pctx->bufsz *= 2;
235                 pctx->buf = uci_realloc(ctx, pctx->buf, pctx->bufsz);
236         } while (1);
237 }
238
239 /* 
240  * parse a character escaped by '\'
241  * returns true if the escaped character is to be parsed
242  * returns false if the escaped character is to be ignored
243  */
244 static inline bool parse_backslash(struct uci_context *ctx, char **str)
245 {
246         /* skip backslash */
247         *str += 1;
248
249         /* undecoded backslash at the end of line, fetch the next line */
250         if (!**str) {
251                 *str += 1;
252                 uci_getln(ctx, *str - ctx->pctx->buf);
253                 return false;
254         }
255
256         /* FIXME: decode escaped char, necessary? */
257         return true;
258 }
259
260 /*
261  * move the string pointer forward until a non-whitespace character or
262  * EOL is reached
263  */
264 static void skip_whitespace(struct uci_context *ctx, char **str)
265 {
266 restart:
267         while (**str && isspace(**str))
268                 *str += 1;
269
270         if (**str == '\\') {
271                 if (!parse_backslash(ctx, str))
272                         goto restart;
273         }
274 }
275
276 static inline void addc(char **dest, char **src)
277 {
278         **dest = **src;
279         *dest += 1;
280         *src += 1;
281 }
282
283 /*
284  * parse a double quoted string argument from the command line
285  */
286 static void parse_double_quote(struct uci_context *ctx, char **str, char **target)
287 {
288         char c;
289
290         /* skip quote character */
291         *str += 1;
292
293         while ((c = **str)) {
294                 switch(c) {
295                 case '"':
296                         **target = 0;
297                         *str += 1;
298                         return;
299                 case '\\':
300                         if (!parse_backslash(ctx, str))
301                                 continue;
302                         /* fall through */
303                 default:
304                         addc(target, str);
305                         break;
306                 }
307         }
308         uci_parse_error(ctx, *str, "unterminated \"");
309 }
310
311 /*
312  * parse a single quoted string argument from the command line
313  */
314 static void parse_single_quote(struct uci_context *ctx, char **str, char **target)
315 {
316         char c;
317         /* skip quote character */
318         *str += 1;
319
320         while ((c = **str)) {
321                 switch(c) {
322                 case '\'':
323                         **target = 0;
324                         *str += 1;
325                         return;
326                 default:
327                         addc(target, str);
328                 }
329         }
330         uci_parse_error(ctx, *str, "unterminated '");
331 }
332
333 /*
334  * parse a string from the command line and detect the quoting style
335  */
336 static void parse_str(struct uci_context *ctx, char **str, char **target)
337 {
338         bool next = true;
339         do {
340                 switch(**str) {
341                 case '\'':
342                         parse_single_quote(ctx, str, target);
343                         break;
344                 case '"':
345                         parse_double_quote(ctx, str, target);
346                         break;
347                 case '#':
348                         **str = 0;
349                         /* fall through */
350                 case 0:
351                         goto done;
352                 case ';':
353                         next = false;
354                         goto done;
355                 case '\\':
356                         if (!parse_backslash(ctx, str))
357                                 continue;
358                         /* fall through */
359                 default:
360                         addc(target, str);
361                         break;
362                 }
363         } while (**str && !isspace(**str));
364 done:
365
366         /* 
367          * if the string was unquoted and we've stopped at a whitespace
368          * character, skip to the next one, because the whitespace will
369          * be overwritten by a null byte here
370          */
371         if (**str && next)
372                 *str += 1;
373
374         /* terminate the parsed string */
375         **target = 0;
376 }
377
378 /*
379  * extract the next argument from the command line
380  */
381 static char *next_arg(struct uci_context *ctx, char **str, bool required, bool name)
382 {
383         char *val;
384         char *ptr;
385
386         val = ptr = *str;
387         skip_whitespace(ctx, str);
388         if(*str[0] == ';') {
389                 *str[0] = 0;
390                 *str += 1;
391         } else {
392                 parse_str(ctx, str, &ptr);
393         }
394         if (!*val) {
395                 if (required)
396                         uci_parse_error(ctx, *str, "insufficient arguments");
397                 goto done;
398         }
399
400         if (name && !uci_validate_name(val))
401                 uci_parse_error(ctx, val, "invalid character in field");
402
403 done:
404         return val;
405 }
406
407 int uci_parse_argument(struct uci_context *ctx, FILE *stream, char **str, char **result)
408 {
409         UCI_HANDLE_ERR(ctx);
410         UCI_ASSERT(ctx, str != NULL);
411         UCI_ASSERT(ctx, result != NULL);
412
413         if (ctx->pctx && (ctx->pctx->file != stream))
414                 uci_cleanup(ctx);
415
416         if (!ctx->pctx)
417                 uci_alloc_parse_context(ctx);
418
419         ctx->pctx->file = stream;
420
421         if (!*str) {
422                 uci_getln(ctx, 0);
423                 *str = ctx->pctx->buf;
424         }
425
426         *result = next_arg(ctx, str, false, false);
427
428         return 0;
429 }
430
431
432 /*
433  * open a stream and go to the right position
434  *
435  * note: when opening for write and seeking to the beginning of
436  * the stream, truncate the file
437  */
438 static FILE *uci_open_stream(struct uci_context *ctx, const char *filename, int pos, bool write, bool create)
439 {
440         struct stat statbuf;
441         FILE *file = NULL;
442         int fd, ret;
443         int mode = (write ? O_RDWR : O_RDONLY);
444
445         if (create)
446                 mode |= O_CREAT;
447
448         if (!write && ((stat(filename, &statbuf) < 0) ||
449                 ((statbuf.st_mode &  S_IFMT) != S_IFREG))) {
450                 UCI_THROW(ctx, UCI_ERR_NOTFOUND);
451         }
452
453         fd = open(filename, mode, UCI_FILEMODE);
454         if (fd < 0)
455                 goto error;
456
457         ret = flock(fd, (write ? LOCK_EX : LOCK_SH));
458         if ((ret < 0) && (errno != ENOSYS))
459                 goto error;
460
461         ret = lseek(fd, 0, pos);
462
463         if (ret < 0)
464                 goto error;
465
466         file = fdopen(fd, (write ? "w+" : "r"));
467         if (file)
468                 goto done;
469
470 error:
471         UCI_THROW(ctx, UCI_ERR_IO);
472 done:
473         return file;
474 }
475
476 static void uci_close_stream(FILE *stream)
477 {
478         int fd;
479
480         if (!stream)
481                 return;
482
483         fflush(stream);
484         fd = fileno(stream);
485         flock(fd, LOCK_UN);
486         fclose(stream);
487 }
488
489