1 /* 2 * testcode/replay.c - store and use a replay of events for the DNS resolver. 3 * 4 * Copyright (c) 2007, NLnet Labs. All rights reserved. 5 * 6 * This software is open source. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 12 * Redistributions of source code must retain the above copyright notice, 13 * this list of conditions and the following disclaimer. 14 * 15 * Redistributions in binary form must reproduce the above copyright notice, 16 * this list of conditions and the following disclaimer in the documentation 17 * and/or other materials provided with the distribution. 18 * 19 * Neither the name of the NLNET LABS nor the names of its contributors may 20 * be used to endorse or promote products derived from this software without 21 * specific prior written permission. 22 * 23 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 25 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 26 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 27 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 28 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 29 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 30 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 31 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 32 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 33 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 */ 35 36 /** 37 * \file 38 * Store and use a replay of events for the DNS resolver. 39 * Used to test known scenarios to get known outcomes. 40 */ 41 42 #include "config.h" 43 /* for strtod prototype */ 44 #include <math.h> 45 #include <ctype.h> 46 #include <time.h> 47 #include "util/log.h" 48 #include "util/net_help.h" 49 #include "util/config_file.h" 50 #include "testcode/replay.h" 51 #include "testcode/testpkts.h" 52 #include "testcode/fake_event.h" 53 #include "sldns/str2wire.h" 54 55 /** max length of lines in file */ 56 #define MAX_LINE_LEN 10240 57 58 /** 59 * Expand a macro 60 * @param store: value storage 61 * @param runtime: replay runtime for other stuff. 62 * @param text: the macro text, after the ${, Updated to after the } when 63 * done (successfully). 64 * @return expanded text, malloced. NULL on failure. 65 */ 66 static char* macro_expand(rbtree_type* store, 67 struct replay_runtime* runtime, char** text); 68 69 /** compare of time values */ 70 static int 71 timeval_smaller(const struct timeval* x, const struct timeval* y) 72 { 73 #ifndef S_SPLINT_S 74 if(x->tv_sec < y->tv_sec) 75 return 1; 76 else if(x->tv_sec == y->tv_sec) { 77 if(x->tv_usec <= y->tv_usec) 78 return 1; 79 else return 0; 80 } 81 else return 0; 82 #endif 83 } 84 85 /** parse keyword in string. 86 * @param line: if found, the line is advanced to after the keyword. 87 * @param keyword: string. 88 * @return: true if found, false if not. 89 */ 90 static int 91 parse_keyword(char** line, const char* keyword) 92 { 93 size_t len = (size_t)strlen(keyword); 94 if(strncmp(*line, keyword, len) == 0) { 95 *line += len; 96 return 1; 97 } 98 return 0; 99 } 100 101 /** delete moment */ 102 static void 103 replay_moment_delete(struct replay_moment* mom) 104 { 105 if(!mom) 106 return; 107 if(mom->match) { 108 delete_entry(mom->match); 109 } 110 free(mom->autotrust_id); 111 free(mom->string); 112 free(mom->variable); 113 config_delstrlist(mom->file_content); 114 free(mom); 115 } 116 117 /** delete range */ 118 static void 119 replay_range_delete(struct replay_range* rng) 120 { 121 if(!rng) 122 return; 123 delete_entry(rng->match); 124 free(rng); 125 } 126 127 /** strip whitespace from end of string */ 128 static void 129 strip_end_white(char* p) 130 { 131 size_t i; 132 for(i = strlen(p); i > 0; i--) { 133 if(isspace((unsigned char)p[i-1])) 134 p[i-1] = 0; 135 else return; 136 } 137 } 138 139 /** 140 * Read a range from file. 141 * @param remain: Rest of line (after RANGE keyword). 142 * @param in: file to read from. 143 * @param name: name to print in errors. 144 * @param pstate: read state structure with 145 * with lineno : incremented as lines are read. 146 * ttl, origin, prev for readentry. 147 * @param line: line buffer. 148 * @return: range object to add to list, or NULL on error. 149 */ 150 static struct replay_range* 151 replay_range_read(char* remain, FILE* in, const char* name, 152 struct sldns_file_parse_state* pstate, char* line) 153 { 154 struct replay_range* rng = (struct replay_range*)malloc( 155 sizeof(struct replay_range)); 156 off_t pos; 157 char *parse; 158 struct entry* entry, *last = NULL; 159 if(!rng) 160 return NULL; 161 memset(rng, 0, sizeof(*rng)); 162 /* read time range */ 163 if(sscanf(remain, " %d %d", &rng->start_step, &rng->end_step)!=2) { 164 log_err("Could not read time range: %s", line); 165 free(rng); 166 return NULL; 167 } 168 /* read entries */ 169 pos = ftello(in); 170 while(fgets(line, MAX_LINE_LEN-1, in)) { 171 pstate->lineno++; 172 parse = line; 173 while(isspace((unsigned char)*parse)) 174 parse++; 175 if(!*parse || *parse == ';') { 176 pos = ftello(in); 177 continue; 178 } 179 if(parse_keyword(&parse, "ADDRESS")) { 180 while(isspace((unsigned char)*parse)) 181 parse++; 182 strip_end_white(parse); 183 if(!extstrtoaddr(parse, &rng->addr, &rng->addrlen)) { 184 log_err("Line %d: could not read ADDRESS: %s", 185 pstate->lineno, parse); 186 free(rng); 187 return NULL; 188 } 189 pos = ftello(in); 190 continue; 191 } 192 if(parse_keyword(&parse, "RANGE_END")) { 193 return rng; 194 } 195 /* set position before line; read entry */ 196 pstate->lineno--; 197 fseeko(in, pos, SEEK_SET); 198 entry = read_entry(in, name, pstate, 1); 199 if(!entry) 200 fatal_exit("%d: bad entry", pstate->lineno); 201 entry->next = NULL; 202 if(last) 203 last->next = entry; 204 else rng->match = entry; 205 last = entry; 206 207 pos = ftello(in); 208 } 209 replay_range_delete(rng); 210 return NULL; 211 } 212 213 /** Read FILE match content */ 214 static void 215 read_file_content(FILE* in, int* lineno, struct replay_moment* mom) 216 { 217 char line[MAX_LINE_LEN]; 218 char* remain = line; 219 struct config_strlist** last = &mom->file_content; 220 line[MAX_LINE_LEN-1]=0; 221 if(!fgets(line, MAX_LINE_LEN-1, in)) 222 fatal_exit("FILE_BEGIN expected at line %d", *lineno); 223 if(!parse_keyword(&remain, "FILE_BEGIN")) 224 fatal_exit("FILE_BEGIN expected at line %d", *lineno); 225 while(fgets(line, MAX_LINE_LEN-1, in)) { 226 (*lineno)++; 227 if(strncmp(line, "FILE_END", 8) == 0) { 228 return; 229 } 230 if(line[0]) line[strlen(line)-1] = 0; /* remove newline */ 231 if(!cfg_strlist_insert(last, strdup(line))) 232 fatal_exit("malloc failure"); 233 last = &( (*last)->next ); 234 } 235 fatal_exit("no FILE_END in input file"); 236 } 237 238 /** read assign step info */ 239 static void 240 read_assign_step(char* remain, struct replay_moment* mom) 241 { 242 char buf[1024]; 243 char eq; 244 int skip; 245 buf[sizeof(buf)-1]=0; 246 if(sscanf(remain, " %1023s %c %n", buf, &eq, &skip) != 2) 247 fatal_exit("cannot parse assign: %s", remain); 248 mom->variable = strdup(buf); 249 if(eq != '=') 250 fatal_exit("no '=' in assign: %s", remain); 251 remain += skip; 252 if(remain[0]) remain[strlen(remain)-1]=0; /* remove newline */ 253 mom->string = strdup(remain); 254 if(!mom->variable || !mom->string) 255 fatal_exit("out of memory"); 256 } 257 258 /** 259 * Read a replay moment 'STEP' from file. 260 * @param remain: Rest of line (after STEP keyword). 261 * @param in: file to read from. 262 * @param name: name to print in errors. 263 * @param pstate: with lineno, ttl, origin, prev for parse state. 264 * lineno is incremented. 265 * @return: range object to add to list, or NULL on error. 266 */ 267 static struct replay_moment* 268 replay_moment_read(char* remain, FILE* in, const char* name, 269 struct sldns_file_parse_state* pstate) 270 { 271 struct replay_moment* mom = (struct replay_moment*)malloc( 272 sizeof(struct replay_moment)); 273 int skip = 0; 274 int readentry = 0; 275 if(!mom) 276 return NULL; 277 memset(mom, 0, sizeof(*mom)); 278 if(sscanf(remain, " %d%n", &mom->time_step, &skip) != 1) { 279 log_err("%d: cannot read number: %s", pstate->lineno, remain); 280 free(mom); 281 return NULL; 282 } 283 remain += skip; 284 while(isspace((unsigned char)*remain)) 285 remain++; 286 if(parse_keyword(&remain, "NOTHING")) { 287 mom->evt_type = repevt_nothing; 288 } else if(parse_keyword(&remain, "QUERY")) { 289 mom->evt_type = repevt_front_query; 290 readentry = 1; 291 if(!extstrtoaddr("127.0.0.1", &mom->addr, &mom->addrlen)) 292 fatal_exit("internal error"); 293 } else if(parse_keyword(&remain, "CHECK_ANSWER")) { 294 mom->evt_type = repevt_front_reply; 295 readentry = 1; 296 } else if(parse_keyword(&remain, "CHECK_OUT_QUERY")) { 297 mom->evt_type = repevt_back_query; 298 readentry = 1; 299 } else if(parse_keyword(&remain, "REPLY")) { 300 mom->evt_type = repevt_back_reply; 301 readentry = 1; 302 } else if(parse_keyword(&remain, "TIMEOUT")) { 303 mom->evt_type = repevt_timeout; 304 } else if(parse_keyword(&remain, "TIME_PASSES")) { 305 mom->evt_type = repevt_time_passes; 306 while(isspace((unsigned char)*remain)) 307 remain++; 308 if(parse_keyword(&remain, "EVAL")) { 309 while(isspace((unsigned char)*remain)) 310 remain++; 311 mom->string = strdup(remain); 312 if(!mom->string) fatal_exit("out of memory"); 313 if(strlen(mom->string)>0) 314 mom->string[strlen(mom->string)-1]=0; 315 remain += strlen(mom->string); 316 } 317 } else if(parse_keyword(&remain, "CHECK_AUTOTRUST")) { 318 mom->evt_type = repevt_autotrust_check; 319 while(isspace((unsigned char)*remain)) 320 remain++; 321 if(strlen(remain)>0 && remain[strlen(remain)-1]=='\n') 322 remain[strlen(remain)-1] = 0; 323 mom->autotrust_id = strdup(remain); 324 if(!mom->autotrust_id) fatal_exit("out of memory"); 325 read_file_content(in, &pstate->lineno, mom); 326 } else if(parse_keyword(&remain, "CHECK_TEMPFILE")) { 327 mom->evt_type = repevt_tempfile_check; 328 while(isspace((unsigned char)*remain)) 329 remain++; 330 if(strlen(remain)>0 && remain[strlen(remain)-1]=='\n') 331 remain[strlen(remain)-1] = 0; 332 mom->autotrust_id = strdup(remain); 333 if(!mom->autotrust_id) fatal_exit("out of memory"); 334 read_file_content(in, &pstate->lineno, mom); 335 } else if(parse_keyword(&remain, "ERROR")) { 336 mom->evt_type = repevt_error; 337 } else if(parse_keyword(&remain, "TRAFFIC")) { 338 mom->evt_type = repevt_traffic; 339 } else if(parse_keyword(&remain, "ASSIGN")) { 340 mom->evt_type = repevt_assign; 341 read_assign_step(remain, mom); 342 } else if(parse_keyword(&remain, "INFRA_RTT")) { 343 char *s, *m; 344 mom->evt_type = repevt_infra_rtt; 345 while(isspace((unsigned char)*remain)) 346 remain++; 347 s = remain; 348 remain = strchr(s, ' '); 349 if(!remain) fatal_exit("expected three args for INFRA_RTT"); 350 remain[0] = 0; 351 remain++; 352 while(isspace((unsigned char)*remain)) 353 remain++; 354 m = strchr(remain, ' '); 355 if(!m) fatal_exit("expected three args for INFRA_RTT"); 356 m[0] = 0; 357 m++; 358 while(isspace((unsigned char)*m)) 359 m++; 360 if(!extstrtoaddr(s, &mom->addr, &mom->addrlen)) 361 fatal_exit("bad infra_rtt address %s", s); 362 if(strlen(m)>0 && m[strlen(m)-1]=='\n') 363 m[strlen(m)-1] = 0; 364 mom->variable = strdup(remain); 365 mom->string = strdup(m); 366 if(!mom->string) fatal_exit("out of memory"); 367 if(!mom->variable) fatal_exit("out of memory"); 368 } else { 369 log_err("%d: unknown event type %s", pstate->lineno, remain); 370 free(mom); 371 return NULL; 372 } 373 while(isspace((unsigned char)*remain)) 374 remain++; 375 if(parse_keyword(&remain, "ADDRESS")) { 376 while(isspace((unsigned char)*remain)) 377 remain++; 378 if(strlen(remain) > 0) /* remove \n */ 379 remain[strlen(remain)-1] = 0; 380 if(!extstrtoaddr(remain, &mom->addr, &mom->addrlen)) { 381 log_err("line %d: could not parse ADDRESS: %s", 382 pstate->lineno, remain); 383 free(mom); 384 return NULL; 385 } 386 } 387 if(parse_keyword(&remain, "ELAPSE")) { 388 double sec; 389 errno = 0; 390 sec = strtod(remain, &remain); 391 if(sec == 0. && errno != 0) { 392 log_err("line %d: could not parse ELAPSE: %s (%s)", 393 pstate->lineno, remain, strerror(errno)); 394 free(mom); 395 return NULL; 396 } 397 #ifndef S_SPLINT_S 398 mom->elapse.tv_sec = (int)sec; 399 mom->elapse.tv_usec = (int)((sec - (double)mom->elapse.tv_sec) 400 *1000000. + 0.5); 401 #endif 402 } 403 404 if(readentry) { 405 mom->match = read_entry(in, name, pstate, 1); 406 if(!mom->match) { 407 free(mom); 408 return NULL; 409 } 410 } 411 412 return mom; 413 } 414 415 /** makes scenario with title on rest of line */ 416 static struct replay_scenario* 417 make_scenario(char* line) 418 { 419 struct replay_scenario* scen; 420 while(isspace((unsigned char)*line)) 421 line++; 422 if(!*line) { 423 log_err("scenario: no title given"); 424 return NULL; 425 } 426 scen = (struct replay_scenario*)malloc(sizeof(struct replay_scenario)); 427 if(!scen) 428 return NULL; 429 memset(scen, 0, sizeof(*scen)); 430 scen->title = strdup(line); 431 if(!scen->title) { 432 free(scen); 433 return NULL; 434 } 435 return scen; 436 } 437 438 struct replay_scenario* 439 replay_scenario_read(FILE* in, const char* name, int* lineno) 440 { 441 char line[MAX_LINE_LEN]; 442 char *parse; 443 struct replay_scenario* scen = NULL; 444 struct sldns_file_parse_state pstate; 445 line[MAX_LINE_LEN-1]=0; 446 memset(&pstate, 0, sizeof(pstate)); 447 pstate.default_ttl = 3600; 448 pstate.lineno = *lineno; 449 450 while(fgets(line, MAX_LINE_LEN-1, in)) { 451 parse=line; 452 pstate.lineno++; 453 (*lineno)++; 454 while(isspace((unsigned char)*parse)) 455 parse++; 456 if(!*parse) 457 continue; /* empty line */ 458 if(parse_keyword(&parse, ";")) 459 continue; /* comment */ 460 if(parse_keyword(&parse, "SCENARIO_BEGIN")) { 461 scen = make_scenario(parse); 462 if(!scen) 463 fatal_exit("%d: could not make scen", *lineno); 464 continue; 465 } 466 if(!scen) 467 fatal_exit("%d: expected SCENARIO", *lineno); 468 if(parse_keyword(&parse, "RANGE_BEGIN")) { 469 struct replay_range* newr = replay_range_read(parse, 470 in, name, &pstate, line); 471 if(!newr) 472 fatal_exit("%d: bad range", pstate.lineno); 473 *lineno = pstate.lineno; 474 newr->next_range = scen->range_list; 475 scen->range_list = newr; 476 } else if(parse_keyword(&parse, "STEP")) { 477 struct replay_moment* mom = replay_moment_read(parse, 478 in, name, &pstate); 479 if(!mom) 480 fatal_exit("%d: bad moment", pstate.lineno); 481 *lineno = pstate.lineno; 482 if(scen->mom_last && 483 scen->mom_last->time_step >= mom->time_step) 484 fatal_exit("%d: time goes backwards", *lineno); 485 if(scen->mom_last) 486 scen->mom_last->mom_next = mom; 487 else scen->mom_first = mom; 488 scen->mom_last = mom; 489 } else if(parse_keyword(&parse, "SCENARIO_END")) { 490 struct replay_moment *p = scen->mom_first; 491 int num = 0; 492 while(p) { 493 num++; 494 p = p->mom_next; 495 } 496 log_info("Scenario has %d steps", num); 497 return scen; 498 } 499 } 500 log_err("scenario read failed at line %d (no SCENARIO_END?)", *lineno); 501 replay_scenario_delete(scen); 502 return NULL; 503 } 504 505 void 506 replay_scenario_delete(struct replay_scenario* scen) 507 { 508 struct replay_moment* mom, *momn; 509 struct replay_range* rng, *rngn; 510 if(!scen) 511 return; 512 free(scen->title); 513 mom = scen->mom_first; 514 while(mom) { 515 momn = mom->mom_next; 516 replay_moment_delete(mom); 517 mom = momn; 518 } 519 rng = scen->range_list; 520 while(rng) { 521 rngn = rng->next_range; 522 replay_range_delete(rng); 523 rng = rngn; 524 } 525 free(scen); 526 } 527 528 /** fetch oldest timer in list that is enabled */ 529 static struct fake_timer* 530 first_timer(struct replay_runtime* runtime) 531 { 532 struct fake_timer* p, *res = NULL; 533 for(p=runtime->timer_list; p; p=p->next) { 534 if(!p->enabled) 535 continue; 536 if(!res) 537 res = p; 538 else if(timeval_smaller(&p->tv, &res->tv)) 539 res = p; 540 } 541 return res; 542 } 543 544 struct fake_timer* 545 replay_get_oldest_timer(struct replay_runtime* runtime) 546 { 547 struct fake_timer* t = first_timer(runtime); 548 if(t && timeval_smaller(&t->tv, &runtime->now_tv)) 549 return t; 550 return NULL; 551 } 552 553 int 554 replay_var_compare(const void* a, const void* b) 555 { 556 struct replay_var* x = (struct replay_var*)a; 557 struct replay_var* y = (struct replay_var*)b; 558 return strcmp(x->name, y->name); 559 } 560 561 rbtree_type* 562 macro_store_create(void) 563 { 564 return rbtree_create(&replay_var_compare); 565 } 566 567 /** helper function to delete macro values */ 568 static void 569 del_macro(rbnode_type* x, void* ATTR_UNUSED(arg)) 570 { 571 struct replay_var* v = (struct replay_var*)x; 572 free(v->name); 573 free(v->value); 574 free(v); 575 } 576 577 void 578 macro_store_delete(rbtree_type* store) 579 { 580 if(!store) 581 return; 582 traverse_postorder(store, del_macro, NULL); 583 free(store); 584 } 585 586 /** return length of macro */ 587 static size_t 588 macro_length(char* text) 589 { 590 /* we are after ${, looking for } */ 591 int depth = 0; 592 size_t len = 0; 593 while(*text) { 594 len++; 595 if(*text == '}') { 596 if(depth == 0) 597 break; 598 depth--; 599 } else if(text[0] == '$' && text[1] == '{') { 600 depth++; 601 } 602 text++; 603 } 604 return len; 605 } 606 607 /** insert new stuff at start of buffer */ 608 static int 609 do_buf_insert(char* buf, size_t remain, char* after, char* inserted) 610 { 611 char* save = strdup(after); 612 size_t len; 613 if(!save) return 0; 614 if(strlen(inserted) > remain) { 615 free(save); 616 return 0; 617 } 618 len = strlcpy(buf, inserted, remain); 619 buf += len; 620 remain -= len; 621 (void)strlcpy(buf, save, remain); 622 free(save); 623 return 1; 624 } 625 626 /** do macro recursion */ 627 static char* 628 do_macro_recursion(rbtree_type* store, struct replay_runtime* runtime, 629 char* at, size_t remain) 630 { 631 char* after = at+2; 632 char* expand = macro_expand(store, runtime, &after); 633 if(!expand) 634 return NULL; /* expansion failed */ 635 if(!do_buf_insert(at, remain, after, expand)) { 636 free(expand); 637 return NULL; 638 } 639 free(expand); 640 return at; /* and parse over the expanded text to see if again */ 641 } 642 643 /** get var from store */ 644 static struct replay_var* 645 macro_getvar(rbtree_type* store, char* name) 646 { 647 struct replay_var k; 648 k.node.key = &k; 649 k.name = name; 650 return (struct replay_var*)rbtree_search(store, &k); 651 } 652 653 /** do macro variable */ 654 static char* 655 do_macro_variable(rbtree_type* store, char* buf, size_t remain) 656 { 657 struct replay_var* v; 658 char* at = buf+1; 659 char* name = at; 660 char sv; 661 if(at[0]==0) 662 return NULL; /* no variable name after $ */ 663 while(*at && (isalnum((unsigned char)*at) || *at=='_')) { 664 at++; 665 } 666 /* terminator, we are working in macro_expand() buffer */ 667 sv = *at; 668 *at = 0; 669 v = macro_getvar(store, name); 670 *at = sv; 671 672 if(!v) { 673 log_err("variable is not defined: $%s", name); 674 return NULL; /* variable undefined is error for now */ 675 } 676 677 /* insert the variable contents */ 678 if(!do_buf_insert(buf, remain, at, v->value)) 679 return NULL; 680 return buf; /* and expand the variable contents */ 681 } 682 683 /** do ctime macro on argument */ 684 static char* 685 do_macro_ctime(char* arg) 686 { 687 char buf[32]; 688 time_t tt = (time_t)atoi(arg); 689 if(tt == 0 && strcmp(arg, "0") != 0) { 690 log_err("macro ctime: expected number, not: %s", arg); 691 return NULL; 692 } 693 ctime_r(&tt, buf); 694 if(buf[0]) buf[strlen(buf)-1]=0; /* remove trailing newline */ 695 return strdup(buf); 696 } 697 698 /** perform arithmetic operator */ 699 static double 700 perform_arith(double x, char op, double y, double* res) 701 { 702 switch(op) { 703 case '+': 704 *res = x+y; 705 break; 706 case '-': 707 *res = x-y; 708 break; 709 case '/': 710 *res = x/y; 711 break; 712 case '*': 713 *res = x*y; 714 break; 715 default: 716 return 0; 717 } 718 719 return 1; 720 } 721 722 /** do macro arithmetic on two numbers and operand */ 723 static char* 724 do_macro_arith(char* orig, size_t remain, char** arithstart) 725 { 726 double x, y, result; 727 char operator; 728 int skip; 729 char buf[32]; 730 char* at; 731 /* not yet done? we want number operand number expanded first. */ 732 if(!*arithstart) { 733 /* remember start pos of expr, skip the first number */ 734 at = orig; 735 *arithstart = at; 736 while(*at && (isdigit((unsigned char)*at) || *at == '.')) 737 at++; 738 return at; 739 } 740 /* move back to start */ 741 remain += (size_t)(orig - *arithstart); 742 at = *arithstart; 743 744 /* parse operands */ 745 if(sscanf(at, " %lf %c %lf%n", &x, &operator, &y, &skip) != 3) { 746 *arithstart = NULL; 747 return do_macro_arith(orig, remain, arithstart); 748 } 749 if(isdigit((unsigned char)operator)) { 750 *arithstart = orig; 751 return at+skip; /* do nothing, but setup for later number */ 752 } 753 754 /* calculate result */ 755 if(!perform_arith(x, operator, y, &result)) { 756 log_err("unknown operator: %s", at); 757 return NULL; 758 } 759 760 /* put result back in buffer */ 761 snprintf(buf, sizeof(buf), "%.12g", result); 762 if(!do_buf_insert(at, remain, at+skip, buf)) 763 return NULL; 764 765 /* the result can be part of another expression, restart that */ 766 *arithstart = NULL; 767 return at; 768 } 769 770 /** Do range macro on expanded buffer */ 771 static char* 772 do_macro_range(char* buf) 773 { 774 double x, y, z; 775 if(sscanf(buf, " %lf %lf %lf", &x, &y, &z) != 3) { 776 log_err("range func requires 3 args: %s", buf); 777 return NULL; 778 } 779 if(x <= y && y <= z) { 780 char res[1024]; 781 snprintf(res, sizeof(res), "%.24g", y); 782 return strdup(res); 783 } 784 fatal_exit("value %.24g not in range [%.24g, %.24g]", y, x, z); 785 return NULL; 786 } 787 788 static char* 789 macro_expand(rbtree_type* store, struct replay_runtime* runtime, char** text) 790 { 791 char buf[10240]; 792 char* at = *text; 793 size_t len = macro_length(at); 794 int dofunc = 0; 795 char* arithstart = NULL; 796 if(len >= sizeof(buf)) 797 return NULL; /* too long */ 798 buf[0] = 0; 799 (void)strlcpy(buf, at, len+1-1); /* do not copy last '}' character */ 800 at = buf; 801 802 /* check for functions */ 803 if(strcmp(buf, "time") == 0) { 804 snprintf(buf, sizeof(buf), ARG_LL "d", (long long)runtime->now_secs); 805 *text += len; 806 return strdup(buf); 807 } else if(strcmp(buf, "timeout") == 0) { 808 time_t res = 0; 809 struct fake_timer* t = first_timer(runtime); 810 if(t && (time_t)t->tv.tv_sec >= runtime->now_secs) 811 res = (time_t)t->tv.tv_sec - runtime->now_secs; 812 snprintf(buf, sizeof(buf), ARG_LL "d", (long long)res); 813 *text += len; 814 return strdup(buf); 815 } else if(strncmp(buf, "ctime ", 6) == 0 || 816 strncmp(buf, "ctime\t", 6) == 0) { 817 at += 6; 818 dofunc = 1; 819 } else if(strncmp(buf, "range ", 6) == 0 || 820 strncmp(buf, "range\t", 6) == 0) { 821 at += 6; 822 dofunc = 1; 823 } 824 825 /* actual macro text expansion */ 826 while(*at) { 827 size_t remain = sizeof(buf)-strlen(buf); 828 if(strncmp(at, "${", 2) == 0) { 829 at = do_macro_recursion(store, runtime, at, remain); 830 } else if(*at == '$') { 831 at = do_macro_variable(store, at, remain); 832 } else if(isdigit((unsigned char)*at)) { 833 at = do_macro_arith(at, remain, &arithstart); 834 } else { 835 /* copy until whitespace or operator */ 836 if(*at && (isalnum((unsigned char)*at) || *at=='_')) { 837 at++; 838 while(*at && (isalnum((unsigned char)*at) || *at=='_')) 839 at++; 840 } else at++; 841 } 842 if(!at) return NULL; /* failure */ 843 } 844 *text += len; 845 if(dofunc) { 846 /* post process functions, buf has the argument(s) */ 847 if(strncmp(buf, "ctime", 5) == 0) { 848 return do_macro_ctime(buf+6); 849 } else if(strncmp(buf, "range", 5) == 0) { 850 return do_macro_range(buf+6); 851 } 852 } 853 return strdup(buf); 854 } 855 856 char* 857 macro_process(rbtree_type* store, struct replay_runtime* runtime, char* text) 858 { 859 char buf[10240]; 860 char* next, *expand; 861 char* at = text; 862 if(!strstr(text, "${")) 863 return strdup(text); /* no macros */ 864 buf[0] = 0; 865 buf[sizeof(buf)-1]=0; 866 while( (next=strstr(at, "${")) ) { 867 /* copy text before next macro */ 868 if((size_t)(next-at) >= sizeof(buf)-strlen(buf)) 869 return NULL; /* string too long */ 870 (void)strlcpy(buf+strlen(buf), at, (size_t)(next-at+1)); 871 /* process the macro itself */ 872 next += 2; 873 expand = macro_expand(store, runtime, &next); 874 if(!expand) return NULL; /* expansion failed */ 875 (void)strlcpy(buf+strlen(buf), expand, sizeof(buf)-strlen(buf)); 876 free(expand); 877 at = next; 878 } 879 /* copy remainder fixed text */ 880 (void)strlcpy(buf+strlen(buf), at, sizeof(buf)-strlen(buf)); 881 return strdup(buf); 882 } 883 884 char* 885 macro_lookup(rbtree_type* store, char* name) 886 { 887 struct replay_var* x = macro_getvar(store, name); 888 if(!x) return strdup(""); 889 return strdup(x->value); 890 } 891 892 void macro_print_debug(rbtree_type* store) 893 { 894 struct replay_var* x; 895 RBTREE_FOR(x, struct replay_var*, store) { 896 log_info("%s = %s", x->name, x->value); 897 } 898 } 899 900 int 901 macro_assign(rbtree_type* store, char* name, char* value) 902 { 903 struct replay_var* x = macro_getvar(store, name); 904 if(x) { 905 free(x->value); 906 } else { 907 x = (struct replay_var*)malloc(sizeof(*x)); 908 if(!x) return 0; 909 x->node.key = x; 910 x->name = strdup(name); 911 if(!x->name) { 912 free(x); 913 return 0; 914 } 915 (void)rbtree_insert(store, &x->node); 916 } 917 x->value = strdup(value); 918 return x->value != NULL; 919 } 920 921 /* testbound assert function for selftest. counts the number of tests */ 922 #define tb_assert(x) \ 923 do { if(!(x)) fatal_exit("%s:%d: %s: assertion %s failed", \ 924 __FILE__, __LINE__, __func__, #x); \ 925 num_asserts++; \ 926 } while(0); 927 928 void testbound_selftest(void) 929 { 930 /* test the macro store */ 931 rbtree_type* store = macro_store_create(); 932 char* v; 933 int r; 934 int num_asserts = 0; 935 tb_assert(store); 936 937 v = macro_lookup(store, "bla"); 938 tb_assert(strcmp(v, "") == 0); 939 free(v); 940 941 v = macro_lookup(store, "vlerk"); 942 tb_assert(strcmp(v, "") == 0); 943 free(v); 944 945 r = macro_assign(store, "bla", "waarde1"); 946 tb_assert(r); 947 948 v = macro_lookup(store, "vlerk"); 949 tb_assert(strcmp(v, "") == 0); 950 free(v); 951 952 v = macro_lookup(store, "bla"); 953 tb_assert(strcmp(v, "waarde1") == 0); 954 free(v); 955 956 r = macro_assign(store, "vlerk", "kanteel"); 957 tb_assert(r); 958 959 v = macro_lookup(store, "bla"); 960 tb_assert(strcmp(v, "waarde1") == 0); 961 free(v); 962 963 v = macro_lookup(store, "vlerk"); 964 tb_assert(strcmp(v, "kanteel") == 0); 965 free(v); 966 967 r = macro_assign(store, "bla", "ww"); 968 tb_assert(r); 969 970 v = macro_lookup(store, "bla"); 971 tb_assert(strcmp(v, "ww") == 0); 972 free(v); 973 974 tb_assert( macro_length("}") == 1); 975 tb_assert( macro_length("blabla}") == 7); 976 tb_assert( macro_length("bla${zoink}bla}") == 7+8); 977 tb_assert( macro_length("bla${zoink}${bla}bla}") == 7+8+6); 978 979 v = macro_process(store, NULL, ""); 980 tb_assert( v && strcmp(v, "") == 0); 981 free(v); 982 983 v = macro_process(store, NULL, "${}"); 984 tb_assert( v && strcmp(v, "") == 0); 985 free(v); 986 987 v = macro_process(store, NULL, "blabla ${} dinges"); 988 tb_assert( v && strcmp(v, "blabla dinges") == 0); 989 free(v); 990 991 v = macro_process(store, NULL, "1${$bla}2${$bla}3"); 992 tb_assert( v && strcmp(v, "1ww2ww3") == 0); 993 free(v); 994 995 v = macro_process(store, NULL, "it is ${ctime 123456}"); 996 tb_assert( v && strcmp(v, "it is Fri Jan 2 10:17:36 1970") == 0); 997 free(v); 998 999 r = macro_assign(store, "t1", "123456"); 1000 tb_assert(r); 1001 v = macro_process(store, NULL, "it is ${ctime ${$t1}}"); 1002 tb_assert( v && strcmp(v, "it is Fri Jan 2 10:17:36 1970") == 0); 1003 free(v); 1004 1005 v = macro_process(store, NULL, "it is ${ctime $t1}"); 1006 tb_assert( v && strcmp(v, "it is Fri Jan 2 10:17:36 1970") == 0); 1007 free(v); 1008 1009 r = macro_assign(store, "x", "1"); 1010 tb_assert(r); 1011 r = macro_assign(store, "y", "2"); 1012 tb_assert(r); 1013 v = macro_process(store, NULL, "${$x + $x}"); 1014 tb_assert( v && strcmp(v, "2") == 0); 1015 free(v); 1016 v = macro_process(store, NULL, "${$x - $x}"); 1017 tb_assert( v && strcmp(v, "0") == 0); 1018 free(v); 1019 v = macro_process(store, NULL, "${$y * $y}"); 1020 tb_assert( v && strcmp(v, "4") == 0); 1021 free(v); 1022 v = macro_process(store, NULL, "${32 / $y + $x + $y}"); 1023 tb_assert( v && strcmp(v, "19") == 0); 1024 free(v); 1025 1026 v = macro_process(store, NULL, "${32 / ${$y+$y} + ${${100*3}/3}}"); 1027 tb_assert( v && strcmp(v, "108") == 0); 1028 free(v); 1029 1030 v = macro_process(store, NULL, "${1 2 33 2 1}"); 1031 tb_assert( v && strcmp(v, "1 2 33 2 1") == 0); 1032 free(v); 1033 1034 v = macro_process(store, NULL, "${123 3 + 5}"); 1035 tb_assert( v && strcmp(v, "123 8") == 0); 1036 free(v); 1037 1038 v = macro_process(store, NULL, "${123 glug 3 + 5}"); 1039 tb_assert( v && strcmp(v, "123 glug 8") == 0); 1040 free(v); 1041 1042 macro_store_delete(store); 1043 printf("selftest successful (%d checks).\n", num_asserts); 1044 } 1045