summaryrefslogtreecommitdiff
path: root/usr.sbin/ifstated/parse.y
diff options
context:
space:
mode:
Diffstat (limited to 'usr.sbin/ifstated/parse.y')
-rw-r--r--usr.sbin/ifstated/parse.y869
1 files changed, 869 insertions, 0 deletions
diff --git a/usr.sbin/ifstated/parse.y b/usr.sbin/ifstated/parse.y
new file mode 100644
index 00000000000..918491bf94a
--- /dev/null
+++ b/usr.sbin/ifstated/parse.y
@@ -0,0 +1,869 @@
+/* $OpenBSD: parse.y,v 1.1 2004/02/04 23:47:49 mcbride Exp $ */
+
+/*
+ * Copyright (c) 2004 Ryan McBride <mcbride@openbsd.org>
+ * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org>
+ * Copyright (c) 2001 Markus Friedl. All rights reserved.
+ * Copyright (c) 2001 Daniel Hartmeier. All rights reserved.
+ * Copyright (c) 2001 Theo de Raadt. All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+%{
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <net/if.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <syslog.h>
+#include <event.h>
+
+#include "ifstated.h"
+
+static struct ifsd_config *conf;
+static FILE *fin = NULL;
+static int lineno = 1;
+static int errors = 0;
+static int pdebug = 1;
+char *infile;
+char *start_state;
+
+struct ifsd_action *curaction;
+struct ifsd_state *curstate = NULL;
+
+int yyerror(const char *, ...);
+int yyparse(void);
+int kw_cmp(const void *, const void *);
+int lookup(char *);
+int lgetc(FILE *);
+int lungetc(int);
+int findeol(void);
+int yylex(void);
+
+
+TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead);
+struct sym {
+ TAILQ_ENTRY(sym) entries;
+ int used;
+ int persist;
+ char *nam;
+ char *val;
+};
+
+void link_states(struct ifsd_action *);
+int symset(const char *, const char *, int);
+char *symget(const char *);
+int atoul(char *, u_long *);
+void set_expression_depth(struct ifsd_expression *, int);
+void init_state(struct ifsd_state *);
+struct ifsd_ifstate *new_ifstate(u_short, int);
+struct ifsd_external *new_external(char *, u_int32_t);
+
+typedef struct {
+ union {
+ u_int32_t number;
+ char *string;
+ struct in_addr addr;
+ u_short interface;
+
+ struct ifsd_expression *expression;
+ struct ifsd_ifstate *ifstate;
+ struct ifsd_external *external;
+
+ } v;
+ int lineno;
+} YYSTYPE;
+
+%}
+
+%token STATE INITSTATE
+%token LINK UP DOWN UNKNOWN ADDED REMOVED
+%token IF RUN SETSTATE EVERY INIT LOGLEVEL
+%left AND OR
+%left UNARY
+%token ERROR
+%token <v.string> STRING
+%type <v.number> number
+%type <v.string> string
+%type <v.interface> interface
+%type <v.ifstate> if_test
+%type <v.external> ext_test
+%type <v.expression> expr term
+%%
+
+grammar : /* empty */
+ | grammar '\n'
+ | grammar conf_main '\n'
+ | grammar varset '\n'
+ | grammar action '\n'
+ | grammar state '\n'
+ | grammar error '\n' { errors++; }
+ ;
+
+number : STRING {
+ u_long ulval;
+
+ if (atoul($1, &ulval) == -1) {
+ yyerror("%s is not a number", $1);
+ YYERROR;
+ } else
+ $$ = ulval;
+ }
+ ;
+
+string : string STRING {
+ if (asprintf(&$$, "%s %s", $1, $2) == -1) {
+ yyerror("string: asprintf");
+ YYERROR;
+ }
+ free($1);
+ free($2);
+ }
+ | STRING
+ ;
+
+varset : STRING '=' string {
+ if (conf->opts & IFSD_OPT_VERBOSE)
+ printf("%s = \"%s\"\n", $1, $3);
+ if (symset($1, $3, 0) == -1) {
+ yyerror("cannot store variable");
+ YYERROR;
+ }
+ }
+ ;
+
+conf_main : INITSTATE STRING {
+ if ((start_state = strdup($2)) == NULL) {
+ yyerror("conf_main: strdup");
+ YYERROR;
+ }
+ }
+ | LOGLEVEL STRING {
+ if (!strcmp($2, "none"))
+ conf->loglevel = IFSD_LOG_NONE;
+ else if (!strcmp($2, "quiet"))
+ conf->loglevel = IFSD_LOG_QUIET;
+ else if (!strcmp($2, "normal"))
+ conf->loglevel = IFSD_LOG_NORMAL;
+ else if (!strcmp($2, "verbose"))
+ conf->loglevel = IFSD_LOG_VERBOSE;
+ else if (!strcmp($2, "debug"))
+ conf->loglevel = IFSD_LOG_DEBUG;
+ }
+ ;
+
+interface : STRING {
+ if (($$ = if_nametoindex($1)) == 0) {
+ yyerror("unknown interface %s", $1);
+ YYERROR;
+ }
+ }
+ ;
+
+optnl : '\n' optnl
+ |
+ ;
+
+nl : '\n' optnl /* one newline or more */
+ ;
+
+action : RUN STRING {
+ struct ifsd_action *action;
+
+ if ((action = calloc(1, sizeof(*action))) == NULL)
+ err(1, "action: calloc");
+ action->type = IFSD_ACTION_COMMAND;
+ action->act.command = strdup($2);
+ if (action->act.command == NULL)
+ err(1, "action: strdup");
+ TAILQ_INSERT_TAIL(&curaction->act.c.actions,
+ action, entries);
+ }
+ | SETSTATE STRING {
+ struct ifsd_action *action;
+
+ if (curstate == NULL) {
+ yyerror("setstate must be used inside 'if'");
+ YYERROR;
+ }
+ if ((action = calloc(1, sizeof(*action))) == NULL)
+ err(1, "action: calloc");
+ action->type = IFSD_ACTION_CHANGESTATE;
+ if ((action->act.statename = strdup($2)) == NULL)
+ err(1, "action: strdup");
+ TAILQ_INSERT_TAIL(&curaction->act.c.actions,
+ action, entries);
+ }
+ | IF {
+ struct ifsd_action *action;
+
+ if ((action = calloc(1, sizeof(*action))) == NULL)
+ err(1, "action: calloc");
+ action->type = IFSD_ACTION_CONDITION;
+ TAILQ_INIT(&action->act.c.actions);
+ TAILQ_INSERT_TAIL(&curaction->act.c.actions,
+ action, entries);
+ action->parent = curaction;
+ curaction = action;
+ } expr optnl '{' optnl action_l '}' {
+ set_expression_depth(curaction->act.c.expression, 0);
+ curaction = curaction->parent;
+ }
+ ;
+
+action_l : action_l action nl
+ | action nl
+ ;
+
+init : INIT {
+ if (curstate != NULL)
+ curaction = curstate->init;
+ else
+ curaction = conf->always.init;
+ if (curaction == NULL) errx(1, "curaction == NULL");
+
+ } optnl '{' optnl action_l '}' {
+ if (curstate != NULL)
+ curaction = curstate->always;
+ else
+ curaction = conf->always.always;
+ if (curaction == NULL) errx(1, "curaction == NULL");
+ }
+
+if_test : interface LINK UP {
+ $$ = new_ifstate($1, IFSD_LINKUP);
+ }
+ | interface LINK DOWN {
+ $$ = new_ifstate($1, IFSD_LINKDOWN);
+ }
+ | interface LINK UNKNOWN {
+ $$ = new_ifstate($1, IFSD_LINKUNKNOWN);
+ }
+ ;
+
+ext_test : STRING EVERY number {
+ $$ = new_external($1, $3);
+ }
+ ;
+
+term : if_test {
+ if (($$ = calloc(1, sizeof(*$$))) == NULL)
+ errx(1, "term: calloc");
+ curaction->act.c.expression = $$;
+ $$->type = IFSD_OPER_IFSTATE;
+ $$->u.ifstate = $1;
+ TAILQ_INSERT_TAIL(&$1->expressions, $$, entries);
+ }
+ | ext_test {
+ if (($$ = calloc(1, sizeof(*$$))) == NULL)
+ errx(1, "term: calloc");
+ curaction->act.c.expression = $$;
+ $$->type = IFSD_OPER_EXTERNAL;
+ $$->u.external = $1;
+ TAILQ_INSERT_TAIL(&$1->expressions, $$, entries);
+ }
+ | '(' expr ')' {
+ $$ = $2;
+ }
+ ;
+
+expr : '!' expr %prec UNARY {
+ if (($$ = calloc(1, sizeof(*$$))) == NULL)
+ errx(1, "expr: calloc");
+ curaction->act.c.expression = $$;
+ $$->type = IFSD_OPER_NOT;
+ $2->parent = $$;
+ $$->right = $2;
+ }
+ | expr AND expr {
+ if (($$ = calloc(1, sizeof(*$$))) == NULL)
+ errx(1, "expr: calloc");
+ curaction->act.c.expression = $$;
+ $$->type = IFSD_OPER_AND;
+ $1->parent = $$;
+ $3->parent = $$;
+ $$->left = $1;
+ $$->right = $3;
+ }
+ | expr OR expr {
+ if (($$ = calloc(1, sizeof(*$$))) == NULL)
+ errx(1, "expr: calloc");
+ curaction->act.c.expression = $$;
+ $$->type = IFSD_OPER_OR;
+ $1->parent = $$;
+ $3->parent = $$;
+ $$->left = $1;
+ $$->right = $3;
+ }
+ | term
+ ;
+
+state : STATE string {
+ struct ifsd_state *state = NULL;
+
+ TAILQ_FOREACH(state, &conf->states, entries)
+ if (!strcmp(state->name, $2)) {
+ yyerror("state %s already exists", $2);
+ YYERROR;
+ }
+ if ((state = calloc(1, sizeof(*curstate))) == NULL)
+ errx(1, "state: calloc");
+ init_state(state);
+ if ((state->name = strdup($2)) == NULL)
+ errx(1, "state: strdup");
+ curstate = state;
+ curaction = state->always;
+ } optnl '{' optnl stateopts_l '}' {
+ TAILQ_INSERT_TAIL(&conf->states, curstate, entries);
+ curstate = NULL;
+ curaction = conf->always.always;
+ }
+ ;
+
+stateopts_l : stateopts_l stateoptsl
+ | stateoptsl
+ ;
+
+stateoptsl : init nl
+ | action nl
+ ;
+
+%%
+
+struct keywords {
+ const char *k_name;
+ int k_val;
+};
+
+int
+yyerror(const char *fmt, ...)
+{
+ va_list ap;
+
+ errors = 1;
+ va_start(ap, fmt);
+ fprintf(stderr, "%s:%d: ", infile, yylval.lineno);
+ vfprintf(stderr, fmt, ap);
+ fprintf(stderr, "\n");
+ va_end(ap);
+ return (0);
+}
+
+int
+kw_cmp(const void *k, const void *e)
+{
+ return (strcmp(k, ((const struct keywords *)e)->k_name));
+}
+
+int
+lookup(char *s)
+{
+ /* this has to be sorted always */
+ static const struct keywords keywords[] = {
+ { "added", ADDED},
+ { "and", AND},
+ { "down", DOWN},
+ { "every", EVERY},
+ { "if", IF},
+ { "init", INIT},
+ { "init-state", INITSTATE},
+ { "link", LINK},
+ { "loglevel", LOGLEVEL},
+ { "or", OR},
+ { "removed", REMOVED},
+ { "run", RUN},
+ { "set-state", SETSTATE},
+ { "state", STATE},
+ { "unknown", UNKNOWN},
+ { "up", UP}
+ };
+ const struct keywords *p;
+
+ p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
+ sizeof(keywords[0]), kw_cmp);
+
+ if (p) {
+ if (pdebug > 1)
+ fprintf(stderr, "%s: %d\n", s, p->k_val);
+ return (p->k_val);
+ } else {
+ if (pdebug > 1)
+ fprintf(stderr, "string: %s\n", s);
+ return (STRING);
+ }
+}
+
+#define MAXPUSHBACK 128
+
+char *parsebuf;
+int parseindex;
+char pushback_buffer[MAXPUSHBACK];
+int pushback_index = 0;
+
+int
+lgetc(FILE *f)
+{
+ int c, next;
+
+ if (parsebuf) {
+ /* Read character from the parsebuffer instead of input. */
+ if (parseindex >= 0) {
+ c = parsebuf[parseindex++];
+ if (c != '\0')
+ return (c);
+ parsebuf = NULL;
+ } else
+ parseindex++;
+ }
+
+ if (pushback_index)
+ return (pushback_buffer[--pushback_index]);
+
+ while ((c = getc(f)) == '\\') {
+ next = getc(f);
+ if (next != '\n') {
+ if (isspace(next))
+ yyerror("whitespace after \\");
+ ungetc(next, f);
+ break;
+ }
+ yylval.lineno = lineno;
+ lineno++;
+ }
+ if (c == '\t' || c == ' ') {
+ /* Compress blanks to a single space. */
+ do {
+ c = getc(f);
+ } while (c == '\t' || c == ' ');
+ ungetc(c, f);
+ c = ' ';
+ }
+
+ return (c);
+}
+
+int
+lungetc(int c)
+{
+ if (c == EOF)
+ return (EOF);
+ if (parsebuf) {
+ parseindex--;
+ if (parseindex >= 0)
+ return (c);
+ }
+ if (pushback_index < MAXPUSHBACK-1)
+ return (pushback_buffer[pushback_index++] = c);
+ else
+ return (EOF);
+}
+
+int
+findeol(void)
+{
+ int c;
+
+ parsebuf = NULL;
+ pushback_index = 0;
+
+ /* skip to either EOF or the first real EOL */
+ while (1) {
+ c = lgetc(fin);
+ if (c == '\n') {
+ lineno++;
+ break;
+ }
+ if (c == EOF)
+ break;
+ }
+ return (ERROR);
+}
+
+int
+yylex(void)
+{
+ char buf[8096];
+ char *p, *val;
+ int endc, c;
+ int token;
+
+top:
+ p = buf;
+ while ((c = lgetc(fin)) == ' ')
+ ; /* nothing */
+
+ yylval.lineno = lineno;
+ if (c == '#')
+ while ((c = lgetc(fin)) != '\n' && c != EOF)
+ ; /* nothing */
+ if (c == '$' && parsebuf == NULL) {
+ while (1) {
+ if ((c = lgetc(fin)) == EOF)
+ return (0);
+
+ if (p + 1 >= buf + sizeof(buf) - 1) {
+ yyerror("string too long");
+ return (findeol());
+ }
+ if (isalnum(c) || c == '_') {
+ *p++ = (char)c;
+ continue;
+ }
+ *p = '\0';
+ lungetc(c);
+ break;
+ }
+ val = symget(buf);
+ if (val == NULL) {
+ yyerror("macro '%s' not defined", buf);
+ return (findeol());
+ }
+ parsebuf = val;
+ parseindex = 0;
+ goto top;
+ }
+
+ switch (c) {
+ case '\'':
+ case '"':
+ endc = c;
+ while (1) {
+ if ((c = lgetc(fin)) == EOF)
+ return (0);
+ if (c == endc) {
+ *p = '\0';
+ break;
+ }
+ if (c == '\n') {
+ lineno++;
+ continue;
+ }
+ if (p + 1 >= buf + sizeof(buf) - 1) {
+ yyerror("string too long");
+ return (findeol());
+ }
+ *p++ = (char)c;
+ }
+ yylval.v.string = strdup(buf);
+ if (yylval.v.string == NULL)
+ errx(1, "yylex: strdup");
+ return (STRING);
+ }
+
+#define allowed_in_string(x) \
+ (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
+ x != '{' && x != '}' && \
+ x != '!' && x != '=' && x != '#' && \
+ x != ','))
+
+ if (isalnum(c) || c == ':' || c == '_') {
+ do {
+ *p++ = c;
+ if ((unsigned)(p-buf) >= sizeof(buf)) {
+ yyerror("string too long");
+ return (findeol());
+ }
+ } while ((c = lgetc(fin)) != EOF && (allowed_in_string(c)));
+ lungetc(c);
+ *p = '\0';
+ token = lookup(buf);
+ yylval.v.string = strdup(buf);
+ if (yylval.v.string == NULL)
+ errx(1, "yylex: strdup");
+ return (token);
+ }
+ if (c == '\n') {
+ yylval.lineno = lineno;
+ lineno++;
+ }
+ if (c == EOF)
+ return (0);
+ return (c);
+}
+
+int
+parse_config(char *filename, struct ifsd_config *xconf)
+{
+ struct sym *sym, *next;
+ struct ifsd_state *state;
+
+ if ((conf = calloc(1, sizeof(struct ifsd_config))) == NULL)
+ errx(1, "parse_config calloc");
+
+ TAILQ_INIT(&conf->states);
+
+ init_state(&conf->always);
+ curaction = conf->always.always;
+ conf->loglevel = IFSD_LOG_NORMAL;
+
+ if ((fin = fopen(filename, "r")) == NULL) {
+ warn("%s", filename);
+ free(conf);
+ return (-1);
+ }
+ infile = filename;
+
+ yyparse();
+
+ fclose(fin);
+
+ /* Link states */
+ TAILQ_FOREACH(state, &conf->states, entries) {
+ link_states(state->init);
+ link_states(state->always);
+ }
+
+ if (start_state != NULL) {
+ TAILQ_FOREACH(state, &conf->states, entries) {
+ if (strcmp(start_state, state->name) == 0) {
+ conf->curstate = state;
+ break;
+ }
+ }
+ if (conf->curstate == NULL)
+ errx(1, "invalid start state %s", start_state);
+ } else {
+ conf->curstate = TAILQ_FIRST(&conf->states);
+ }
+
+ /* Free macros and check which have not been used. */
+ for (sym = TAILQ_FIRST(&symhead); sym != NULL; sym = next) {
+ next = TAILQ_NEXT(sym, entries);
+ if ((conf->opts & IFSD_OPT_VERBOSE2) && !sym->used)
+ fprintf(stderr, "warning: macro '%s' not "
+ "used\n", sym->nam);
+ if (!sym->persist) {
+ free(sym->nam);
+ free(sym->val);
+ TAILQ_REMOVE(&symhead, sym, entries);
+ free(sym);
+ }
+ }
+
+ bcopy(conf, xconf, sizeof(*conf));
+ free(conf);
+
+ return (errors ? -1 : 0);
+}
+
+void
+link_states(struct ifsd_action *action)
+{
+ struct ifsd_action *subaction;
+
+ switch (action->type) {
+ default:
+ case IFSD_ACTION_COMMAND:
+ break;
+ case IFSD_ACTION_CHANGESTATE: {
+ struct ifsd_state *state;
+
+ TAILQ_FOREACH(state, &conf->states, entries) {
+ if (strcmp(action->act.statename,
+ state->name) == 0) {
+ action->act.nextstate = state;
+ break;
+ }
+ }
+ break;
+ }
+ case IFSD_ACTION_CONDITION:
+ TAILQ_FOREACH(subaction, &action->act.c.actions, entries)
+ link_states(subaction);
+ break;
+ }
+}
+
+int
+symset(const char *nam, const char *val, int persist)
+{
+ struct sym *sym;
+
+ for (sym = TAILQ_FIRST(&symhead); sym && strcmp(nam, sym->nam);
+ sym = TAILQ_NEXT(sym, entries))
+ ; /* nothing */
+
+ if (sym != NULL) {
+ if (sym->persist == 1)
+ return (0);
+ else {
+ free(sym->nam);
+ free(sym->val);
+ TAILQ_REMOVE(&symhead, sym, entries);
+ free(sym);
+ }
+ }
+ if ((sym = calloc(1, sizeof(*sym))) == NULL)
+ return (-1);
+
+ sym->nam = strdup(nam);
+ if (sym->nam == NULL) {
+ free(sym);
+ return (-1);
+ }
+ sym->val = strdup(val);
+ if (sym->val == NULL) {
+ free(sym->nam);
+ free(sym);
+ return (-1);
+ }
+ sym->used = 0;
+ sym->persist = persist;
+ TAILQ_INSERT_TAIL(&symhead, sym, entries);
+ return (0);
+}
+
+int
+cmdline_symset(char *s)
+{
+ char *sym, *val;
+ int ret;
+ size_t len;
+
+ if ((val = strrchr(s, '=')) == NULL)
+ return (-1);
+
+ len = strlen(s) - strlen(val) + 1;
+ if ((sym = malloc(len)) == NULL)
+ errx(1, "cmdline_symset: malloc");
+
+ strlcpy(sym, s, len);
+
+ ret = symset(sym, val + 1, 1);
+ free(sym);
+
+ return (ret);
+}
+
+char *
+symget(const char *nam)
+{
+ struct sym *sym;
+
+ TAILQ_FOREACH(sym, &symhead, entries)
+ if (strcmp(nam, sym->nam) == 0) {
+ sym->used = 1;
+ return (sym->val);
+ }
+ return (NULL);
+}
+
+int
+atoul(char *s, u_long *ulvalp)
+{
+ u_long ulval;
+ char *ep;
+
+ errno = 0;
+ ulval = strtoul(s, &ep, 0);
+ if (s[0] == '\0' || *ep != '\0')
+ return (-1);
+ if (errno == ERANGE && ulval == ULONG_MAX)
+ return (-1);
+ *ulvalp = ulval;
+ return (0);
+}
+
+void
+set_expression_depth(struct ifsd_expression *expression, int depth)
+{
+ expression->depth = depth;
+ if (conf->maxdepth < depth)
+ conf->maxdepth = depth;
+ if (expression->left != NULL)
+ set_expression_depth(expression->left, depth + 1);
+ if (expression->right != NULL)
+ set_expression_depth(expression->right, depth + 1);
+}
+
+void
+init_state(struct ifsd_state *state)
+{
+ TAILQ_INIT(&state->interface_states);
+ TAILQ_INIT(&state->external_tests);
+
+ if ((state->init = calloc(1, sizeof(*state->init))) == NULL)
+ err(1, "init_state: calloc");
+ state->init->type = IFSD_ACTION_CONDITION;
+ TAILQ_INIT(&state->init->act.c.actions);
+
+ if ((state->always = calloc(1, sizeof(*state->always))) == NULL)
+ err(1, "init_state: calloc");
+ state->always->type = IFSD_ACTION_CONDITION;
+ TAILQ_INIT(&state->always->act.c.actions);
+}
+
+struct ifsd_ifstate *
+new_ifstate(u_short ifindex, int s)
+{
+ struct ifsd_ifstate *ifstate = NULL;
+ struct ifsd_state *state;
+
+ if (curstate != NULL)
+ state = curstate;
+ else
+ state = &conf->always;
+
+ TAILQ_FOREACH(ifstate, &state->interface_states, entries)
+ if (ifstate->ifindex == ifindex && ifstate->ifstate == s)
+ break;
+ if (ifstate == NULL) {
+ if ((ifstate = calloc(1, sizeof(*ifstate))) == NULL)
+ errx(1, "new_ifstate: calloc");
+ ifstate->ifindex = ifindex;
+ ifstate->ifstate = s;
+ TAILQ_INIT(&ifstate->expressions);
+ TAILQ_INSERT_TAIL(&state->interface_states, ifstate, entries);
+ }
+ ifstate->prevstate = -1;
+ ifstate->refcount++;
+ return (ifstate);
+}
+
+struct ifsd_external *
+new_external(char *command, u_int32_t frequency)
+{
+ struct ifsd_external *external = NULL;
+ struct ifsd_state *state;
+
+ if (curstate != NULL)
+ state = curstate;
+ else
+ state = &conf->always;
+
+ TAILQ_FOREACH(external, &state->external_tests, entries)
+ if (strcmp(external->command, command) == 0 &&
+ external->frequency == frequency)
+ break;
+ if (external == NULL) {
+ if ((external = calloc(1, sizeof(*external))) == NULL)
+ errx(1, "new_external: calloc");
+ if ((external->command = strdup(command)) == NULL)
+ errx(1, "new_external: strdup");
+ external->frequency = frequency;
+ TAILQ_INIT(&external->expressions);
+ TAILQ_INSERT_TAIL(&state->external_tests, external, entries);
+ }
+ external->prevstatus = -1;
+ external->refcount++;
+ return (external);
+}