/* Read the configuration file for sysmon. */ #define getline posix_getline /* Avoid collision with POSIX.1-2008 function */ #include #include #include #include #include #undef getline #include "action.h" #include "etree.h" #include "global.h" #include "server.h" #include "window.h" /*************************************************************************/ static const char *configname; /* Name of configuration file */ static int linenum = 0; /* Current line number in config file */ /*************************************************************************/ /*************************************************************************/ /* Complain about something. */ static void error(const char *fmt, ...) { va_list args; va_start(args, fmt); fprintf(stderr, "%s:%d: ", configname, linenum); vfprintf(stderr, fmt, args); fputc('\n', stderr); } /*************************************************************************/ /* Read a line from the configuration file. Return NULL on end-of-file. */ static char *readline(FILE *f) { static char *buf = NULL; static int bufsize = 0; int c, len = 0; linenum++; while ((c = fgetc(f)) != '\n' && c != EOF) { if (len+1 > bufsize) { bufsize = len+256; buf = realloc(buf, bufsize); } buf[len++] = c; } if (c == EOF && len == 0) { if (buf) { free(buf); buf = NULL; bufsize = 0; } return NULL; } if (len+2 > bufsize) { bufsize = len+256; buf = realloc(buf, bufsize); } buf[len++] = '\n'; buf[len] = 0; return buf; } /*************************************************************************/ /* Read a line which is not either blank or a comment and return it. */ static char *getline(FILE *f) { char *line; do { line = readline(f); } while (line && (*line == '#' || line[strspn(line, " \t\r\n\f")] == 0)); return line; } /*************************************************************************/ /* Read a "word" (by C variable names) from the given string and advance * the string. Returns NULL if no word was found. */ static char *getword(char **string) { char *s = *string; static char *retbuf = NULL; while (isspace(*s)) s++; *string = s; if (!isalpha(*s) && *s != '_') return NULL; s++; while (isalnum(*s) || *s == '_') s++; if (retbuf) free(retbuf); retbuf = malloc(s - *string + 1); strncpy(retbuf, *string, s - *string); retbuf[s - *string] = 0; while (isspace(*s)) s++; *string = s; return retbuf; } /*************************************************************************/ /* Read a {} block from the given string, reading additional lines from the * given file if necessary. The '{' is assumed to have been already passed * over. The return value will be NULL if no valid block (i.e. closing * brace) was found. *lineptr will be set to the character after the last * one scanned (i.e. after the closing brace or at the end of the string). */ /* XXX this doesn't read quotes */ static char *getblock(char **lineptr, FILE *f) { char *s; char *retbuf = NULL; int retsize = 0, retlen = 0; int bracecount = 0; do { while (!(s = strpbrk(*lineptr, "{}"))) { if (retlen + strlen(*lineptr) + 1 > retsize) { retsize = retlen + strlen(*lineptr) + 256; retbuf = realloc(retbuf, retsize); } strcpy(retbuf+retlen, *lineptr); retlen += strlen(*lineptr); if (!(*lineptr = readline(f))) { error("Expected '}' to close block"); free(retbuf); return NULL; } if (**lineptr == '#') { (*lineptr)[0] = '\n'; (*lineptr)[1] = 0; } } if (*s == '{') bracecount++; else bracecount--; s++; if (bracecount < 0) /* Don't include final closing brace */ s--; if (retlen + (s - *lineptr) + 1 > retsize) { retsize = retlen + (s - *lineptr) + 256; retbuf = realloc(retbuf, retsize); } if (s > *lineptr) { strncpy(retbuf+retlen, *lineptr, s - *lineptr); retlen += s - *lineptr; retbuf[retlen] = 0; } *lineptr = s; } while (bracecount >= 0); (*lineptr)++; return retbuf; } /*************************************************************************/ /* Read a directive from the given string and return it. A directive is * terminated by either a semicolon or a close brace matching an open * brace. If there is nothing left in the string, return NULL; if what is * left isn't a complete directive, return (char *)-1. Modifies the string * by side effect. `line' is updated to reflect the line number at the end * of the directive (but before any {} block). * * An `if () ...' construct or `else ...' construct counts as a single * directive. */ /* Can't someone clean this up? Please? */ static char *getdirective(char **string, int *line) { char *s, *ret; int linestart = 1, wordstart = 1, is_if = 0, last_if = 0; int ifcount = 0, bracecount = 0, saveline = -1; s = *string; while (isspace(*s)) { if (*s == '\n') (*line)++; s++; } *string = s; if (!*s) return NULL; ret = s; do { /* "if" or "else" mean we're in an if block. */ if (!bracecount && wordstart && strncasecmp(s, "if", 2) == 0 && !isalnum(s[2]) && s[2] != '_') { if (linestart) is_if = 1; last_if = 1; ifcount++; s += 1; } else if (!bracecount && wordstart && strncasecmp(s, "else", 4) == 0 && !isalnum(s[4]) && s[4] != '_') { if (linestart) is_if = 2; last_if = 2; ifcount++; s += 3; } else if (*s == '"') { int quoteline = *line; s++; while (*s && *s != '"') { if (*s == '\\') s++; if (*s == '\n') (*line)++; s++; } if (!*s) { error("Unterminated string (possibly starting on line %d)", quoteline); return (char *)-1; } wordstart = 1; } else if (*s == '{') { bracecount++; if (bracecount == 1) saveline = *line; wordstart = 1; } else if (*s == '}') { if (bracecount > 0) { bracecount--; } else { /* { to keep Emacs from getting confused */ error("Extraneous '}', ignoring"); } wordstart = 1; } else if (*s == '\n') { (*line)++; wordstart = 1; } else if (isalnum(*s) || *s == '_') { wordstart = 0; } else { wordstart = 1; } if (bracecount == 0 && (*s == ';' || *s == '}')) { char *t = s; int l = *line; ifcount--; if (is_if == 2 && last_if == 1) { /* Check for an else */ t++; while (isspace(*t)) { if (*t == '\n') l++; t++; } if (strncasecmp(t,"else",4) == 0 && !isalnum(t[4]) && t[4] != '_') { is_if = 2; ifcount++; s = t+3; *line = l; } else { ifcount = 0; } } else if (is_if == 2) { ifcount = 0; } } linestart = 0; } while (ifcount > (is_if ? 0 : -1) && *++s); if (!*s) { /* XXX make this smarter (e.g. `if (a) b' gives "unbalanced if's") */ if (ifcount > 0) error("Unbalanced if's"); else if (bracecount > 0) error("Incomplete if-block (missing close brace)"); else error("Incomplete directive (missing semicolon)"); return (char *)-1; } if (saveline > 0) *line = saveline; if (!is_if) *s = 0; s++; if (is_if) { /* XXX memory LEAK! */ static char *result; result = malloc(s - *string + 1); strncpy(result, *string, s - *string); result[s - *string] = 0; ret = result; } *string = s; return ret; } /*************************************************************************/ /*************************************************************************/ /* Handy little macro to verify argument counts. */ #define CHECK_NARGS(name,min,max) do { \ if (nargs < min) { \ error("Too few arguments for `%s'", name); \ etree_free_list(args, nargs); \ return 0; \ } else if (nargs > max) { \ error("Too many arguments for `%s'", name); \ etree_free_list(args, nargs); \ return 0; \ } \ } while (0) /*************************************************************************/ static int do_main(char *block, int firstline) { char *s, *word; int line = firstline, lastline = linenum; ETree **args; int nargs; Server *server; while ((s = getdirective(&block, &line)) && s != (char *)-1) { linenum = line; word = getword(&s); args = etree_create_list(s, &s, &nargs); while (isspace(*s)) s++; if (*s) { error("Syntax error in expression"); return 0; } if (strcasecmp(word, "server") == 0) { char *servername, *t; CHECK_NARGS("server", 2, 3); server = server_new(); servername = etree_eval_s(args[0]); if ((t = strchr(servername, ':'))) { *t++ = 0; server->port = atoi(servername); if (server->port <= 0 || server->port >= 65536) { error("Port number must be positive and less than 65536"); return 0; } servername = t; } server->name = strdup(servername); server->password = strdup(etree_eval_s(args[1])); server->alias = args[2] ? strdup(etree_eval_s(args[2])) : NULL; } else if (strcasecmp(word, "email") == 0) { CHECK_NARGS("email", 2, 2); } else { error("Unknown main directive `%s'", word); return 0; } linenum = lastline; } return s==NULL ? 1 : 0; } /*************************************************************************/ static int do_action_block(Action *act, char *block, int *firstline) { char *s, *word; int line = *firstline, lastline = linenum; ETree **args; int nargs; ActionDirective *ad; int last_was_if = 0; /* Flag: did we just see an if? */ while ((s = getdirective(&block, &line)) && s != (char *)-1) { if (!*s) continue; linenum = line; word = getword(&s); if (strcasecmp(word, "else") != 0) args = etree_create_list(s, &s, &nargs); else { nargs = 0; args = NULL; } while (isspace(*s)) s++; if (*s && strcasecmp(word,"if") != 0 && strcasecmp(word,"else") != 0) { error("Syntax error in expression"); return 0; } if (strcasecmp(word, "name") == 0) { CHECK_NARGS("name", 1, 1); act->name = strdup(etree_eval_s(args[0])); } else if (strcasecmp(word, "check") == 0) { CHECK_NARGS("check", 1, 1); act->check = etree_eval_i(args[0]); if (act->check < 0) { error("Check period must be non-negative"); return 0; } } else if (strcasecmp(word, "timeout") == 0) { CHECK_NARGS("check", 1, 1); act->timeout = etree_eval_i(args[0]); if (act->timeout <= 0) { error("Check period must be positive"); return 0; } } else if (strcasecmp(word, "setstate") == 0) { CHECK_NARGS("setstate", 1, 1); ad = malloc(sizeof(*ad)); ad->what = AD_SETSTATE; ad->nargs = nargs; ad->args = args; nargs = 0; args = NULL; act->dirlist_size++; act->dirlist = realloc(act->dirlist, sizeof(*ad) * act->dirlist_size); act->dirlist[act->dirlist_size - 1] = ad; } else if (strcasecmp(word, "setmessage") == 0) { CHECK_NARGS("setmessage", 1, 1); ad = malloc(sizeof(*ad)); ad->what = AD_SETMESSAGE; ad->nargs = nargs; ad->args = args; nargs = 0; args = NULL; act->dirlist_size++; act->dirlist = realloc(act->dirlist, sizeof(*ad) * act->dirlist_size); act->dirlist[act->dirlist_size - 1] = ad; } else if (strcasecmp(word, "connect") == 0) { CHECK_NARGS("connect", 2, 5); ad = malloc(sizeof(*ad)); ad->what = AD_CONNECT; ad->nargs = nargs; ad->args = args; nargs = 0; args = NULL; act->dirlist_size++; act->dirlist = realloc(act->dirlist, sizeof(*ad) * act->dirlist_size); act->dirlist[act->dirlist_size - 1] = ad; } else if (strcasecmp(word, "if") == 0 || strcasecmp(word, "else") == 0) { ETree *arg; ad = malloc(sizeof(*ad)); word = strdup(word); arg = malloc(sizeof(ETree)); arg->type = ET_INT; arg->u.intval = -act->dirlist_size; if (strcasecmp(word, "if") == 0) { if (nargs == 0) { error("Missing or invalid expression for `if'"); return 0; } ad->what = AD_IF; ad->nargs = 2; ad->args = malloc(sizeof(ETree *) * 2); ad->args[0] = args[0]; ad->args[1] = arg; nargs = 0; free(args); args = NULL; } else { if (!last_was_if) { error("Unmatched `else'"); return 0; } ad->what = AD_ELSE; ad->nargs = 1; ad->args = malloc(sizeof(ETree *)); ad->args[0] = arg; } act->dirlist_size++; act->dirlist = realloc(act->dirlist, sizeof(*ad) * act->dirlist_size); act->dirlist[act->dirlist_size - 1] = ad; if (*s == '{') { s++; s[strlen(s)-1] = 0; /* kill last } */ } if (!do_action_block(act, s, &line)) return 0; arg->u.intval += act->dirlist_size-1; if (strcasecmp(word, "if") == 0) last_was_if = 2; free(word); } else { error("Unknown action directive `%s'", word); return 0; } if (last_was_if > 0) last_was_if--; etree_free_list(args, nargs); linenum = lastline; } /* while (getdirective()) */ return s==NULL ? 1 : 0; } static int do_action(char *block, int firstline) { Action *act; act = act_new(); if (!do_action_block(act, block, &firstline)) return 0; return 1; } /*************************************************************************/ static int do_window_block(Window *win, char *block, int *firstline) { char *s, *word; int line = *firstline, lastline = linenum; ETree **args; int nargs; WinAction *wa; int last_was_if = 0; /* Flag: did we just see an if? */ while ((s = getdirective(&block, &line)) && s != (char *)-1) { if (!*s) continue; linenum = line; word = getword(&s); if (strcasecmp(word, "else") != 0) args = etree_create_list(s, &s, &nargs); else { nargs = 0; args = NULL; } while (isspace(*s)) s++; if (*s && strcasecmp(word,"if") != 0 && strcasecmp(word,"else") != 0) { error("Syntax error in expression"); return 0; } if (strcasecmp(word, "name") == 0) { CHECK_NARGS("name", 1, 1); win->name = strdup(etree_eval_s(args[0])); } else if (strcasecmp(word, "title") == 0) { CHECK_NARGS("title", 1, 1); win->title = strdup(etree_eval_s(args[0])); } else if (strcasecmp(word, "position") == 0) { CHECK_NARGS("position", 2, 2); win->x = etree_eval_i(args[0]); win->y = etree_eval_i(args[1]); } else if (strcasecmp(word, "size") == 0) { CHECK_NARGS("size", 2, 2); /* XXX fix default width */ if (args[0]) win->width = etree_eval_i(args[0]); else win->width = scrwidth; win->height = etree_eval_i(args[1]); } else if (strcasecmp(word, "border") == 0) { CHECK_NARGS("border", 1, 1); s = etree_eval_s(args[0]); if (strcasecmp(s, "on") == 0) win->flags |= WF_BORDER; else if (strcasecmp(s, "off") == 0) win->flags &= ~WF_BORDER; else { error("Argument for `border' must be either `on' or `off'"); return 0; } } else if (strcasecmp(word, "wrap") == 0) { CHECK_NARGS("wrap", 1, 1); s = etree_eval_s(args[0]); if (strcasecmp(s, "on") == 0) win->flags |= WF_WRAP; else if (strcasecmp(s, "off") == 0) win->flags &= ~WF_WRAP; else { error("Argument for `wrap' must be either `on' or `off'"); return 0; } } else if (strcasecmp(word, "scroll") == 0) { CHECK_NARGS("scroll", 1, 1); s = etree_eval_s(args[0]); if (strcasecmp(s, "on") == 0) win->flags |= WF_SCROLL; else if (strcasecmp(s, "off") == 0) win->flags &= ~WF_SCROLL; else { error("Argument for `scroll' must be either `on' or `off'"); return 0; } } else if (strcasecmp(word, "update") == 0) { CHECK_NARGS("update", 1, 1); win->refresh = etree_eval_i(args[0]); if (win->refresh < 0) { error("Update period must be non-negative"); return 0; } } else if (strcasecmp(word, "clear") == 0) { CHECK_NARGS("clear", 0, 0); wa = malloc(sizeof(*wa)); wa->what = WA_CLEAR; wa->nargs = 0; win->drawlist_size++; win->drawlist = realloc(win->drawlist, sizeof(WinAction *) * win->drawlist_size); win->drawlist[win->drawlist_size - 1] = wa; } else if (strcasecmp(word, "cursor") == 0) { CHECK_NARGS("cursor", 2, 2); wa = malloc(sizeof(*wa)); wa->what = WA_CURSOR; wa->nargs = nargs; wa->args = args; nargs = 0; args = NULL; win->drawlist_size++; win->drawlist = realloc(win->drawlist, sizeof(WinAction *) * win->drawlist_size); win->drawlist[win->drawlist_size - 1] = wa; } else if (strcasecmp(word, "color") == 0) { CHECK_NARGS("color", 1, 2); wa = malloc(sizeof(*wa)); wa->what = WA_COLOR; wa->nargs = nargs; wa->args = args; nargs = 0; args = NULL; win->drawlist_size++; win->drawlist = realloc(win->drawlist, sizeof(WinAction *) * win->drawlist_size); win->drawlist[win->drawlist_size - 1] = wa; } else if (strcasecmp(word, "bordercolor") == 0) { CHECK_NARGS("bordercolor", 1, 2); wa = malloc(sizeof(*wa)); wa->what = WA_BORDERCOLOR; wa->nargs = nargs; wa->args = args; nargs = 0; args = NULL; win->drawlist_size++; win->drawlist = realloc(win->drawlist, sizeof(WinAction *) * win->drawlist_size); win->drawlist[win->drawlist_size - 1] = wa; } else if (strcasecmp(word, "titlecolor") == 0) { CHECK_NARGS("titlecolor", 1, 2); wa = malloc(sizeof(*wa)); wa->what = WA_TITLECOLOR; wa->nargs = nargs; wa->args = args; nargs = 0; args = NULL; win->drawlist_size++; win->drawlist = realloc(win->drawlist, sizeof(WinAction *) * win->drawlist_size); win->drawlist[win->drawlist_size - 1] = wa; } else if (strcasecmp(word, "printf") == 0) { CHECK_NARGS("printf", 1, 9999); wa = malloc(sizeof(*wa)); wa->what = WA_PRINTF; wa->nargs = nargs; wa->args = args; nargs = 0; args = NULL; win->drawlist_size++; win->drawlist = realloc(win->drawlist, sizeof(WinAction *) * win->drawlist_size); win->drawlist[win->drawlist_size - 1] = wa; } else if (strcasecmp(word, "beep") == 0) { CHECK_NARGS("beep", 0, 0); wa = malloc(sizeof(*wa)); wa->what = WA_BEEP; wa->nargs = 0; win->drawlist_size++; win->drawlist = realloc(win->drawlist, sizeof(WinAction *) * win->drawlist_size); win->drawlist[win->drawlist_size - 1] = wa; } else if (strcasecmp(word, "setstate") == 0) { CHECK_NARGS("setstate", 2, 2); wa = malloc(sizeof(*wa)); wa->what = WA_SETSTATE; wa->nargs = nargs; wa->args = args; nargs = 0; args = NULL; win->drawlist_size++; win->drawlist = realloc(win->drawlist, sizeof(WinAction *) * win->drawlist_size); win->drawlist[win->drawlist_size - 1] = wa; } else if (strcasecmp(word, "if") == 0 || strcasecmp(word, "else") == 0) { ETree *arg; wa = malloc(sizeof(*wa)); word = strdup(word); arg = malloc(sizeof(ETree)); arg->type = ET_INT; arg->u.intval = -win->drawlist_size; if (strcasecmp(word, "if") == 0) { if (nargs == 0) { error("Missing or invalid expression for `if'"); return 0; } wa->what = WA_IF; wa->nargs = 2; wa->args = malloc(sizeof(ETree *) * 2); wa->args[0] = args[0]; wa->args[1] = arg; nargs = 0; free(args); args = NULL; } else { if (!last_was_if) { error("Unmatched `else'"); return 0; } wa->what = WA_ELSE; wa->nargs = 1; wa->args = malloc(sizeof(ETree *)); wa->args[0] = arg; } win->drawlist_size++; win->drawlist = realloc(win->drawlist, sizeof(WinAction *) * win->drawlist_size); win->drawlist[win->drawlist_size - 1] = wa; if (*s == '{') { s++; s[strlen(s)-1] = 0; /* kill last } */ } if (!do_window_block(win, s, &line)) return 0; arg->u.intval += win->drawlist_size-1; if (strcasecmp(word, "if") == 0) last_was_if = 2; free(word); } else { error("Unknown window directive `%s'", word); return 0; } if (last_was_if > 0) last_was_if--; etree_free_list(args, nargs); linenum = lastline; } /* while (getdirective()) */ return s==NULL ? 1 : 0; } static int do_window(char *block, int firstline) { Window *win; win = win_new(); win->width = win->height = -1; win->flags = WF_BORDER; if (!do_window_block(win, block, &firstline)) return 0; if (win->width < 0 || win->height < 0) { error("Window size not set"); return 0; } else if (win->width == 0 || win->height == 0) { error("Window too small"); return 0; } else if ((win->flags & WF_BORDER) && (win->width<=2 || win->height<=2)) { error("Window too small"); return 0; } if (win->x < 0) win->x += scrwidth; if (win->y < 0) win->y += scrheight; return 1; } /*************************************************************************/ /* Read a configuration block; return -1 if we hit end-of-file, 0 if the * block was invalid, and 1 on success. */ static int read_block(FILE *f) { static char *line = NULL; char *s, *block; int ret; int firstline; if (line) { while (*line && isspace(*line)) line++; if (!*line) line = NULL; } if (!line) { if (!(line = getline(f))) return -1; } firstline = linenum; s = getword(&line); if (!s) { error("Expected block name"); return 0; } if (!*line) { line = getline(f); if (!line) { error("Expected `{' to begin block"); return 0; } while (isspace(*line)) line++; } if (*line != '{') { error("Expected `{' to begin block"); return 0; } line++; firstline = linenum; block = getblock(&line, f); if (!block) return 0; if (strcasecmp(s, "main") == 0) { ret = do_main(block, firstline); } else if (strcasecmp(s, "action") == 0) { ret = do_action(block, firstline); } else if (strcasecmp(s, "window") == 0) { ret = do_window(block, firstline); } else { error("Unknown block name `%s'", s); ret = 0; } free(block); return ret; } /*************************************************************************/ /* Read in a given configuration file. Return 1 if the file was * successfully processed, 0 otherwise. */ int read_config(const char *filename) { FILE *f; int i; configname = filename; if (!(f = fopen(filename, "r"))) { perror(filename); return 0; } while ((i = read_block(f)) == 1) ; return i==0 ? 0 : 1; } /*************************************************************************/