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