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