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_t* 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 replay_scenario_delete(scen); 492 return NULL; 493 } 494 495 void 496 replay_scenario_delete(struct replay_scenario* scen) 497 { 498 struct replay_moment* mom, *momn; 499 struct replay_range* rng, *rngn; 500 if(!scen) 501 return; 502 free(scen->title); 503 mom = scen->mom_first; 504 while(mom) { 505 momn = mom->mom_next; 506 replay_moment_delete(mom); 507 mom = momn; 508 } 509 rng = scen->range_list; 510 while(rng) { 511 rngn = rng->next_range; 512 replay_range_delete(rng); 513 rng = rngn; 514 } 515 free(scen); 516 } 517 518 /** fetch oldest timer in list that is enabled */ 519 static struct fake_timer* 520 first_timer(struct replay_runtime* runtime) 521 { 522 struct fake_timer* p, *res = NULL; 523 for(p=runtime->timer_list; p; p=p->next) { 524 if(!p->enabled) 525 continue; 526 if(!res) 527 res = p; 528 else if(timeval_smaller(&p->tv, &res->tv)) 529 res = p; 530 } 531 return res; 532 } 533 534 struct fake_timer* 535 replay_get_oldest_timer(struct replay_runtime* runtime) 536 { 537 struct fake_timer* t = first_timer(runtime); 538 if(t && timeval_smaller(&t->tv, &runtime->now_tv)) 539 return t; 540 return NULL; 541 } 542 543 int 544 replay_var_compare(const void* a, const void* b) 545 { 546 struct replay_var* x = (struct replay_var*)a; 547 struct replay_var* y = (struct replay_var*)b; 548 return strcmp(x->name, y->name); 549 } 550 551 rbtree_t* 552 macro_store_create(void) 553 { 554 return rbtree_create(&replay_var_compare); 555 } 556 557 /** helper function to delete macro values */ 558 static void 559 del_macro(rbnode_t* x, void* ATTR_UNUSED(arg)) 560 { 561 struct replay_var* v = (struct replay_var*)x; 562 free(v->name); 563 free(v->value); 564 free(v); 565 } 566 567 void 568 macro_store_delete(rbtree_t* store) 569 { 570 if(!store) 571 return; 572 traverse_postorder(store, del_macro, NULL); 573 free(store); 574 } 575 576 /** return length of macro */ 577 static size_t 578 macro_length(char* text) 579 { 580 /* we are after ${, looking for } */ 581 int depth = 0; 582 size_t len = 0; 583 while(*text) { 584 len++; 585 if(*text == '}') { 586 if(depth == 0) 587 break; 588 depth--; 589 } else if(text[0] == '$' && text[1] == '{') { 590 depth++; 591 } 592 text++; 593 } 594 return len; 595 } 596 597 /** insert new stuff at start of buffer */ 598 static int 599 do_buf_insert(char* buf, size_t remain, char* after, char* inserted) 600 { 601 char* save = strdup(after); 602 size_t len; 603 if(!save) return 0; 604 if(strlen(inserted) > remain) { 605 free(save); 606 return 0; 607 } 608 len = strlcpy(buf, inserted, remain); 609 buf += len; 610 remain -= len; 611 (void)strlcpy(buf, save, remain); 612 free(save); 613 return 1; 614 } 615 616 /** do macro recursion */ 617 static char* 618 do_macro_recursion(rbtree_t* store, struct replay_runtime* runtime, 619 char* at, size_t remain) 620 { 621 char* after = at+2; 622 char* expand = macro_expand(store, runtime, &after); 623 if(!expand) 624 return NULL; /* expansion failed */ 625 if(!do_buf_insert(at, remain, after, expand)) { 626 free(expand); 627 return NULL; 628 } 629 free(expand); 630 return at; /* and parse over the expanded text to see if again */ 631 } 632 633 /** get var from store */ 634 static struct replay_var* 635 macro_getvar(rbtree_t* store, char* name) 636 { 637 struct replay_var k; 638 k.node.key = &k; 639 k.name = name; 640 return (struct replay_var*)rbtree_search(store, &k); 641 } 642 643 /** do macro variable */ 644 static char* 645 do_macro_variable(rbtree_t* store, char* buf, size_t remain) 646 { 647 struct replay_var* v; 648 char* at = buf+1; 649 char* name = at; 650 char sv; 651 if(at[0]==0) 652 return NULL; /* no variable name after $ */ 653 while(*at && (isalnum((unsigned char)*at) || *at=='_')) { 654 at++; 655 } 656 /* terminator, we are working in macro_expand() buffer */ 657 sv = *at; 658 *at = 0; 659 v = macro_getvar(store, name); 660 *at = sv; 661 662 if(!v) { 663 log_err("variable is not defined: $%s", name); 664 return NULL; /* variable undefined is error for now */ 665 } 666 667 /* insert the variable contents */ 668 if(!do_buf_insert(buf, remain, at, v->value)) 669 return NULL; 670 return buf; /* and expand the variable contents */ 671 } 672 673 /** do ctime macro on argument */ 674 static char* 675 do_macro_ctime(char* arg) 676 { 677 char buf[32]; 678 time_t tt = (time_t)atoi(arg); 679 if(tt == 0 && strcmp(arg, "0") != 0) { 680 log_err("macro ctime: expected number, not: %s", arg); 681 return NULL; 682 } 683 ctime_r(&tt, buf); 684 if(buf[0]) buf[strlen(buf)-1]=0; /* remove trailing newline */ 685 return strdup(buf); 686 } 687 688 /** perform arithmetic operator */ 689 static double 690 perform_arith(double x, char op, double y, double* res) 691 { 692 switch(op) { 693 case '+': 694 *res = x+y; 695 break; 696 case '-': 697 *res = x-y; 698 break; 699 case '/': 700 *res = x/y; 701 break; 702 case '*': 703 *res = x*y; 704 break; 705 default: 706 return 0; 707 } 708 709 return 1; 710 } 711 712 /** do macro arithmetic on two numbers and operand */ 713 static char* 714 do_macro_arith(char* orig, size_t remain, char** arithstart) 715 { 716 double x, y, result; 717 char operator; 718 int skip; 719 char buf[32]; 720 char* at; 721 /* not yet done? we want number operand number expanded first. */ 722 if(!*arithstart) { 723 /* remember start pos of expr, skip the first number */ 724 at = orig; 725 *arithstart = at; 726 while(*at && (isdigit((unsigned char)*at) || *at == '.')) 727 at++; 728 return at; 729 } 730 /* move back to start */ 731 remain += (size_t)(orig - *arithstart); 732 at = *arithstart; 733 734 /* parse operands */ 735 if(sscanf(at, " %lf %c %lf%n", &x, &operator, &y, &skip) != 3) { 736 *arithstart = NULL; 737 return do_macro_arith(orig, remain, arithstart); 738 } 739 if(isdigit((unsigned char)operator)) { 740 *arithstart = orig; 741 return at+skip; /* do nothing, but setup for later number */ 742 } 743 744 /* calculate result */ 745 if(!perform_arith(x, operator, y, &result)) { 746 log_err("unknown operator: %s", at); 747 return NULL; 748 } 749 750 /* put result back in buffer */ 751 snprintf(buf, sizeof(buf), "%.12g", result); 752 if(!do_buf_insert(at, remain, at+skip, buf)) 753 return NULL; 754 755 /* the result can be part of another expression, restart that */ 756 *arithstart = NULL; 757 return at; 758 } 759 760 /** Do range macro on expanded buffer */ 761 static char* 762 do_macro_range(char* buf) 763 { 764 double x, y, z; 765 if(sscanf(buf, " %lf %lf %lf", &x, &y, &z) != 3) { 766 log_err("range func requires 3 args: %s", buf); 767 return NULL; 768 } 769 if(x <= y && y <= z) { 770 char res[1024]; 771 snprintf(res, sizeof(res), "%.24g", y); 772 return strdup(res); 773 } 774 fatal_exit("value %.24g not in range [%.24g, %.24g]", y, x, z); 775 return NULL; 776 } 777 778 static char* 779 macro_expand(rbtree_t* store, struct replay_runtime* runtime, char** text) 780 { 781 char buf[10240]; 782 char* at = *text; 783 size_t len = macro_length(at); 784 int dofunc = 0; 785 char* arithstart = NULL; 786 if(len >= sizeof(buf)) 787 return NULL; /* too long */ 788 buf[0] = 0; 789 (void)strlcpy(buf, at, len+1-1); /* do not copy last '}' character */ 790 at = buf; 791 792 /* check for functions */ 793 if(strcmp(buf, "time") == 0) { 794 snprintf(buf, sizeof(buf), ARG_LL "d", (long long)runtime->now_secs); 795 *text += len; 796 return strdup(buf); 797 } else if(strcmp(buf, "timeout") == 0) { 798 time_t res = 0; 799 struct fake_timer* t = first_timer(runtime); 800 if(t && (time_t)t->tv.tv_sec >= runtime->now_secs) 801 res = (time_t)t->tv.tv_sec - runtime->now_secs; 802 snprintf(buf, sizeof(buf), ARG_LL "d", (long long)res); 803 *text += len; 804 return strdup(buf); 805 } else if(strncmp(buf, "ctime ", 6) == 0 || 806 strncmp(buf, "ctime\t", 6) == 0) { 807 at += 6; 808 dofunc = 1; 809 } else if(strncmp(buf, "range ", 6) == 0 || 810 strncmp(buf, "range\t", 6) == 0) { 811 at += 6; 812 dofunc = 1; 813 } 814 815 /* actual macro text expansion */ 816 while(*at) { 817 size_t remain = sizeof(buf)-strlen(buf); 818 if(strncmp(at, "${", 2) == 0) { 819 at = do_macro_recursion(store, runtime, at, remain); 820 } else if(*at == '$') { 821 at = do_macro_variable(store, at, remain); 822 } else if(isdigit((unsigned char)*at)) { 823 at = do_macro_arith(at, remain, &arithstart); 824 } else { 825 /* copy until whitespace or operator */ 826 if(*at && (isalnum((unsigned char)*at) || *at=='_')) { 827 at++; 828 while(*at && (isalnum((unsigned char)*at) || *at=='_')) 829 at++; 830 } else at++; 831 } 832 if(!at) return NULL; /* failure */ 833 } 834 *text += len; 835 if(dofunc) { 836 /* post process functions, buf has the argument(s) */ 837 if(strncmp(buf, "ctime", 5) == 0) { 838 return do_macro_ctime(buf+6); 839 } else if(strncmp(buf, "range", 5) == 0) { 840 return do_macro_range(buf+6); 841 } 842 } 843 return strdup(buf); 844 } 845 846 char* 847 macro_process(rbtree_t* store, struct replay_runtime* runtime, char* text) 848 { 849 char buf[10240]; 850 char* next, *expand; 851 char* at = text; 852 if(!strstr(text, "${")) 853 return strdup(text); /* no macros */ 854 buf[0] = 0; 855 buf[sizeof(buf)-1]=0; 856 while( (next=strstr(at, "${")) ) { 857 /* copy text before next macro */ 858 if((size_t)(next-at) >= sizeof(buf)-strlen(buf)) 859 return NULL; /* string too long */ 860 (void)strlcpy(buf+strlen(buf), at, (size_t)(next-at+1)); 861 /* process the macro itself */ 862 next += 2; 863 expand = macro_expand(store, runtime, &next); 864 if(!expand) return NULL; /* expansion failed */ 865 (void)strlcpy(buf+strlen(buf), expand, sizeof(buf)-strlen(buf)); 866 free(expand); 867 at = next; 868 } 869 /* copy remainder fixed text */ 870 (void)strlcpy(buf+strlen(buf), at, sizeof(buf)-strlen(buf)); 871 return strdup(buf); 872 } 873 874 char* 875 macro_lookup(rbtree_t* store, char* name) 876 { 877 struct replay_var* x = macro_getvar(store, name); 878 if(!x) return strdup(""); 879 return strdup(x->value); 880 } 881 882 void macro_print_debug(rbtree_t* store) 883 { 884 struct replay_var* x; 885 RBTREE_FOR(x, struct replay_var*, store) { 886 log_info("%s = %s", x->name, x->value); 887 } 888 } 889 890 int 891 macro_assign(rbtree_t* store, char* name, char* value) 892 { 893 struct replay_var* x = macro_getvar(store, name); 894 if(x) { 895 free(x->value); 896 } else { 897 x = (struct replay_var*)malloc(sizeof(*x)); 898 if(!x) return 0; 899 x->node.key = x; 900 x->name = strdup(name); 901 if(!x->name) { 902 free(x); 903 return 0; 904 } 905 (void)rbtree_insert(store, &x->node); 906 } 907 x->value = strdup(value); 908 return x->value != NULL; 909 } 910 911 /* testbound assert function for selftest. counts the number of tests */ 912 #define tb_assert(x) \ 913 do { if(!(x)) fatal_exit("%s:%d: %s: assertion %s failed", \ 914 __FILE__, __LINE__, __func__, #x); \ 915 num_asserts++; \ 916 } while(0); 917 918 void testbound_selftest(void) 919 { 920 /* test the macro store */ 921 rbtree_t* store = macro_store_create(); 922 char* v; 923 int r; 924 int num_asserts = 0; 925 tb_assert(store); 926 927 v = macro_lookup(store, "bla"); 928 tb_assert(strcmp(v, "") == 0); 929 free(v); 930 931 v = macro_lookup(store, "vlerk"); 932 tb_assert(strcmp(v, "") == 0); 933 free(v); 934 935 r = macro_assign(store, "bla", "waarde1"); 936 tb_assert(r); 937 938 v = macro_lookup(store, "vlerk"); 939 tb_assert(strcmp(v, "") == 0); 940 free(v); 941 942 v = macro_lookup(store, "bla"); 943 tb_assert(strcmp(v, "waarde1") == 0); 944 free(v); 945 946 r = macro_assign(store, "vlerk", "kanteel"); 947 tb_assert(r); 948 949 v = macro_lookup(store, "bla"); 950 tb_assert(strcmp(v, "waarde1") == 0); 951 free(v); 952 953 v = macro_lookup(store, "vlerk"); 954 tb_assert(strcmp(v, "kanteel") == 0); 955 free(v); 956 957 r = macro_assign(store, "bla", "ww"); 958 tb_assert(r); 959 960 v = macro_lookup(store, "bla"); 961 tb_assert(strcmp(v, "ww") == 0); 962 free(v); 963 964 tb_assert( macro_length("}") == 1); 965 tb_assert( macro_length("blabla}") == 7); 966 tb_assert( macro_length("bla${zoink}bla}") == 7+8); 967 tb_assert( macro_length("bla${zoink}${bla}bla}") == 7+8+6); 968 969 v = macro_process(store, NULL, ""); 970 tb_assert( v && strcmp(v, "") == 0); 971 free(v); 972 973 v = macro_process(store, NULL, "${}"); 974 tb_assert( v && strcmp(v, "") == 0); 975 free(v); 976 977 v = macro_process(store, NULL, "blabla ${} dinges"); 978 tb_assert( v && strcmp(v, "blabla dinges") == 0); 979 free(v); 980 981 v = macro_process(store, NULL, "1${$bla}2${$bla}3"); 982 tb_assert( v && strcmp(v, "1ww2ww3") == 0); 983 free(v); 984 985 v = macro_process(store, NULL, "it is ${ctime 123456}"); 986 tb_assert( v && strcmp(v, "it is Fri Jan 2 10:17:36 1970") == 0); 987 free(v); 988 989 r = macro_assign(store, "t1", "123456"); 990 tb_assert(r); 991 v = macro_process(store, NULL, "it is ${ctime ${$t1}}"); 992 tb_assert( v && strcmp(v, "it is Fri Jan 2 10:17:36 1970") == 0); 993 free(v); 994 995 v = macro_process(store, NULL, "it is ${ctime $t1}"); 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, "x", "1"); 1000 tb_assert(r); 1001 r = macro_assign(store, "y", "2"); 1002 tb_assert(r); 1003 v = macro_process(store, NULL, "${$x + $x}"); 1004 tb_assert( v && strcmp(v, "2") == 0); 1005 free(v); 1006 v = macro_process(store, NULL, "${$x - $x}"); 1007 tb_assert( v && strcmp(v, "0") == 0); 1008 free(v); 1009 v = macro_process(store, NULL, "${$y * $y}"); 1010 tb_assert( v && strcmp(v, "4") == 0); 1011 free(v); 1012 v = macro_process(store, NULL, "${32 / $y + $x + $y}"); 1013 tb_assert( v && strcmp(v, "19") == 0); 1014 free(v); 1015 1016 v = macro_process(store, NULL, "${32 / ${$y+$y} + ${${100*3}/3}}"); 1017 tb_assert( v && strcmp(v, "108") == 0); 1018 free(v); 1019 1020 v = macro_process(store, NULL, "${1 2 33 2 1}"); 1021 tb_assert( v && strcmp(v, "1 2 33 2 1") == 0); 1022 free(v); 1023 1024 v = macro_process(store, NULL, "${123 3 + 5}"); 1025 tb_assert( v && strcmp(v, "123 8") == 0); 1026 free(v); 1027 1028 v = macro_process(store, NULL, "${123 glug 3 + 5}"); 1029 tb_assert( v && strcmp(v, "123 glug 8") == 0); 1030 free(v); 1031 1032 macro_store_delete(store); 1033 printf("selftest successful (%d checks).\n", num_asserts); 1034 } 1035