1/* $NetBSD: citrus_prop.c,v 1.6 2022/04/19 20:32:14 rillig Exp $ */ 2 3/*- 4 * Copyright (c)2006 Citrus Project, 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 * SUCH DAMAGE. 27 * 28 */ 29 30#include <sys/cdefs.h> 31#if defined(LIBC_SCCS) && !defined(lint) 32__RCSID("$NetBSD: citrus_prop.c,v 1.6 2022/04/19 20:32:14 rillig Exp $"); 33#endif /* LIBC_SCCS and not lint */ 34 35#include <assert.h> 36#include <errno.h> 37#include <limits.h> 38#include <stdbool.h> 39#include <stddef.h> 40#include <stdio.h> 41#include <stdint.h> 42#include <stdlib.h> 43#include <string.h> 44 45#include "citrus_namespace.h" 46#include "citrus_bcs.h" 47#include "citrus_region.h" 48#include "citrus_memstream.h" 49#include "citrus_prop.h" 50 51typedef struct { 52 _citrus_prop_type_t type; 53 union { 54 const char *str; 55 int chr; 56 bool boolean; 57 uint64_t num; 58 } u; 59} _citrus_prop_object_t; 60 61static __inline void 62_citrus_prop_object_init(_citrus_prop_object_t *obj, _citrus_prop_type_t type) 63{ 64 _DIAGASSERT(obj != NULL); 65 66 obj->type = type; 67 memset(&obj->u, 0, sizeof(obj->u)); 68} 69 70static __inline void 71_citrus_prop_object_uninit(_citrus_prop_object_t *obj) 72{ 73 _DIAGASSERT(obj != NULL); 74 75 if (obj->type == _CITRUS_PROP_STR) 76 free(__UNCONST(obj->u.str)); 77} 78 79static const char *xdigit = "0123456789ABCDEF"; 80 81#define _CITRUS_PROP_READ_UINT_COMMON(_func_, _type_, _max_) \ 82static int \ 83_citrus_prop_read_##_func_##_common(struct _memstream * __restrict ms, \ 84 _type_ * __restrict result, int base) \ 85{ \ 86 _type_ acc, cutoff; \ 87 int n, ch, cutlim; \ 88 char *p; \ 89 \ 90 _DIAGASSERT(ms != NULL); \ 91 _DIAGASSERT(result != NULL); \ 92 \ 93 acc = (_type_)0; \ 94 cutoff = _max_ / base; \ 95 cutlim = _max_ % base; \ 96 for (;;) { \ 97 ch = _memstream_getc(ms); \ 98 p = strchr(xdigit, _bcs_toupper(ch)); \ 99 if (p == NULL || (n = (p - xdigit)) >= base) \ 100 break; \ 101 if (acc > cutoff || (acc == cutoff && n > cutlim)) \ 102 break; \ 103 acc *= base; \ 104 acc += n; \ 105 } \ 106 _memstream_ungetc(ms, ch); \ 107 *result = acc; \ 108 return 0; \ 109} 110_CITRUS_PROP_READ_UINT_COMMON(chr, int, UCHAR_MAX) 111_CITRUS_PROP_READ_UINT_COMMON(num, uint64_t, UINT64_MAX) 112#undef _CITRUS_PROP_READ_UINT_COMMON 113 114#define _CITRUS_PROP_READ_INT(_func_, _type_) \ 115static int \ 116_citrus_prop_read_##_func_(struct _memstream * __restrict ms, \ 117 _citrus_prop_object_t * __restrict obj) \ 118{ \ 119 int ch, neg, base; \ 120 \ 121 _DIAGASSERT(ms != NULL); \ 122 _DIAGASSERT(obj != NULL); \ 123 \ 124 _memstream_skip_ws(ms); \ 125 ch = _memstream_getc(ms); \ 126 neg = 0; \ 127 switch (ch) { \ 128 case '-': \ 129 neg = 1; \ 130 case '+': \ 131 ch = _memstream_getc(ms); \ 132 } \ 133 base = 10; \ 134 if (ch == '0') { \ 135 base -= 2; \ 136 ch = _memstream_getc(ms); \ 137 if (ch == 'x' || ch == 'X') { \ 138 ch = _memstream_getc(ms); \ 139 if (_bcs_isxdigit(ch) == 0) { \ 140 _memstream_ungetc(ms, ch); \ 141 obj->u._func_ = 0; \ 142 return 0; \ 143 } \ 144 base += 8; \ 145 } \ 146 } else if (_bcs_isdigit(ch) == 0) \ 147 return EINVAL; \ 148 _memstream_ungetc(ms, ch); \ 149 return _citrus_prop_read_##_func_##_common \ 150 (ms, &obj->u._func_, base); \ 151} 152_CITRUS_PROP_READ_INT(chr, int) 153_CITRUS_PROP_READ_INT(num, uint64_t) 154#undef _CITRUS_PROP_READ_INT 155 156static int 157_citrus_prop_read_character_common(struct _memstream * __restrict ms, 158 int * __restrict result) 159{ 160 int ch, base; 161 162 _DIAGASSERT(ms != NULL); 163 _DIAGASSERT(result != NULL); 164 165 ch = _memstream_getc(ms); 166 if (ch != '\\') { 167 *result = ch; 168 } else { 169 ch = _memstream_getc(ms); 170 base = 16; 171 switch (ch) { 172 case 'a': *result = '\a'; break; 173 case 'b': *result = '\b'; break; 174 case 'f': *result = '\f'; break; 175 case 'n': *result = '\n'; break; 176 case 'r': *result = '\r'; break; 177 case 't': *result = '\t'; break; 178 case 'v': *result = '\v'; break; 179 /*FALLTHROUGH*/ 180 case '0': case '1': case '2': case '3': 181 case '4': case '5': case '6': case '7': 182 _memstream_ungetc(ms, ch); 183 base -= 8; 184 case 'x': 185 return _citrus_prop_read_chr_common(ms, result, base); 186 187 default: 188 /* unknown escape */ 189 *result = ch; 190 } 191 } 192 return 0; 193} 194 195static int 196_citrus_prop_read_character(struct _memstream * __restrict ms, 197 _citrus_prop_object_t * __restrict obj) 198{ 199 int ch, errnum; 200 201 _DIAGASSERT(ms != NULL); 202 _DIAGASSERT(obj != NULL); 203 204 _memstream_skip_ws(ms); 205 ch = _memstream_getc(ms); 206 if (ch != '\'') { 207 _memstream_ungetc(ms, ch); 208 return _citrus_prop_read_chr(ms, obj); 209 } 210 errnum = _citrus_prop_read_character_common(ms, &ch); 211 if (errnum != 0) 212 return errnum; 213 obj->u.chr = ch; 214 ch = _memstream_getc(ms); 215 if (ch != '\'') 216 return EINVAL; 217 return 0; 218} 219 220static int 221_citrus_prop_read_bool(struct _memstream * __restrict ms, 222 _citrus_prop_object_t * __restrict obj) 223{ 224 _DIAGASSERT(ms != NULL); 225 _DIAGASSERT(obj != NULL); 226 227 _memstream_skip_ws(ms); 228 switch (_bcs_tolower(_memstream_getc(ms))) { 229 case 't': 230 if (_bcs_tolower(_memstream_getc(ms)) == 'r' && 231 _bcs_tolower(_memstream_getc(ms)) == 'u' && 232 _bcs_tolower(_memstream_getc(ms)) == 'e') { 233 obj->u.boolean = true; 234 return 0; 235 } 236 break; 237 case 'f': 238 if (_bcs_tolower(_memstream_getc(ms)) == 'a' && 239 _bcs_tolower(_memstream_getc(ms)) == 'l' && 240 _bcs_tolower(_memstream_getc(ms)) == 's' && 241 _bcs_tolower(_memstream_getc(ms)) == 'e') { 242 obj->u.boolean = false; 243 return 0; 244 } 245 } 246 return EINVAL; 247} 248 249static int 250_citrus_prop_read_str(struct _memstream * __restrict ms, 251 _citrus_prop_object_t * __restrict obj) 252{ 253 int errnum, quot, ch; 254 char *s, *t; 255#define _CITRUS_PROP_STR_BUFSIZ 512 256 size_t n, m; 257 258 _DIAGASSERT(ms != NULL); 259 _DIAGASSERT(obj != NULL); 260 261 m = _CITRUS_PROP_STR_BUFSIZ; 262 s = malloc(m); 263 if (s == NULL) 264 return ENOMEM; 265 n = 0; 266 _memstream_skip_ws(ms); 267 quot = _memstream_getc(ms); 268 switch (quot) { 269 case EOF: 270 goto done; 271 case '\\': 272 _memstream_ungetc(ms, quot); 273 quot = EOF; 274 /*FALLTHROUGH*/ 275 case '\"': case '\'': 276 break; 277 default: 278 s[n] = quot; 279 ++n, --m; 280 quot = EOF; 281 } 282 for (;;) { 283 if (m < 1) { 284 m = _CITRUS_PROP_STR_BUFSIZ; 285 t = realloc(s, n + m); 286 if (t == NULL) { 287 free(s); 288 return ENOMEM; 289 } 290 s = t; 291 } 292 ch = _memstream_getc(ms); 293 if (quot == ch || (quot == EOF && 294 (ch == ';' || _bcs_isspace(ch)))) { 295done: 296 s[n] = '\0'; 297 obj->u.str = (const char *)s; 298 return 0; 299 } 300 _memstream_ungetc(ms, ch); 301 errnum = _citrus_prop_read_character_common(ms, &ch); 302 if (errnum != 0) 303 return errnum; 304 s[n] = ch; 305 ++n, --m; 306 } 307 free(s); 308 return EINVAL; 309#undef _CITRUS_PROP_STR_BUFSIZ 310} 311 312typedef int (*_citrus_prop_read_type_t)(struct _memstream * __restrict, 313 _citrus_prop_object_t * __restrict); 314 315static const _citrus_prop_read_type_t readers[] = { 316 _citrus_prop_read_bool, 317 _citrus_prop_read_str, 318 _citrus_prop_read_character, 319 _citrus_prop_read_num, 320}; 321 322static __inline int 323_citrus_prop_read_symbol(struct _memstream * __restrict ms, 324 char * __restrict s, size_t n) 325{ 326 int ch; 327 size_t m; 328 329 _DIAGASSERT(ms != NULL); 330 _DIAGASSERT(s != NULL); 331 _DIAGASSERT(n > 0); 332 333 for (m = 0; m < n; ++m) { 334 ch = _memstream_getc(ms); 335 if (ch != '_' && _bcs_isalnum(ch) == 0) 336 goto name_found; 337 s[m] = ch; 338 } 339 ch = _memstream_getc(ms); 340 if (ch == '_' || _bcs_isalnum(ch) != 0) 341 return EINVAL; 342 343name_found: 344 _memstream_ungetc(ms, ch); 345 s[m] = '\0'; 346 347 return 0; 348} 349 350static int 351_citrus_prop_parse_element(struct _memstream * __restrict ms, 352 const _citrus_prop_hint_t * __restrict hints, 353 void * __restrict context) 354{ 355 int ch, errnum; 356#define _CITRUS_PROP_HINT_NAME_LEN_MAX 255 357 char name[_CITRUS_PROP_HINT_NAME_LEN_MAX + 1]; 358 const _citrus_prop_hint_t *hint; 359 _citrus_prop_object_t ostart, oend; 360 361 _DIAGASSERT(ms != NULL); 362 _DIAGASSERT(hints != NULL); 363 364 errnum = _citrus_prop_read_symbol(ms, name, sizeof(name)); 365 if (errnum != 0) 366 return errnum; 367 for (hint = hints; hint->name != NULL; ++hint) { 368 if (_citrus_bcs_strcasecmp(name, hint->name) == 0) 369 goto hint_found; 370 } 371 return EINVAL; 372 373hint_found: 374 _memstream_skip_ws(ms); 375 ch = _memstream_getc(ms); 376 if (ch != '=' && ch != ':') 377 _memstream_ungetc(ms, ch); 378 do { 379 _citrus_prop_object_init(&ostart, hint->type); 380 _citrus_prop_object_init(&oend, hint->type); 381 errnum = (*readers[hint->type])(ms, &ostart); 382 if (errnum != 0) 383 return errnum; 384 _memstream_skip_ws(ms); 385 ch = _memstream_getc(ms); 386 switch (hint->type) { 387 case _CITRUS_PROP_BOOL: 388 case _CITRUS_PROP_STR: 389 break; 390 default: 391 if (ch != '-') 392 break; 393 errnum = (*readers[hint->type])(ms, &oend); 394 if (errnum != 0) 395 return errnum; 396 _memstream_skip_ws(ms); 397 ch = _memstream_getc(ms); 398 } 399#define CALL0(_func_) \ 400do { \ 401 _DIAGASSERT(hint->cb._func_.func != NULL); \ 402 errnum = (*hint->cb._func_.func)(context, \ 403 hint->name, ostart.u._func_); \ 404} while (0) 405#define CALL1(_func_) \ 406do { \ 407 _DIAGASSERT(hint->cb._func_.func != NULL); \ 408 errnum = (*hint->cb._func_.func)(context, \ 409 hint->name, ostart.u._func_, oend.u._func_);\ 410} while (0) 411 412 switch (hint->type) { 413 414 case _CITRUS_PROP_BOOL: 415 CALL0(boolean); 416 break; 417 418 case _CITRUS_PROP_STR: 419 CALL0(str); 420 break; 421 422 case _CITRUS_PROP_CHR: 423 CALL1(chr); 424 break; 425 426 case _CITRUS_PROP_NUM: 427 CALL1(num); 428 break; 429 430 default: 431 abort(); 432 /*NOTREACHED*/ 433 } 434#undef CALL0 435#undef CALL1 436 _citrus_prop_object_uninit(&ostart); 437 _citrus_prop_object_uninit(&oend); 438 if (errnum != 0) 439 return errnum; 440 } while (ch == ','); 441 if (ch != ';') 442 _memstream_ungetc(ms, ch); 443 return 0; 444} 445 446int 447_citrus_prop_parse_variable(const _citrus_prop_hint_t * __restrict hints, 448 void * __restrict context, const void *var, size_t lenvar) 449{ 450 struct _memstream ms; 451 int errnum, ch; 452 453 _DIAGASSERT(hints != NULL); 454 455 _memstream_bind_ptr(&ms, __UNCONST(var), lenvar); 456 for (;;) { 457 _memstream_skip_ws(&ms); 458 ch = _memstream_getc(&ms); 459 if (ch == EOF || ch == '\0') 460 break; 461 _memstream_ungetc(&ms, ch); 462 errnum = _citrus_prop_parse_element(&ms, hints, context); 463 if (errnum != 0) 464 return errnum; 465 } 466 return 0; 467} 468