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, "ERROR")) { 327 mom->evt_type = repevt_error; 328 } else if(parse_keyword(&remain, "TRAFFIC")) { 329 mom->evt_type = repevt_traffic; 330 } else if(parse_keyword(&remain, "ASSIGN")) { 331 mom->evt_type = repevt_assign; 332 read_assign_step(remain, mom); 333 } else if(parse_keyword(&remain, "INFRA_RTT")) { 334 char *s, *m; 335 mom->evt_type = repevt_infra_rtt; 336 while(isspace((unsigned char)*remain)) 337 remain++; 338 s = remain; 339 remain = strchr(s, ' '); 340 if(!remain) fatal_exit("expected three args for INFRA_RTT"); 341 remain[0] = 0; 342 remain++; 343 while(isspace((unsigned char)*remain)) 344 remain++; 345 m = strchr(remain, ' '); 346 if(!m) fatal_exit("expected three args for INFRA_RTT"); 347 m[0] = 0; 348 m++; 349 while(isspace((unsigned char)*m)) 350 m++; 351 if(!extstrtoaddr(s, &mom->addr, &mom->addrlen)) 352 fatal_exit("bad infra_rtt address %s", s); 353 if(strlen(m)>0 && m[strlen(m)-1]=='\n') 354 m[strlen(m)-1] = 0; 355 mom->variable = strdup(remain); 356 mom->string = strdup(m); 357 if(!mom->string) fatal_exit("out of memory"); 358 if(!mom->variable) fatal_exit("out of memory"); 359 } else { 360 log_err("%d: unknown event type %s", pstate->lineno, remain); 361 free(mom); 362 return NULL; 363 } 364 while(isspace((unsigned char)*remain)) 365 remain++; 366 if(parse_keyword(&remain, "ADDRESS")) { 367 while(isspace((unsigned char)*remain)) 368 remain++; 369 if(strlen(remain) > 0) /* remove \n */ 370 remain[strlen(remain)-1] = 0; 371 if(!extstrtoaddr(remain, &mom->addr, &mom->addrlen)) { 372 log_err("line %d: could not parse ADDRESS: %s", 373 pstate->lineno, remain); 374 free(mom); 375 return NULL; 376 } 377 } 378 if(parse_keyword(&remain, "ELAPSE")) { 379 double sec; 380 errno = 0; 381 sec = strtod(remain, &remain); 382 if(sec == 0. && errno != 0) { 383 log_err("line %d: could not parse ELAPSE: %s (%s)", 384 pstate->lineno, remain, strerror(errno)); 385 free(mom); 386 return NULL; 387 } 388 #ifndef S_SPLINT_S 389 mom->elapse.tv_sec = (int)sec; 390 mom->elapse.tv_usec = (int)((sec - (double)mom->elapse.tv_sec) 391 *1000000. + 0.5); 392 #endif 393 } 394 395 if(readentry) { 396 mom->match = read_entry(in, name, pstate, 1); 397 if(!mom->match) { 398 free(mom); 399 return NULL; 400 } 401 } 402 403 return mom; 404 } 405 406 /** makes scenario with title on rest of line */ 407 static struct replay_scenario* 408 make_scenario(char* line) 409 { 410 struct replay_scenario* scen; 411 while(isspace((unsigned char)*line)) 412 line++; 413 if(!*line) { 414 log_err("scenario: no title given"); 415 return NULL; 416 } 417 scen = (struct replay_scenario*)malloc(sizeof(struct replay_scenario)); 418 if(!scen) 419 return NULL; 420 memset(scen, 0, sizeof(*scen)); 421 scen->title = strdup(line); 422 if(!scen->title) { 423 free(scen); 424 return NULL; 425 } 426 return scen; 427 } 428 429 struct replay_scenario* 430 replay_scenario_read(FILE* in, const char* name, int* lineno) 431 { 432 char line[MAX_LINE_LEN]; 433 char *parse; 434 struct replay_scenario* scen = NULL; 435 struct sldns_file_parse_state pstate; 436 line[MAX_LINE_LEN-1]=0; 437 memset(&pstate, 0, sizeof(pstate)); 438 pstate.default_ttl = 3600; 439 pstate.lineno = *lineno; 440 441 while(fgets(line, MAX_LINE_LEN-1, in)) { 442 parse=line; 443 pstate.lineno++; 444 (*lineno)++; 445 while(isspace((unsigned char)*parse)) 446 parse++; 447 if(!*parse) 448 continue; /* empty line */ 449 if(parse_keyword(&parse, ";")) 450 continue; /* comment */ 451 if(parse_keyword(&parse, "SCENARIO_BEGIN")) { 452 scen = make_scenario(parse); 453 if(!scen) 454 fatal_exit("%d: could not make scen", *lineno); 455 continue; 456 } 457 if(!scen) 458 fatal_exit("%d: expected SCENARIO", *lineno); 459 if(parse_keyword(&parse, "RANGE_BEGIN")) { 460 struct replay_range* newr = replay_range_read(parse, 461 in, name, &pstate, line); 462 if(!newr) 463 fatal_exit("%d: bad range", pstate.lineno); 464 *lineno = pstate.lineno; 465 newr->next_range = scen->range_list; 466 scen->range_list = newr; 467 } else if(parse_keyword(&parse, "STEP")) { 468 struct replay_moment* mom = replay_moment_read(parse, 469 in, name, &pstate); 470 if(!mom) 471 fatal_exit("%d: bad moment", pstate.lineno); 472 *lineno = pstate.lineno; 473 if(scen->mom_last && 474 scen->mom_last->time_step >= mom->time_step) 475 fatal_exit("%d: time goes backwards", *lineno); 476 if(scen->mom_last) 477 scen->mom_last->mom_next = mom; 478 else scen->mom_first = mom; 479 scen->mom_last = mom; 480 } else if(parse_keyword(&parse, "SCENARIO_END")) { 481 struct replay_moment *p = scen->mom_first; 482 int num = 0; 483 while(p) { 484 num++; 485 p = p->mom_next; 486 } 487 log_info("Scenario has %d steps", num); 488 return scen; 489 } 490 } 491 log_err("scenario read failed at line %d (no SCENARIO_END?)", *lineno); 492 replay_scenario_delete(scen); 493 return NULL; 494 } 495 496 void 497 replay_scenario_delete(struct replay_scenario* scen) 498 { 499 struct replay_moment* mom, *momn; 500 struct replay_range* rng, *rngn; 501 if(!scen) 502 return; 503 free(scen->title); 504 mom = scen->mom_first; 505 while(mom) { 506 momn = mom->mom_next; 507 replay_moment_delete(mom); 508 mom = momn; 509 } 510 rng = scen->range_list; 511 while(rng) { 512 rngn = rng->next_range; 513 replay_range_delete(rng); 514 rng = rngn; 515 } 516 free(scen); 517 } 518 519 /** fetch oldest timer in list that is enabled */ 520 static struct fake_timer* 521 first_timer(struct replay_runtime* runtime) 522 { 523 struct fake_timer* p, *res = NULL; 524 for(p=runtime->timer_list; p; p=p->next) { 525 if(!p->enabled) 526 continue; 527 if(!res) 528 res = p; 529 else if(timeval_smaller(&p->tv, &res->tv)) 530 res = p; 531 } 532 return res; 533 } 534 535 struct fake_timer* 536 replay_get_oldest_timer(struct replay_runtime* runtime) 537 { 538 struct fake_timer* t = first_timer(runtime); 539 if(t && timeval_smaller(&t->tv, &runtime->now_tv)) 540 return t; 541 return NULL; 542 } 543 544 int 545 replay_var_compare(const void* a, const void* b) 546 { 547 struct replay_var* x = (struct replay_var*)a; 548 struct replay_var* y = (struct replay_var*)b; 549 return strcmp(x->name, y->name); 550 } 551 552 rbtree_type* 553 macro_store_create(void) 554 { 555 return rbtree_create(&replay_var_compare); 556 } 557 558 /** helper function to delete macro values */ 559 static void 560 del_macro(rbnode_type* x, void* ATTR_UNUSED(arg)) 561 { 562 struct replay_var* v = (struct replay_var*)x; 563 free(v->name); 564 free(v->value); 565 free(v); 566 } 567 568 void 569 macro_store_delete(rbtree_type* store) 570 { 571 if(!store) 572 return; 573 traverse_postorder(store, del_macro, NULL); 574 free(store); 575 } 576 577 /** return length of macro */ 578 static size_t 579 macro_length(char* text) 580 { 581 /* we are after ${, looking for } */ 582 int depth = 0; 583 size_t len = 0; 584 while(*text) { 585 len++; 586 if(*text == '}') { 587 if(depth == 0) 588 break; 589 depth--; 590 } else if(text[0] == '$' && text[1] == '{') { 591 depth++; 592 } 593 text++; 594 } 595 return len; 596 } 597 598 /** insert new stuff at start of buffer */ 599 static int 600 do_buf_insert(char* buf, size_t remain, char* after, char* inserted) 601 { 602 char* save = strdup(after); 603 size_t len; 604 if(!save) return 0; 605 if(strlen(inserted) > remain) { 606 free(save); 607 return 0; 608 } 609 len = strlcpy(buf, inserted, remain); 610 buf += len; 611 remain -= len; 612 (void)strlcpy(buf, save, remain); 613 free(save); 614 return 1; 615 } 616 617 /** do macro recursion */ 618 static char* 619 do_macro_recursion(rbtree_type* store, struct replay_runtime* runtime, 620 char* at, size_t remain) 621 { 622 char* after = at+2; 623 char* expand = macro_expand(store, runtime, &after); 624 if(!expand) 625 return NULL; /* expansion failed */ 626 if(!do_buf_insert(at, remain, after, expand)) { 627 free(expand); 628 return NULL; 629 } 630 free(expand); 631 return at; /* and parse over the expanded text to see if again */ 632 } 633 634 /** get var from store */ 635 static struct replay_var* 636 macro_getvar(rbtree_type* store, char* name) 637 { 638 struct replay_var k; 639 k.node.key = &k; 640 k.name = name; 641 return (struct replay_var*)rbtree_search(store, &k); 642 } 643 644 /** do macro variable */ 645 static char* 646 do_macro_variable(rbtree_type* store, char* buf, size_t remain) 647 { 648 struct replay_var* v; 649 char* at = buf+1; 650 char* name = at; 651 char sv; 652 if(at[0]==0) 653 return NULL; /* no variable name after $ */ 654 while(*at && (isalnum((unsigned char)*at) || *at=='_')) { 655 at++; 656 } 657 /* terminator, we are working in macro_expand() buffer */ 658 sv = *at; 659 *at = 0; 660 v = macro_getvar(store, name); 661 *at = sv; 662 663 if(!v) { 664 log_err("variable is not defined: $%s", name); 665 return NULL; /* variable undefined is error for now */ 666 } 667 668 /* insert the variable contents */ 669 if(!do_buf_insert(buf, remain, at, v->value)) 670 return NULL; 671 return buf; /* and expand the variable contents */ 672 } 673 674 /** do ctime macro on argument */ 675 static char* 676 do_macro_ctime(char* arg) 677 { 678 char buf[32]; 679 time_t tt = (time_t)atoi(arg); 680 if(tt == 0 && strcmp(arg, "0") != 0) { 681 log_err("macro ctime: expected number, not: %s", arg); 682 return NULL; 683 } 684 ctime_r(&tt, buf); 685 if(buf[0]) buf[strlen(buf)-1]=0; /* remove trailing newline */ 686 return strdup(buf); 687 } 688 689 /** perform arithmetic operator */ 690 static double 691 perform_arith(double x, char op, double y, double* res) 692 { 693 switch(op) { 694 case '+': 695 *res = x+y; 696 break; 697 case '-': 698 *res = x-y; 699 break; 700 case '/': 701 *res = x/y; 702 break; 703 case '*': 704 *res = x*y; 705 break; 706 default: 707 return 0; 708 } 709 710 return 1; 711 } 712 713 /** do macro arithmetic on two numbers and operand */ 714 static char* 715 do_macro_arith(char* orig, size_t remain, char** arithstart) 716 { 717 double x, y, result; 718 char operator; 719 int skip; 720 char buf[32]; 721 char* at; 722 /* not yet done? we want number operand number expanded first. */ 723 if(!*arithstart) { 724 /* remember start pos of expr, skip the first number */ 725 at = orig; 726 *arithstart = at; 727 while(*at && (isdigit((unsigned char)*at) || *at == '.')) 728 at++; 729 return at; 730 } 731 /* move back to start */ 732 remain += (size_t)(orig - *arithstart); 733 at = *arithstart; 734 735 /* parse operands */ 736 if(sscanf(at, " %lf %c %lf%n", &x, &operator, &y, &skip) != 3) { 737 *arithstart = NULL; 738 return do_macro_arith(orig, remain, arithstart); 739 } 740 if(isdigit((unsigned char)operator)) { 741 *arithstart = orig; 742 return at+skip; /* do nothing, but setup for later number */ 743 } 744 745 /* calculate result */ 746 if(!perform_arith(x, operator, y, &result)) { 747 log_err("unknown operator: %s", at); 748 return NULL; 749 } 750 751 /* put result back in buffer */ 752 snprintf(buf, sizeof(buf), "%.12g", result); 753 if(!do_buf_insert(at, remain, at+skip, buf)) 754 return NULL; 755 756 /* the result can be part of another expression, restart that */ 757 *arithstart = NULL; 758 return at; 759 } 760 761 /** Do range macro on expanded buffer */ 762 static char* 763 do_macro_range(char* buf) 764 { 765 double x, y, z; 766 if(sscanf(buf, " %lf %lf %lf", &x, &y, &z) != 3) { 767 log_err("range func requires 3 args: %s", buf); 768 return NULL; 769 } 770 if(x <= y && y <= z) { 771 char res[1024]; 772 snprintf(res, sizeof(res), "%.24g", y); 773 return strdup(res); 774 } 775 fatal_exit("value %.24g not in range [%.24g, %.24g]", y, x, z); 776 return NULL; 777 } 778 779 static char* 780 macro_expand(rbtree_type* store, struct replay_runtime* runtime, char** text) 781 { 782 char buf[10240]; 783 char* at = *text; 784 size_t len = macro_length(at); 785 int dofunc = 0; 786 char* arithstart = NULL; 787 if(len >= sizeof(buf)) 788 return NULL; /* too long */ 789 buf[0] = 0; 790 (void)strlcpy(buf, at, len+1-1); /* do not copy last '}' character */ 791 at = buf; 792 793 /* check for functions */ 794 if(strcmp(buf, "time") == 0) { 795 snprintf(buf, sizeof(buf), ARG_LL "d", (long long)runtime->now_secs); 796 *text += len; 797 return strdup(buf); 798 } else if(strcmp(buf, "timeout") == 0) { 799 time_t res = 0; 800 struct fake_timer* t = first_timer(runtime); 801 if(t && (time_t)t->tv.tv_sec >= runtime->now_secs) 802 res = (time_t)t->tv.tv_sec - runtime->now_secs; 803 snprintf(buf, sizeof(buf), ARG_LL "d", (long long)res); 804 *text += len; 805 return strdup(buf); 806 } else if(strncmp(buf, "ctime ", 6) == 0 || 807 strncmp(buf, "ctime\t", 6) == 0) { 808 at += 6; 809 dofunc = 1; 810 } else if(strncmp(buf, "range ", 6) == 0 || 811 strncmp(buf, "range\t", 6) == 0) { 812 at += 6; 813 dofunc = 1; 814 } 815 816 /* actual macro text expansion */ 817 while(*at) { 818 size_t remain = sizeof(buf)-strlen(buf); 819 if(strncmp(at, "${", 2) == 0) { 820 at = do_macro_recursion(store, runtime, at, remain); 821 } else if(*at == '$') { 822 at = do_macro_variable(store, at, remain); 823 } else if(isdigit((unsigned char)*at)) { 824 at = do_macro_arith(at, remain, &arithstart); 825 } else { 826 /* copy until whitespace or operator */ 827 if(*at && (isalnum((unsigned char)*at) || *at=='_')) { 828 at++; 829 while(*at && (isalnum((unsigned char)*at) || *at=='_')) 830 at++; 831 } else at++; 832 } 833 if(!at) return NULL; /* failure */ 834 } 835 *text += len; 836 if(dofunc) { 837 /* post process functions, buf has the argument(s) */ 838 if(strncmp(buf, "ctime", 5) == 0) { 839 return do_macro_ctime(buf+6); 840 } else if(strncmp(buf, "range", 5) == 0) { 841 return do_macro_range(buf+6); 842 } 843 } 844 return strdup(buf); 845 } 846 847 char* 848 macro_process(rbtree_type* store, struct replay_runtime* runtime, char* text) 849 { 850 char buf[10240]; 851 char* next, *expand; 852 char* at = text; 853 if(!strstr(text, "${")) 854 return strdup(text); /* no macros */ 855 buf[0] = 0; 856 buf[sizeof(buf)-1]=0; 857 while( (next=strstr(at, "${")) ) { 858 /* copy text before next macro */ 859 if((size_t)(next-at) >= sizeof(buf)-strlen(buf)) 860 return NULL; /* string too long */ 861 (void)strlcpy(buf+strlen(buf), at, (size_t)(next-at+1)); 862 /* process the macro itself */ 863 next += 2; 864 expand = macro_expand(store, runtime, &next); 865 if(!expand) return NULL; /* expansion failed */ 866 (void)strlcpy(buf+strlen(buf), expand, sizeof(buf)-strlen(buf)); 867 free(expand); 868 at = next; 869 } 870 /* copy remainder fixed text */ 871 (void)strlcpy(buf+strlen(buf), at, sizeof(buf)-strlen(buf)); 872 return strdup(buf); 873 } 874 875 char* 876 macro_lookup(rbtree_type* store, char* name) 877 { 878 struct replay_var* x = macro_getvar(store, name); 879 if(!x) return strdup(""); 880 return strdup(x->value); 881 } 882 883 void macro_print_debug(rbtree_type* store) 884 { 885 struct replay_var* x; 886 RBTREE_FOR(x, struct replay_var*, store) { 887 log_info("%s = %s", x->name, x->value); 888 } 889 } 890 891 int 892 macro_assign(rbtree_type* store, char* name, char* value) 893 { 894 struct replay_var* x = macro_getvar(store, name); 895 if(x) { 896 free(x->value); 897 } else { 898 x = (struct replay_var*)malloc(sizeof(*x)); 899 if(!x) return 0; 900 x->node.key = x; 901 x->name = strdup(name); 902 if(!x->name) { 903 free(x); 904 return 0; 905 } 906 (void)rbtree_insert(store, &x->node); 907 } 908 x->value = strdup(value); 909 return x->value != NULL; 910 } 911 912 /* testbound assert function for selftest. counts the number of tests */ 913 #define tb_assert(x) \ 914 do { if(!(x)) fatal_exit("%s:%d: %s: assertion %s failed", \ 915 __FILE__, __LINE__, __func__, #x); \ 916 num_asserts++; \ 917 } while(0); 918 919 void testbound_selftest(void) 920 { 921 /* test the macro store */ 922 rbtree_type* store = macro_store_create(); 923 char* v; 924 int r; 925 int num_asserts = 0; 926 tb_assert(store); 927 928 v = macro_lookup(store, "bla"); 929 tb_assert(strcmp(v, "") == 0); 930 free(v); 931 932 v = macro_lookup(store, "vlerk"); 933 tb_assert(strcmp(v, "") == 0); 934 free(v); 935 936 r = macro_assign(store, "bla", "waarde1"); 937 tb_assert(r); 938 939 v = macro_lookup(store, "vlerk"); 940 tb_assert(strcmp(v, "") == 0); 941 free(v); 942 943 v = macro_lookup(store, "bla"); 944 tb_assert(strcmp(v, "waarde1") == 0); 945 free(v); 946 947 r = macro_assign(store, "vlerk", "kanteel"); 948 tb_assert(r); 949 950 v = macro_lookup(store, "bla"); 951 tb_assert(strcmp(v, "waarde1") == 0); 952 free(v); 953 954 v = macro_lookup(store, "vlerk"); 955 tb_assert(strcmp(v, "kanteel") == 0); 956 free(v); 957 958 r = macro_assign(store, "bla", "ww"); 959 tb_assert(r); 960 961 v = macro_lookup(store, "bla"); 962 tb_assert(strcmp(v, "ww") == 0); 963 free(v); 964 965 tb_assert( macro_length("}") == 1); 966 tb_assert( macro_length("blabla}") == 7); 967 tb_assert( macro_length("bla${zoink}bla}") == 7+8); 968 tb_assert( macro_length("bla${zoink}${bla}bla}") == 7+8+6); 969 970 v = macro_process(store, NULL, ""); 971 tb_assert( v && strcmp(v, "") == 0); 972 free(v); 973 974 v = macro_process(store, NULL, "${}"); 975 tb_assert( v && strcmp(v, "") == 0); 976 free(v); 977 978 v = macro_process(store, NULL, "blabla ${} dinges"); 979 tb_assert( v && strcmp(v, "blabla dinges") == 0); 980 free(v); 981 982 v = macro_process(store, NULL, "1${$bla}2${$bla}3"); 983 tb_assert( v && strcmp(v, "1ww2ww3") == 0); 984 free(v); 985 986 v = macro_process(store, NULL, "it is ${ctime 123456}"); 987 tb_assert( v && strcmp(v, "it is Fri Jan 2 10:17:36 1970") == 0); 988 free(v); 989 990 r = macro_assign(store, "t1", "123456"); 991 tb_assert(r); 992 v = macro_process(store, NULL, "it is ${ctime ${$t1}}"); 993 tb_assert( v && strcmp(v, "it is Fri Jan 2 10:17:36 1970") == 0); 994 free(v); 995 996 v = macro_process(store, NULL, "it is ${ctime $t1}"); 997 tb_assert( v && strcmp(v, "it is Fri Jan 2 10:17:36 1970") == 0); 998 free(v); 999 1000 r = macro_assign(store, "x", "1"); 1001 tb_assert(r); 1002 r = macro_assign(store, "y", "2"); 1003 tb_assert(r); 1004 v = macro_process(store, NULL, "${$x + $x}"); 1005 tb_assert( v && strcmp(v, "2") == 0); 1006 free(v); 1007 v = macro_process(store, NULL, "${$x - $x}"); 1008 tb_assert( v && strcmp(v, "0") == 0); 1009 free(v); 1010 v = macro_process(store, NULL, "${$y * $y}"); 1011 tb_assert( v && strcmp(v, "4") == 0); 1012 free(v); 1013 v = macro_process(store, NULL, "${32 / $y + $x + $y}"); 1014 tb_assert( v && strcmp(v, "19") == 0); 1015 free(v); 1016 1017 v = macro_process(store, NULL, "${32 / ${$y+$y} + ${${100*3}/3}}"); 1018 tb_assert( v && strcmp(v, "108") == 0); 1019 free(v); 1020 1021 v = macro_process(store, NULL, "${1 2 33 2 1}"); 1022 tb_assert( v && strcmp(v, "1 2 33 2 1") == 0); 1023 free(v); 1024 1025 v = macro_process(store, NULL, "${123 3 + 5}"); 1026 tb_assert( v && strcmp(v, "123 8") == 0); 1027 free(v); 1028 1029 v = macro_process(store, NULL, "${123 glug 3 + 5}"); 1030 tb_assert( v && strcmp(v, "123 glug 8") == 0); 1031 free(v); 1032 1033 macro_store_delete(store); 1034 printf("selftest successful (%d checks).\n", num_asserts); 1035 } 1036