| 1 |
/* $FreeWRT: src/share/misc/licence.template,v 1.20 2006/12/11 21:04:56 tg Rel $ */ |
| 2 |
|
| 3 |
/*- |
| 4 |
* Copyright (c) 2007 |
| 5 |
* Thorsten Glaser <tg@mirbsd.de> |
| 6 |
* |
| 7 |
* Provided that these terms and disclaimer and all copyright notices |
| 8 |
* are retained or reproduced in an accompanying document, permission |
| 9 |
* is granted to deal in this work without restriction, including un- |
| 10 |
* limited rights to use, publicly perform, distribute, sell, modify, |
| 11 |
* merge, give away, or sublicence. |
| 12 |
* |
| 13 |
* Advertising materials mentioning features or use of this work must |
| 14 |
* display the following acknowledgement: |
| 15 |
* This product includes material provided by Thorsten Glaser. |
| 16 |
* This acknowledgement does not need to be reprinted if this work is |
| 17 |
* linked into a bigger work whose licence does not allow such clause |
| 18 |
* and the author of this work is given due credit in the bigger work |
| 19 |
* or its accompanying documents, where such information is generally |
| 20 |
* kept, provided that said credits are retained. |
| 21 |
* |
| 22 |
* This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to |
| 23 |
* the utmost extent permitted by applicable law, neither express nor |
| 24 |
* implied; without malicious intent or gross negligence. In no event |
| 25 |
* may a licensor, author or contributor be held liable for indirect, |
| 26 |
* direct, other damage, loss, or other issues arising in any way out |
| 27 |
* of dealing in the work, even if advised of the possibility of such |
| 28 |
* damage or existence of a defect, except proven that it results out |
| 29 |
* of said person's immediate fault when using the work as intended. |
| 30 |
*/ |
| 31 |
|
| 32 |
#include <sys/param.h> |
| 33 |
#include <sys/mman.h> |
| 34 |
#include <sys/stat.h> |
| 35 |
|
| 36 |
#include <err.h> |
| 37 |
#include <errno.h> |
| 38 |
#include <stdarg.h> |
| 39 |
#include <stdint.h> |
| 40 |
#include <stdio.h> |
| 41 |
#include <stdlib.h> |
| 42 |
#include <string.h> |
| 43 |
|
| 44 |
#include "nfotiser.h" |
| 45 |
|
| 46 |
static void syntaxerr_(size_t, const char *, ...) |
| 47 |
__attribute__((format (printf, 2, 3))) |
| 48 |
__attribute__((noreturn)); |
| 49 |
#define syntaxerr(fmt, ...) syntaxerr_(lineno, (fmt), ##__VA_ARGS__) |
| 50 |
|
| 51 |
/* |
| 52 |
* Parsing works as follows: |
| 53 |
* |
| 54 |
* - strip completely empty line |
| 55 |
* - strip line beginning with hash mark |
| 56 |
* - if line ends with backslash, get next line that is not |
| 57 |
* + empty |
| 58 |
* + beginning with a hash mark |
| 59 |
* and look if it begins with a tab (if not: syntax error) |
| 60 |
* if so, strip backslash + newline + tab and repeat |
| 61 |
* - if line begins with a tab, strip it and append line to |
| 62 |
* the last line, including the newline separator |
| 63 |
* - match line with '([A-Za-z_][A-Za-z0-9_]*)\t(.*)$' and |
| 64 |
* call \1 the key and \2 the value (else: syntax error) |
| 65 |
* - uppercase the key |
| 66 |
* - enter the key/value pair in the system |
| 67 |
* |
| 68 |
* The following is also part of parsing, but left to the caller: |
| 69 |
* - replace ${foo} with the value of key "foo" |
| 70 |
* - undouble all backslashes |
| 71 |
* |
| 72 |
* We enter the key into the system up to three-fold: |
| 73 |
* - KWT_NORMAL => ([A-Za-z_][A-Za-z0-9_]*) |
| 74 |
* + \1 = keyword (toupper'd) |
| 75 |
* - KWT_MULTI => ([A-Za-z_][A-Za-z0-9_]*)_([A-Za-z0-9_]*) |
| 76 |
* + \1 = keyword (toupper'd) |
| 77 |
* + \2 = kw_multi (case preserving) |
| 78 |
* - KWT_ITERATED => ([A-Za-z_][A-Za-z0-9_]*)_([0-9]*) |
| 79 |
* + \1 = keyword (toupper'd) |
| 80 |
* + \2 = kw_iter (unsigned integer value) |
| 81 |
* - KWT_MULTITER => ([A-Za-z_][A-Za-z0-9_]*)_([0-9]*)_([A-Za-z0-9_]*) |
| 82 |
* + \1 = keyword (toupper'd) |
| 83 |
* + \2 = kw_iter (unsigned integer value) |
| 84 |
* + \3 = kw_multi (case preserving) |
| 85 |
* |
| 86 |
* Cf. https://www.freewrt.org/trac/wiki/Documentation/Specs/Freewrt_info_files |
| 87 |
* for more examples and a more human-readable version of this specification. |
| 88 |
*/ |
| 89 |
|
| 90 |
struct parser_result * |
| 91 |
nfo_parse(int fd, const struct parser_keywords *kws) |
| 92 |
{ |
| 93 |
struct parser_result *res; |
| 94 |
struct parser_res *entry; |
| 95 |
const struct parser_keywords *kwp; |
| 96 |
char *cp, *t, *tp, *buf, *buf_base; |
| 97 |
size_t len, n, lineno = 0; |
| 98 |
struct stat sb; |
| 99 |
char *entry_multi; |
| 100 |
unsigned entry_iter; |
| 101 |
|
| 102 |
res = xmalloc(sizeof (struct parser_result)); |
| 103 |
CIRCLEQ_INIT(res); |
| 104 |
|
| 105 |
if (fstat(fd, &sb)) |
| 106 |
err(255, "cannot stat"); |
| 107 |
|
| 108 |
/* slurp whole file into mapped memory */ |
| 109 |
len = sb.st_size; |
| 110 |
D(2, "trying to mmap %zu bytes...", len); |
| 111 |
if ((cp = mmap(NULL, len, PROT_READ, MAP_FILE, fd, 0)) == MAP_FAILED) |
| 112 |
err(255, "cannot mmap %zu bytes", len); |
| 113 |
D(2, "ok\n"); |
| 114 |
/* make a nice NUL-terminated copy (malloc'd) */ |
| 115 |
D(2, "copying %zu bytes...", len); |
| 116 |
buf = buf_base = str_nsave(cp, len); |
| 117 |
D(2, " munmap..."); |
| 118 |
if (munmap(cp, len)) |
| 119 |
err(255, "cannot munmap"); |
| 120 |
D(2, "ok\n"); |
| 121 |
/* don't need the file any more */ |
| 122 |
|
| 123 |
/* now we can operate on the NUL-terminated R/W string “buf” */ |
| 124 |
if (buf[len - 1] != '\n') |
| 125 |
syntaxerr("file does not end with a newline!"); |
| 126 |
|
| 127 |
D(2, "entire string: «%s» (%zu)\n", buf, strlen(buf)); |
| 128 |
lineno = 1; |
| 129 |
|
| 130 |
loop_newline: |
| 131 |
/* completely new line buffer */ |
| 132 |
cp = NULL; |
| 133 |
|
| 134 |
loop_getline: |
| 135 |
/* get a line and add it to line buffer */ |
| 136 |
if (*buf == '\0') { |
| 137 |
D(2, "D: [%4zu] read EOF\n", lineno); |
| 138 |
goto loop_eof; |
| 139 |
} |
| 140 |
if (*buf == '\n') { |
| 141 |
D(2, "D: [%4zu] read newline\n", lineno); |
| 142 |
++buf; |
| 143 |
++lineno; |
| 144 |
goto loop_getline; |
| 145 |
} |
| 146 |
if (*buf == '#') { |
| 147 |
D(2, "D: [%4zu] read comment ", lineno); |
| 148 |
t = buf; |
| 149 |
while (*t != '\n') |
| 150 |
++t; |
| 151 |
*t++ = '\0'; |
| 152 |
D(2, "'%s'\n", buf); |
| 153 |
buf = t; |
| 154 |
++lineno; |
| 155 |
goto loop_getline; |
| 156 |
} |
| 157 |
if (*buf == '\t') { |
| 158 |
D(2, "D: [%4zu] read trail line\n", lineno); |
| 159 |
if (cp == NULL) |
| 160 |
syntaxerr("expected lead line, got trail line!"); |
| 161 |
t = ++buf; |
| 162 |
goto loop_storeline; |
| 163 |
} else { |
| 164 |
D(2, "D: [%4zu] read head line (%02X)\n", lineno, *buf); |
| 165 |
} |
| 166 |
if (cp != NULL) { |
| 167 |
--lineno; |
| 168 |
goto process_line; |
| 169 |
} |
| 170 |
if ((*buf >= 'A' && *buf <= 'Z') || |
| 171 |
(*buf >= 'a' && *buf <= 'z') || *buf == '_') |
| 172 |
t = buf; |
| 173 |
else |
| 174 |
syntaxerr("line must begin with a letter or an underscore!"); |
| 175 |
loop_storeline: |
| 176 |
while (*t++ != '\n') |
| 177 |
; |
| 178 |
t = str_nsave(buf, (tp = t) - buf); |
| 179 |
buf = tp; |
| 180 |
if (cp != NULL) { |
| 181 |
if (*(tp = cp + strlen(cp) - 2) == '\\') |
| 182 |
*tp = '\0'; |
| 183 |
} |
| 184 |
tp = t + strlen(t) - 1; |
| 185 |
*tp = '\0'; |
| 186 |
D(2, "D: [%4zu] storing string '%s'\n", lineno, t); |
| 187 |
*tp = '\n'; |
| 188 |
tp = str_add(cp, t); |
| 189 |
if (cp != NULL) |
| 190 |
free(cp); |
| 191 |
free(t); |
| 192 |
cp = tp; |
| 193 |
++lineno; |
| 194 |
goto loop_getline; |
| 195 |
process_line: |
| 196 |
/* cp points to <line>[<nl><line>…][\]<nl> */ |
| 197 |
/* buf points to first byte of next line */ |
| 198 |
if (*(tp = cp + strlen(cp) - 2) == '\\') |
| 199 |
syntaxerr("expected trail line, got lead line!"); |
| 200 |
process_lastline: |
| 201 |
/* cut off final newline */ |
| 202 |
*++tp = '\0'; |
| 203 |
D(2, "D: [%4zu] processing «%s»\n", lineno, cp); |
| 204 |
|
| 205 |
/* parse the meat out of there */ |
| 206 |
if ((tp = strchr(cp, '\t')) == NULL) |
| 207 |
syntaxerr("expected keyword + tab + value!"); |
| 208 |
*tp++ = '\0'; |
| 209 |
/* cp points to keyword, tp points to value */ |
| 210 |
entry_multi = NULL; |
| 211 |
entry_iter = 0; |
| 212 |
for (kwp = kws; kwp->kwprefix != NULL; ++kwp) { |
| 213 |
char *np; |
| 214 |
|
| 215 |
/* exact match? */ |
| 216 |
if (!strcasecmp(cp, kwp->kwprefix)) |
| 217 |
/* yepp */ break; |
| 218 |
/* prefix match allowed? */ |
| 219 |
if (kwp->kwtype == KWT_NORMAL) |
| 220 |
/* nope */ continue; |
| 221 |
/* prefix match? */ |
| 222 |
if (strncasecmp(cp, kwp->kwprefix, n = strlen(kwp->kwprefix))) |
| 223 |
/* nope */ continue; |
| 224 |
if (cp[n] != '_') |
| 225 |
/* same */ continue; |
| 226 |
/* okay, we got a prefix match, get args */ |
| 227 |
np = cp + n + 1; |
| 228 |
if (kwp->kwtype == KWT_ITERATED || |
| 229 |
kwp->kwtype == KWT_MULTITER) { |
| 230 |
char *zp = np; |
| 231 |
|
| 232 |
if (zp[0] == '0' && (zp[1] == 'x' || zp[1] == 'X')) |
| 233 |
zp += 2; |
| 234 |
while (*zp >= '0' && *zp <= '9') |
| 235 |
++zp; |
| 236 |
if (zp == np) |
| 237 |
syntaxerr("iterator expected"); |
| 238 |
if (*zp != (char)(kwp->kwtype == KWT_ITERATED ? |
| 239 |
'\0' : '_')) |
| 240 |
syntaxerr("%s expected, got 0x%02X", |
| 241 |
kwp->kwtype == KWT_ITERATED ? |
| 242 |
"tab" : "underscore", *zp); |
| 243 |
*zp++ = '\0'; |
| 244 |
entry_iter = (unsigned)strtoul(np, NULL, 0); |
| 245 |
np = zp; |
| 246 |
} |
| 247 |
if (kwp->kwtype == KWT_MULTI || |
| 248 |
kwp->kwtype == KWT_MULTITER) |
| 249 |
entry_multi = str_save(np); |
| 250 |
/* values filled out */ |
| 251 |
break; |
| 252 |
} |
| 253 |
if (kwp->kwprefix == NULL) |
| 254 |
errx(1, "unknown keyword '%s'", cp); |
| 255 |
entry = xmalloc(sizeof (struct parser_res)); |
| 256 |
bzero(entry, sizeof (struct parser_res)); |
| 257 |
entry->keyword = kwp->kwnum; |
| 258 |
entry->kw_multi = entry_multi; |
| 259 |
entry->kw_iter = entry_iter; |
| 260 |
entry->value = str_save(tp); |
| 261 |
CIRCLEQ_INSERT_TAIL(res, entry, e); |
| 262 |
free(cp); |
| 263 |
++lineno; |
| 264 |
goto loop_newline; |
| 265 |
loop_eof: |
| 266 |
if (cp != NULL) { |
| 267 |
if (*(tp = cp + strlen(cp) - 2) == '\\') |
| 268 |
syntaxerr("expected trail line, read end of file!"); |
| 269 |
goto process_lastline; |
| 270 |
} |
| 271 |
free(buf_base); |
| 272 |
return (res); |
| 273 |
} |
| 274 |
|
| 275 |
const struct parser_keywords * |
| 276 |
parser_getkwbynum(parser_kwords num, const struct parser_keywords *kws) |
| 277 |
{ |
| 278 |
const struct parser_keywords *kwp; |
| 279 |
|
| 280 |
for (kwp = kws; kwp->kwprefix != NULL; ++kwp) |
| 281 |
if (kwp->kwnum == num) |
| 282 |
break; |
| 283 |
|
| 284 |
return (kwp->kwprefix == NULL ? NULL : kwp); |
| 285 |
} |
| 286 |
|
| 287 |
void |
| 288 |
parser_dump(struct parser_res *entry, const struct parser_keywords *kws) |
| 289 |
{ |
| 290 |
const struct parser_keywords *kwp; |
| 291 |
|
| 292 |
kwp = parser_getkwbynum(entry->keyword, kws); |
| 293 |
printf("keyword %s (type %s)", |
| 294 |
kwp == NULL ? "<unknown>" : kwp->kwprefix, |
| 295 |
kwp == NULL ? "invalid" : |
| 296 |
kwp->kwtype == KWT_NORMAL ? "normal" : |
| 297 |
kwp->kwtype == KWT_MULTI ? "multi" : |
| 298 |
kwp->kwtype == KWT_ITERATED ? "iterated" : |
| 299 |
kwp->kwtype == KWT_MULTITER ? "multiter" : "unknown"); |
| 300 |
if (kwp) { |
| 301 |
if (kwp->kwtype == KWT_ITERATED || |
| 302 |
kwp->kwtype == KWT_MULTITER) |
| 303 |
printf(", iterator %u", entry->kw_iter); |
| 304 |
if (kwp->kwtype == KWT_MULTI || |
| 305 |
kwp->kwtype == KWT_MULTITER) |
| 306 |
printf(", multival '%s'", entry->kw_multi); |
| 307 |
} |
| 308 |
putchar('\n'); |
| 309 |
} |
| 310 |
|
| 311 |
void |
| 312 |
parser_free(struct parser_result *head) |
| 313 |
{ |
| 314 |
struct parser_res *entry; |
| 315 |
|
| 316 |
if (head == NULL) |
| 317 |
return; |
| 318 |
|
| 319 |
while (!CIRCLEQ_EMPTY(head)) { |
| 320 |
entry = CIRCLEQ_FIRST(head); |
| 321 |
CIRCLEQ_REMOVE(head, entry, e); |
| 322 |
if (entry->kw_multi != NULL) |
| 323 |
free(entry->kw_multi); |
| 324 |
if (entry->value != NULL) |
| 325 |
free(entry->value); |
| 326 |
free(entry); |
| 327 |
} |
| 328 |
|
| 329 |
free(head); |
| 330 |
} |
| 331 |
|
| 332 |
static void |
| 333 |
syntaxerr_(size_t lno, const char *fmt, ...) |
| 334 |
{ |
| 335 |
va_list args; |
| 336 |
|
| 337 |
va_start(args, fmt); |
| 338 |
fflush(NULL); |
| 339 |
fprintf(stderr, "syntax error [%4zu]: ", lno); |
| 340 |
fflush(NULL); |
| 341 |
verrx(1, fmt, args); |
| 342 |
va_end(args); |
| 343 |
} |