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