| 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 |
* - KWT_MULTITOP => ([A-Za-z_][A-Za-z0-9_]*)_(([0-9]*)_)?([A-Za-z0-9_]*) |
| 86 |
* + \1 = keyword (toupper'd) |
| 87 |
* + \2 = kw_iter (unsigned integer value), 0 if not set |
| 88 |
* + \3 = kw_multi (case preserving) |
| 89 |
* All KWT_* can match as if they were KWT_NORMAL (if we have a perfect |
| 90 |
* first match); kw_iter=0 and kw_multi=NULL in that case. |
| 91 |
* |
| 92 |
* Cf. https://www.freewrt.org/trac/wiki/Documentation/Specs/Freewrt_info_files |
| 93 |
* for more examples and a more human-readable version of this specification. |
| 94 |
*/ |
| 95 |
|
| 96 |
struct parser_result * |
| 97 |
nfo_parse(int fd, const struct parser_keywords *kws) |
| 98 |
{ |
| 99 |
struct parser_result *res; |
| 100 |
struct parser_res *entry; |
| 101 |
const struct parser_keywords *kwp; |
| 102 |
char *cp, *t, *tp, *buf, *buf_base; |
| 103 |
size_t len, n, lineno = 0; |
| 104 |
struct stat sb; |
| 105 |
char *entry_multi; |
| 106 |
unsigned entry_iter; |
| 107 |
enum parser_kwtype entry_type; |
| 108 |
|
| 109 |
res = xmalloc(sizeof (struct parser_result)); |
| 110 |
CIRCLEQ_INIT(res); |
| 111 |
|
| 112 |
if (fstat(fd, &sb)) |
| 113 |
err(255, "cannot stat"); |
| 114 |
|
| 115 |
/* slurp whole file into mapped memory */ |
| 116 |
len = sb.st_size; |
| 117 |
D(2, "trying to mmap %zu bytes...", len); |
| 118 |
if ((cp = mmap(NULL, len, PROT_READ, MAP_FILE, fd, 0)) == MAP_FAILED) |
| 119 |
err(255, "cannot mmap %zu bytes", len); |
| 120 |
D(2, "ok\n"); |
| 121 |
/* make a nice NUL-terminated copy (malloc'd) */ |
| 122 |
D(2, "copying %zu bytes...", len); |
| 123 |
buf = buf_base = str_nsave(cp, len); |
| 124 |
D(2, " munmap..."); |
| 125 |
if (munmap(cp, len)) |
| 126 |
err(255, "cannot munmap"); |
| 127 |
D(2, "ok\n"); |
| 128 |
/* don't need the file any more */ |
| 129 |
|
| 130 |
/* now we can operate on the NUL-terminated R/W string “buf” */ |
| 131 |
if (buf[len - 1] != '\n') |
| 132 |
syntaxerr("file does not end with a newline!"); |
| 133 |
|
| 134 |
D(2, "entire string: «%s» (%zu)\n", buf, strlen(buf)); |
| 135 |
lineno = 1; |
| 136 |
|
| 137 |
loop_newline: |
| 138 |
/* completely new line buffer */ |
| 139 |
cp = NULL; |
| 140 |
|
| 141 |
loop_getline: |
| 142 |
/* get a line and add it to line buffer */ |
| 143 |
if (*buf == '\0') { |
| 144 |
D(2, "D: [%4zu] read EOF\n", lineno); |
| 145 |
goto loop_eof; |
| 146 |
} |
| 147 |
if (*buf == '\n') { |
| 148 |
D(2, "D: [%4zu] read newline\n", lineno); |
| 149 |
++buf; |
| 150 |
++lineno; |
| 151 |
goto loop_getline; |
| 152 |
} |
| 153 |
if (*buf == '#') { |
| 154 |
D(2, "D: [%4zu] read comment ", lineno); |
| 155 |
t = buf; |
| 156 |
while (*t != '\n') |
| 157 |
++t; |
| 158 |
*t++ = '\0'; |
| 159 |
D(2, "'%s'\n", buf); |
| 160 |
buf = t; |
| 161 |
++lineno; |
| 162 |
goto loop_getline; |
| 163 |
} |
| 164 |
if (*buf == '\t') { |
| 165 |
D(2, "D: [%4zu] read trail line\n", lineno); |
| 166 |
if (cp == NULL) |
| 167 |
syntaxerr("expected lead line, got trail line!"); |
| 168 |
t = ++buf; |
| 169 |
goto loop_storeline; |
| 170 |
} else { |
| 171 |
D(2, "D: [%4zu] read head line (%02X)\n", lineno, *buf); |
| 172 |
} |
| 173 |
if (cp != NULL) { |
| 174 |
--lineno; |
| 175 |
goto process_line; |
| 176 |
} |
| 177 |
if ((*buf >= 'A' && *buf <= 'Z') || |
| 178 |
(*buf >= 'a' && *buf <= 'z') || *buf == '_') |
| 179 |
t = buf; |
| 180 |
else |
| 181 |
syntaxerr("line must begin with a letter or an underscore!"); |
| 182 |
loop_storeline: |
| 183 |
while (*t++ != '\n') |
| 184 |
; |
| 185 |
t = str_nsave(buf, (tp = t) - buf); |
| 186 |
buf = tp; |
| 187 |
if (cp != NULL) { |
| 188 |
if (*(tp = cp + strlen(cp) - 2) == '\\') |
| 189 |
*tp = '\0'; |
| 190 |
} |
| 191 |
tp = t + strlen(t) - 1; |
| 192 |
*tp = '\0'; |
| 193 |
D(2, "D: [%4zu] storing string '%s'\n", lineno, t); |
| 194 |
*tp = '\n'; |
| 195 |
tp = str_add(cp, t); |
| 196 |
free(t); |
| 197 |
cp = tp; |
| 198 |
++lineno; |
| 199 |
goto loop_getline; |
| 200 |
process_line: |
| 201 |
/* cp points to <line>[<nl><line>…][\]<nl> */ |
| 202 |
/* buf points to first byte of next line */ |
| 203 |
if (*(tp = cp + strlen(cp) - 2) == '\\') |
| 204 |
syntaxerr("expected trail line, got lead line!"); |
| 205 |
process_lastline: |
| 206 |
/* cut off final newline */ |
| 207 |
*++tp = '\0'; |
| 208 |
D(2, "D: [%4zu] processing «%s»\n", lineno, cp); |
| 209 |
|
| 210 |
/* parse the meat out of there */ |
| 211 |
if ((tp = strchr(cp, '\t')) == NULL) |
| 212 |
syntaxerr("expected keyword + tab + value!"); |
| 213 |
*tp++ = '\0'; |
| 214 |
/* cp points to keyword, tp points to value */ |
| 215 |
entry_multi = NULL; |
| 216 |
entry_iter = 0; |
| 217 |
entry_type = KWT_INVALID; |
| 218 |
for (kwp = kws; kwp->kwprefix != NULL; ++kwp) { |
| 219 |
char *np; |
| 220 |
|
| 221 |
/* exact match? */ |
| 222 |
if (!strcasecmp(cp, kwp->kwprefix)) { |
| 223 |
/* yep */ |
| 224 |
entry_type = KWT_NORMAL; |
| 225 |
break; |
| 226 |
} |
| 227 |
/* prefix match allowed? */ |
| 228 |
if (kwp->kwtype == KWT_NORMAL) |
| 229 |
/* nope */ continue; |
| 230 |
/* prefix match? */ |
| 231 |
if (strncasecmp(cp, kwp->kwprefix, n = strlen(kwp->kwprefix))) |
| 232 |
/* nope */ continue; |
| 233 |
if (cp[n] != '_') |
| 234 |
/* same */ continue; |
| 235 |
/* okay, we got a prefix match, get args */ |
| 236 |
np = cp + n + 1; |
| 237 |
entry_type = kwp->kwtype; |
| 238 |
if (kwp->kwtype == KWT_ITERATED || |
| 239 |
(kwp->kwtype == KWT_MULTITOP && |
| 240 |
(*np >= '0' && *np <= '9')) || |
| 241 |
kwp->kwtype == KWT_MULTITER) { |
| 242 |
char *zp = np; |
| 243 |
|
| 244 |
if (zp[0] == '0' && (zp[1] == 'x' || zp[1] == 'X')) |
| 245 |
zp += 2; |
| 246 |
while (*zp >= '0' && *zp <= '9') |
| 247 |
++zp; |
| 248 |
if (zp == np) |
| 249 |
syntaxerr("iterator expected"); |
| 250 |
if (*zp != (char)(kwp->kwtype == KWT_ITERATED ? |
| 251 |
'\0' : '_')) |
| 252 |
syntaxerr("%s expected, got 0x%02X", |
| 253 |
kwp->kwtype == KWT_ITERATED ? |
| 254 |
"tab" : "underscore", *zp); |
| 255 |
*zp++ = '\0'; |
| 256 |
entry_iter = (unsigned)strtoul(np, NULL, 0); |
| 257 |
np = zp; |
| 258 |
if (kwp->kwtype == KWT_MULTITOP) |
| 259 |
entry_type = KWT_MULTITER; |
| 260 |
} |
| 261 |
if (kwp->kwtype == KWT_MULTITOP) |
| 262 |
entry_type = KWT_MULTI; |
| 263 |
if (kwp->kwtype == KWT_MULTI || |
| 264 |
kwp->kwtype == KWT_MULTITOP || |
| 265 |
kwp->kwtype == KWT_MULTITER) |
| 266 |
entry_multi = str_save(np); |
| 267 |
/* values filled out */ |
| 268 |
break; |
| 269 |
} |
| 270 |
if (kwp->kwprefix == NULL) |
| 271 |
errx(1, "unknown keyword '%s'", cp); |
| 272 |
if (entry_type == KWT_INVALID) |
| 273 |
syntaxerr("internal error: invalid entry type"); |
| 274 |
entry = parser_new(kwp->kwnum, entry_type, entry_multi, entry_iter, |
| 275 |
str_save(tp)); |
| 276 |
CIRCLEQ_INSERT_TAIL(res, entry, e); |
| 277 |
free(cp); |
| 278 |
++lineno; |
| 279 |
goto loop_newline; |
| 280 |
loop_eof: |
| 281 |
if (cp != NULL) { |
| 282 |
if (*(tp = cp + strlen(cp) - 2) == '\\') |
| 283 |
syntaxerr("expected trail line, read end of file!"); |
| 284 |
goto process_lastline; |
| 285 |
} |
| 286 |
free(buf_base); |
| 287 |
return (res); |
| 288 |
} |
| 289 |
|
| 290 |
const struct parser_keywords * |
| 291 |
parser_getkwbynum(parser_kwords num, const struct parser_keywords *kws) |
| 292 |
{ |
| 293 |
const struct parser_keywords *kwp; |
| 294 |
|
| 295 |
for (kwp = kws; kwp->kwprefix != NULL; ++kwp) |
| 296 |
if (kwp->kwnum == num) |
| 297 |
break; |
| 298 |
|
| 299 |
return (kwp->kwprefix == NULL ? NULL : kwp); |
| 300 |
} |
| 301 |
|
| 302 |
void |
| 303 |
parser_dump(struct parser_res *entry, const struct parser_keywords *kws) |
| 304 |
{ |
| 305 |
const struct parser_keywords *kwp; |
| 306 |
|
| 307 |
kwp = parser_getkwbynum(entry->keyword, kws); |
| 308 |
if (kwp == NULL) |
| 309 |
fputs("keyword <unknown> (type invalid)", stdout); |
| 310 |
else { |
| 311 |
printf("keyword %s (type %s", /*)*/ kwp->kwprefix, |
| 312 |
entry->itype == KWT_NORMAL ? "normal" : |
| 313 |
entry->itype == KWT_MULTI ? "multi" : |
| 314 |
entry->itype == KWT_ITERATED ? "iterated" : |
| 315 |
entry->itype == KWT_MULTITER ? "multiter" : "unknown"); |
| 316 |
if (kwp->kwtype != entry->itype) |
| 317 |
printf(" orig %s", |
| 318 |
kwp->kwtype == KWT_NORMAL ? "normal" : |
| 319 |
kwp->kwtype == KWT_MULTI ? "multi" : |
| 320 |
kwp->kwtype == KWT_ITERATED ? "iterated" : |
| 321 |
kwp->kwtype == KWT_MULTITER ? "multiter" : |
| 322 |
kwp->kwtype == KWT_MULTITOP ? "multitop" : |
| 323 |
"unknown"); |
| 324 |
fputc(/*(*/ ')', stdout); |
| 325 |
if (entry->itype == KWT_ITERATED || |
| 326 |
entry->itype == KWT_MULTITER) |
| 327 |
printf(", iterator %u", entry->kw_iter); |
| 328 |
if (entry->itype == KWT_MULTI || |
| 329 |
entry->itype == KWT_MULTITER) { |
| 330 |
if (entry->kw_multi) |
| 331 |
printf(", multi '%s'", entry->kw_multi); |
| 332 |
else |
| 333 |
fputs(", multibase", stdout); |
| 334 |
} |
| 335 |
} |
| 336 |
if (entry->value) { |
| 337 |
const uint8_t *cp = entry->value; |
| 338 |
|
| 339 |
fputs(", value\n\t『", stdout); |
| 340 |
while (*cp) { |
| 341 |
while (*cp && *cp != '\n') |
| 342 |
fputc(*cp++, stdout); |
| 343 |
fputs(*cp ? (cp++, "\n\t ") : "』\n", stdout); |
| 344 |
} |
| 345 |
} else |
| 346 |
fputs(", no value\n", stdout); |
| 347 |
} |
| 348 |
|
| 349 |
void |
| 350 |
parser_free(struct parser_result *head) |
| 351 |
{ |
| 352 |
struct parser_res *entry; |
| 353 |
|
| 354 |
if (head == NULL) |
| 355 |
return; |
| 356 |
|
| 357 |
while (!CIRCLEQ_EMPTY(head)) { |
| 358 |
entry = CIRCLEQ_FIRST(head); |
| 359 |
CIRCLEQ_REMOVE(head, entry, e); |
| 360 |
if (entry->kw_multi != NULL) |
| 361 |
free(entry->kw_multi); |
| 362 |
if (entry->value != NULL) |
| 363 |
free(entry->value); |
| 364 |
free(entry); |
| 365 |
} |
| 366 |
|
| 367 |
free(head); |
| 368 |
} |
| 369 |
|
| 370 |
static void |
| 371 |
syntaxerr_(size_t lno, const char *fmt, ...) |
| 372 |
{ |
| 373 |
va_list args; |
| 374 |
|
| 375 |
va_start(args, fmt); |
| 376 |
fflush(NULL); |
| 377 |
fprintf(stderr, "syntax error [%s%s%4zu]: ", |
| 378 |
parser_errpfx ? parser_errpfx : "", |
| 379 |
parser_errpfx ? ":" : "", lno); |
| 380 |
fflush(NULL); |
| 381 |
verrx(1, fmt, args); |
| 382 |
va_end(args); |
| 383 |
} |
| 384 |
|
| 385 |
struct parser_res * |
| 386 |
parser_new(parser_kwords entry_kw, enum parser_kwtype entry_type, |
| 387 |
char *entry_multi, unsigned entry_iter, char *value) |
| 388 |
{ |
| 389 |
struct parser_res *entry; |
| 390 |
|
| 391 |
entry = xmalloc(sizeof (struct parser_res)); |
| 392 |
bzero(entry, sizeof (struct parser_res)); |
| 393 |
entry->keyword = entry_kw; |
| 394 |
entry->itype = entry_type; |
| 395 |
entry->kw_multi = entry_multi; |
| 396 |
entry->kw_iter = entry_iter; |
| 397 |
entry->value = value; |
| 398 |
return (entry); |
| 399 |
} |