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