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