diff options
Diffstat (limited to 'src/pmieconf/rules.c')
-rw-r--r-- | src/pmieconf/rules.c | 2335 |
1 files changed, 2335 insertions, 0 deletions
diff --git a/src/pmieconf/rules.c b/src/pmieconf/rules.c new file mode 100644 index 0000000..cb0c9a0 --- /dev/null +++ b/src/pmieconf/rules.c @@ -0,0 +1,2335 @@ +/* + * rules.c - rule description parsing routines (rules & pmie config) + * + * Copyright (c) 1998-2002 Silicon Graphics, Inc. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <time.h> +#include <ctype.h> +#include <string.h> +#include <dirent.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/param.h> +#include <sys/types.h> +#include "pmapi.h" +#include "impl.h" +#include "rules.h" +#include "stats.h" + +#define SEP __pmPathSeparator() + + +#define PMIE_FILE "pmieconf-pmie" +#define PMIE_VERSION "1" /* local configurations file format version */ +#define RULES_FILE "pmieconf-rules" +#define RULES_VERSION "1" /* rule description file format version */ + +#define START_STRING \ + "// --- START GENERATED SECTION (do not change this section) ---\n" +#define END_STRING \ + "// --- END GENERATED SECTION (changes below will be preserved) ---\n" +#define TOKEN_LENGTH 2048 /* max length of input token, incl string */ +#define LINE_LENGTH 4096 + +#if !defined(sgi) +#define PROC_DIR "/proc" +#else +#define PROC_DIR "/proc/pinfo" +#endif + +char errmsg[512]; /* error message buffer */ +char rulepath[MAXPATHLEN+1]; /* root of rules files */ +char pmiefile[MAXPATHLEN+1]; /* pmie configuration file */ +char token[TOKEN_LENGTH+1]; + +rule_t *rulelist; /* global list of rules */ +unsigned int rulecount; /* # rule list elements */ +rule_t *globals; /* list of atoms with global scope */ + +#define GLOBAL_LEN 7 +static char global_name[] = "global"; /* GLOBAL_LEN chars long */ +static char global_data[] = "generic variables applied to all rules"; +static char global_help[] = \ + "The global variables are used by all rules, but their values can be\n" + "overridden at the level of an individual rule or group of rules."; +static char yes[] = "yes"; +static char no[] = "no"; + +static char *filename; /* file currently being parsed */ +static unsigned int linenum; /* input line number */ + +symbol_t types[] = { + { TYPE_STRING, "string" }, /* predicate data types */ + { TYPE_DOUBLE, "double" }, + { TYPE_INTEGER, "integer" }, + { TYPE_UNSIGNED, "unsigned" }, + { TYPE_PERCENT, "percent" }, + { TYPE_HOSTLIST, "hostlist" }, + { TYPE_INSTLIST, "instlist" }, + { TYPE_PRINT, "print" }, /* action types */ + { TYPE_SHELL, "shell" }, + { TYPE_ALARM, "alarm" }, + { TYPE_SYSLOG, "syslog" }, + { TYPE_RULE, "rule" }, /* fundamental type */ +}; +int numtypes = (sizeof(types)/sizeof(types[0])); + +symbol_t attribs[] = { + { ATTRIB_HELP, "help" }, + { ATTRIB_MODIFY, "modify" }, + { ATTRIB_ENABLED, "enabled" }, + { ATTRIB_DISPLAY, "display" }, + { ATTRIB_DEFAULT, "default" }, + { ATTRIB_DEFAULT, "summary" }, /* alias for "default" */ + { ATTRIB_VERSION, "version" }, /* applies to rules only */ + { ATTRIB_PREDICATE, "predicate" }, /* applies to rules only */ + { ATTRIB_ENUMERATE, "enumerate" }, /* applies to rules only */ +}; +int numattribs = (sizeof(attribs)/sizeof(attribs[0])); + +/* pmiefile variables */ +static int gotpath; /* state flag - has realpath been run */ +static char *save_area; /* holds text to restore on write */ +static int sa_size; /* current size of save area */ +static int sa_mark = 1; /* number used chars in save area, 1 for \0 */ +static dep_t *dlist; /* list of depreciated rules */ +static int dcount; /* number of entries in dlist */ +static char drulestring[] = "rule definition no longer exists"; +static char dverstring[] = "rule version no longer supported"; + +/* io-related stuff */ +extern int resized(void); + +char *get_pmiefile(void) { return &pmiefile[0]; } +char *get_rules(void) { return &rulepath[0]; } + +char * +get_aname(rule_t *r, atom_t *a) +{ + if (r == globals) + return &a->name[GLOBAL_LEN]; /* lose "globals." at the start */ + return a->name; +} + + +/* + * #### error reporting routines ### + */ + +static void +alloc_error(size_t request) +{ + if (linenum == 0) /* parsing user input, not a file */ + snprintf(errmsg, sizeof(errmsg), "insufficient memory for requested operation.\n" + " requested: %u bytes", (unsigned int)request); + else + snprintf(errmsg, sizeof(errmsg), "insufficient memory for parsing file.\n" + " requested: %u bytes", (unsigned int)request); +} + +static void +parse_error(char *expected, char *found) +{ + if (linenum == 0) /* parsing user input, not a file */ + snprintf(errmsg, sizeof(errmsg), "input is invalid - expected %.60s, got \"%.60s\"", + expected, found); + else + snprintf(errmsg, sizeof(errmsg), "file parsing error.\n" + " line number: %u (\"%s\")\n" + " expected: %.60s\n" + " found: %.60s", linenum, filename, expected, found); +} + +/* report attribute format error */ +static void +type_error(char *attrib, char *expected) +{ + snprintf(errmsg, sizeof(errmsg), "%s's value is invalid.\n" + " It should %s.", attrib, expected); +} + + +/* + * #### search routines ### + */ + +char * +find_rule(char *name, rule_t **rule) +{ + int i; + + for (i = 0; i < rulecount; i++) { + if (strcmp(rulelist[i].self.name, name) == 0) { + *rule = &rulelist[i]; + return NULL; + } + } + snprintf(errmsg, sizeof(errmsg), "rule named \"%s\" does not exist", name); + return errmsg; +} + +/* is global attribute 'atom' overridden by a local in 'rule's atom list */ +int +is_overridden(rule_t *rule, atom_t *atom) +{ + atom_t *aptr; + + for (aptr = rule->self.next; aptr != NULL; aptr = aptr->next) + if (strcmp(get_aname(globals, atom), get_aname(rule, aptr)) == 0) + return 1; + return 0; +} + +/* tests whether a rule is in the fullname group, if so returns 0 */ +int +rule_match(char *fullname, char *rulename) +{ + char *s; + + /* if fullname == rulename, then obvious match */ + if (strcmp(fullname, rulename) == 0) + return 1; + /* fullname may be a group, so match against rulename's groups */ + s = strcpy(token, rulename); /* reuse the token buffer */ + while ((s = strrchr(s, '.')) != NULL) { + s[0] = '\0'; + if (strcmp(token, fullname) == 0) + return 1; + } + return 0; +} + +/* find rule or set of rules in given rule or group name */ +char * +lookup_rules(char *name, rule_t ***rlist, unsigned int *count, int all) +{ + size_t size; + rule_t **rptr = NULL; + unsigned int i; + unsigned int matches = 0; + + /* search through the rulelist and build up rlist & count */ + for (i = 0; i < rulecount; i++) { + /* don't match globals if we've been asked for "all" */ + if ((all && i > 0) || rule_match(name, rulelist[i].self.name)) { + size = (1 + matches) * sizeof(rule_t *); + if ((rptr = (rule_t **)realloc(rptr, size)) == NULL) { + snprintf(errmsg, sizeof(errmsg), "insufficient memory for rule search" + " (needed %u bytes)\n", (unsigned int)size); + return errmsg; + } + rptr[matches] = &rulelist[i]; + matches++; + } + } + if (matches == 0) { + snprintf(errmsg, sizeof(errmsg), "no group or rule names match \"%s\"", name); + return errmsg; + } + *rlist = rptr; /* rlist must be freed by caller */ + *count = matches; + return NULL; +} + + +/* + * #### memory management routines ### + */ + +static char * +alloc_string(size_t size) +{ + char *p; + + if ((p = (char *)malloc(size)) == NULL) + alloc_error(size); + return p; +} + +atom_t * +alloc_atom(rule_t *r, atom_t atom, int global) +{ + atom_t *aptr; + atom_t *tmp; + + /* create some space and copy in the atom data we have already */ + if ((aptr = (atom_t *)malloc(sizeof(atom_t))) == NULL) { + alloc_error(sizeof(atom_t)); + return NULL; + } + *aptr = atom; + aptr->next = NULL; /* want contents of this atom, but not rest of list */ + if (global) { /* applies to all rules */ + r = rulelist; + aptr->global = 1; + } + + aptr->next = NULL; /* want contents of this atom, but not rest of list */ + + /* stick into the list of atoms associated with this rule */ + if (r->self.next == NULL) + r->self.next = aptr; /* insert at head of list */ + else { + for (tmp = r->self.next; tmp->next != NULL; tmp = tmp->next); + tmp->next = aptr; /* append at tail of list */ + } + + return aptr; +} + +rule_t * +alloc_rule(rule_t rule) +{ + size_t size; + rule_t *rptr; + + /* first check that name is unique */ + if (find_rule(rule.self.name, &rptr) == NULL) { + snprintf(errmsg, sizeof(errmsg), "rule name \"%s\" has already been used, duplicate name" + " found in:\n\t\"%.60s\", line %u.", rule.self.name, filename, linenum); + return NULL; + } + size = (rulecount+1) * sizeof(rule_t); + if ((rulelist = globals = (rule_t *)realloc(rulelist, size)) == NULL) { + alloc_error(size); + return NULL; + } + rptr = &rulelist[rulecount]; + *rptr = rule; + rulecount++; + return rptr; +} + + +/* + * #### misc parsing routines ### + */ + +/* given string contains no isgraph chars? */ +int +empty_string(char *s) +{ + char *str = s; + while (*str != '\0') { + if (isgraph((int)*str)) + return 0; + str++; + } + return 1; +} + + +/* lookup keyword, returns symbol identifier or -1 if not there */ +int +map_symbol(symbol_t *table, int tsize, char *symbol) +{ + int i; + + for (i = 0; i < tsize; i++) { + if (strcmp(symbol, table[i].symbol) == 0) + return table[i].symbol_id; + } + return -1; +} + +/* lookup symbol identifier, returns keyword or NULL if not there */ +char * +map_identifier(symbol_t *table, int tsize, int symbol_id) +{ + int i; + + for (i = 0; i < tsize; i++) { + if (symbol_id == table[i].symbol_id) + return table[i].symbol; + } + return NULL; +} + + +/* parse yes/no attribute value; returns 0 no, 1 yes, -1 error */ +int +map_boolean(char *token) +{ + if (token[0] == 'y') + return 1; + if (token[0] == 'n') + return 0; + parse_error("yes or no", token); + return -1; +} + + +/* scan token from string, return 1 ok, 0 no more, -1 error */ +int +string_token(char **scan, char *token) +{ + char *s = *scan; + char *t = token; + + while (! isgraph((int)*s) || *s == ',') { + if (*s == '\0') + return 0; + s++; + } + + if (*s == '\'') { /* quoted token */ + *t++ = *s++; + while (*s != '\'') { + if (*s == '\\') + s++; + if (*s == '\0') + return -1; + *t++ = *s++; + } + *t++ = *s++; + } + else { /* ordinary token */ + while (isgraph((int)*s) && *s != ',') + *t++ = *s++; + } + + *t = '\0'; + *scan = s; + return 1; +} + + +/* check proposed value for type, returns NULL/failure message */ +char * +validate(int type, char *name, char *value) +{ + int x; + char *s; + double d; + /* + * Below we don't care about the value from strtol() and strtoul() + * we're interested in updating the pointer "s". The messiness is + * thanks to gcc and glibc ... strtol() amd strtoul() are marked + * __attribute__((warn_unused_result)) ... to avoid warnings on all + * platforms, assign to dummy variables that are explicitly marked + * unused. + */ + long l __attribute__((unused)); + unsigned long ul __attribute__((unused)); + + switch (type) { + case TYPE_RULE: + case TYPE_STRING: + break; + case TYPE_SHELL: + case TYPE_PRINT: + case TYPE_ALARM: + case TYPE_SYSLOG: + if (map_boolean(value) < 0) + return errmsg; + break; + case TYPE_DOUBLE: + d = strtod(value, &s); + if (*s != '\0') { + type_error(name, "be a real number"); + return errmsg; + } + break; + case TYPE_INTEGER: + l = strtol(value, &s, 10); + if (*s != '\0') { + type_error(name, "be an integer number"); + return errmsg; + } + break; + case TYPE_UNSIGNED: + ul = strtoul(value, &s, 10); + if (*s != '\0') { + type_error(name, "be a positive integer number"); + return errmsg; + } + break; + case TYPE_PERCENT: + if ((s = strrchr(value, '%')) != NULL) /* % as final char is OK */ + *s = '\0'; + d = strtod(value, &s); + if (*s != '\0' || d < 0.0 || d > 100.0) { + type_error(name, "be a percentage between 0.0 and 100.0"); + return errmsg; + } + break; + case TYPE_HOSTLIST: + case TYPE_INSTLIST: + if ((s = alloc_string(strlen(value)+1)) == NULL) + return errmsg; + while ((x = string_token(&value, s)) > 0) + ; + if (x < 0) { + type_error(name, "include a closing single quote"); + return errmsg; + } + free(s); + break; + } + return NULL; +} + + +/* + * printable string form of atoms value, returns NULL terminated string + * pp (pretty print) argument valued 1 means use format appropriate for + * a user interface + */ + +char * +value_string(atom_t *atom, int pp) +{ + int key; + int i = 0; + int start = 1; + int quoted = 0; + char *s; + + switch (atom->type) { + case TYPE_RULE: + case TYPE_STRING: + if (pp) { + snprintf(token, sizeof(token), "\"%s\"", atom->data); + return token; + } + return atom->data; + case TYPE_PRINT: + case TYPE_SHELL: + case TYPE_ALARM: + case TYPE_SYSLOG: + return atom->enabled? yes : no; + case TYPE_HOSTLIST: + case TYPE_INSTLIST: + if (pp) token[i++] = '"'; + if (atom->type == TYPE_HOSTLIST) key = ':'; + else key = '#'; + for (s = atom->data; *s != '\0'; s++) { + if (!isspace((int)*s)) { + if (start && !pp) { + token[i++] = key; + token[i++] = '\''; + if (*s != '\'') + quoted = 0; + else if (!quoted) { + quoted = 1; + start = 0; + continue; + } + } + else if (*s == '\'' && !start && !pp) + quoted = 0; + start = 0; + } + else if (!pp && !quoted) { + quoted = 0; + if (i > 0 && token[i-1] != '\'') + token[i++] = '\''; + start = 1; + } + token[i++] = *s; + } + if (!pp && i > 0 && token[i-1] != '\'') + token[i++] = '\''; + else if (pp) token[i++] = '"'; + token[i++] = '\0'; + return token; + case TYPE_DOUBLE: + snprintf(token, sizeof(token), "%g", strtod(atom->data, &s)); + return token; + case TYPE_INTEGER: + snprintf(token, sizeof(token), "%ld", strtol(atom->data, &s, 10)); + return token; + case TYPE_UNSIGNED: + snprintf(token, sizeof(token), "%lu", strtoul(atom->data, &s, 10)); + return token; + case TYPE_PERCENT: + snprintf(token, sizeof(token), "%g%c", strtod(atom->data, &s), pp? '%':'\0'); + return token; + } + return NULL; +} + + +/* #### rules file parsing routines #### */ + + +/* returns attrib number or -1 if not an attribute */ +int +is_attribute(char *aname) +{ + return map_symbol(attribs, numattribs, aname); +} + +/* returns attrib value as a string, or NULL on error */ +char * +get_attribute(char *attrib, atom_t *atom) +{ + char *value = NULL; + + switch (map_symbol(attribs, numattribs, attrib)) { + case ATTRIB_HELP: + value = atom->help; + break; + case ATTRIB_MODIFY: + if (atom->modify) value = yes; + else value = no; + break; + case ATTRIB_ENABLED: + if (atom->enabled) value = yes; + else value = no; + break; + case ATTRIB_DISPLAY: + if (atom->display) value = yes; + else value = no; + break; + case ATTRIB_DEFAULT: + if (IS_ACTION(atom->type)) { + if (atom->enabled) value = yes; + else value = no; + } + else + value = atom->data; + break; + } + return value; +} + + +/* + * #### sorting routines ### + */ + +static int +compare_rules(const void *a, const void *b) +{ + rule_t *ra = (rule_t *)a; + rule_t *rb = (rule_t *)b; + return strcmp(ra->self.name, rb->self.name); +} + +void +sort_rules(void) +{ + /* start at second array element so that 'globals' is skipped */ + qsort(&rulelist[1], rulecount-1, sizeof(rule_t), compare_rules); +} + + +/* revert to default rules file values for a single atom (enabled/data/both) */ +static char * +atom_defaults(atom_t *a, atom_t *p, char *param) +{ + int sts = map_symbol(attribs, numattribs, param); + + if (sts != -1) { /* an attribute - is it valid? */ + if (sts == ATTRIB_ENABLED) { + if (a->global) { /* this was a global atom promoted to local */ + if (p) p->next = a->next; + free(a->name); + free(a); + a = NULL; + } + else { + a->enabled = a->denabled; /* reset enabled flag */ + a->changed = 0; + } + return NULL; + } + snprintf(errmsg, sizeof(errmsg), "variable \"%s\" is inappropriate for this " + "operation", param); + return errmsg; + } + else { + if (a->global) { /* this was a global atom promoted to local */ + if (p) p->next = a->next; + free(a->name); + free(a); + a = NULL; + } + else { + if (strcmp(a->data, a->ddata) != 0) { /* need to alloc mem? */ + free(a->data); + if ((a->data = strdup(a->ddata)) == NULL) { + snprintf(errmsg, sizeof(errmsg), "insufficient memory to set defaults"); + return errmsg; + } + } + a->enabled = a->denabled; + a->changed = 0; + } + return NULL; + } +} + +/* revert to default rules values for a rule or attribute (enabled/data/both) */ +char * +rule_defaults(rule_t *rule, char *param) +{ + atom_t *aptr; + atom_t *prev = NULL; + + if (param == NULL) { /* for this rule, reset all attributes */ + for (aptr = &rule->self; aptr != NULL; aptr = aptr->next) { + atom_defaults(aptr, prev, aptr->name); + prev = aptr; + } + } + else { /* find the associated atom, and just reset that */ + if (map_symbol(attribs, numattribs, param) != -1) { + rule->self.enabled = rule->self.denabled; /* reset enabled flag */ + rule->self.changed = 0; + return NULL; + } + for (aptr = &rule->self; aptr != NULL; aptr = aptr->next) { + if (strcmp(get_aname(rule, aptr), param) == 0) + return atom_defaults(aptr, prev, param); + prev = aptr; + } + } + return NULL; +} + +/* set an attribute field in an atom; returns NULL/failure message */ +static char * +set_attribute(rule_t *r, atom_t *atom, int attrib, char *value, int changed) +{ + char *s; + int sts; + + switch(attrib) { + case ATTRIB_HELP: + if (empty_string(value)) { + parse_error("non-empty string for help", value); + return errmsg; + } + if ((s = alloc_string(strlen(value)+1)) == NULL) + return errmsg; + atom->help = strcpy(s, value); + break; + case ATTRIB_MODIFY: + if ((sts = map_boolean(value)) < 0) + return errmsg; + atom->modify = sts; + break; + case ATTRIB_ENABLED: + if ((sts = map_boolean(value)) < 0) + return errmsg; + if (!changed) /* initially, set enabled to default */ + atom->denabled = sts; + atom->enabled = sts; + break; + case ATTRIB_DISPLAY: + if ((sts = map_boolean(value)) < 0) + return errmsg; + atom->display = sts; + break; + case ATTRIB_DEFAULT: + if (IS_ACTION(atom->type) && changed) { + if ((sts = map_boolean(value)) < 0) + return errmsg; + atom->enabled = sts; + } + else { /* actions from rules file (string) handled here too... */ + if (!IS_ACTION(atom->type) && + (validate(atom->type, get_aname(r, atom), value) != NULL)) + return errmsg; + sts = strlen(value)+1; + if ((s = alloc_string(sts)) == NULL) + return errmsg; + atom->data = strcpy(s, value); + if (!changed) { /* initially, set the default as well */ + if ((s = alloc_string(sts)) == NULL) { + free(atom->data); + atom->data = NULL; + return errmsg; + } + atom->ddata = strcpy(s, value); + } + } + break; + } + if (changed) + atom->changed = 1; + return NULL; +} + +/* set a parameter field in a rule; returns NULL/failure message */ +char * +value_change(rule_t *rule, char *param, char *value) +{ + int sts; + atom_t *aptr; + + if ((sts = map_symbol(attribs, numattribs, param)) != -1) + return set_attribute(rule, &rule->self, sts, value, 1); + else { + for (aptr = rule->self.next; aptr != NULL; aptr = aptr->next) { + if (strcmp(get_aname(rule, aptr), param) == 0) + return set_attribute(rule, aptr, ATTRIB_DEFAULT, value, 1); + } + /* if found in globals, promote the global to customised local.. */ + for (aptr = globals->self.next; aptr != NULL; aptr = aptr->next) { + if (strcmp(get_aname(globals, aptr), param) == 0) { + if ((aptr = alloc_atom(rule, *aptr, 0)) == NULL) + return errmsg; + if ((aptr->name = strdup(get_aname(globals, aptr))) == NULL) { + snprintf(errmsg, sizeof(errmsg), "insufficient memory to change value"); + return errmsg; + } + return set_attribute(globals, aptr, ATTRIB_DEFAULT, value, 1); + } + } + } + snprintf(errmsg, sizeof(errmsg), "variable \"%s\" is undefined for rule %s", + param, rule->self.name); + return errmsg; +} + +static char * +append_string(char *s, char *append, int len) +{ + size_t size = (strlen(s) + len + 1); + +#ifdef PMIECONF_DEBUG + fprintf(stderr, "debug - \"%s\" + (%d)\"%s\" = (%d chars)\n", + s, len, append, size); +#endif + if ((s = (char *)realloc(s, size)) == NULL) + return NULL; + strncat(s, append, len); + s[size-1] = '\0'; + return s; +} + +/* fix up value strings by doing variable expansion */ +char * +dollar_expand(rule_t *rule, char *string, int pp) +{ + atom_t *aptr; + char *tmp, *r; + char *sptr; + char *s; + char *mark = NULL; + char localbuf[TOKEN_LENGTH]; + +#ifdef PMIECONF_DEBUG + fprintf(stderr, "debug - dollar_expand %s in %s\n", string, rule->self.name); +#endif + if ((s = (char *)malloc(sizeof(char))) == NULL) + return NULL; + *s = '\0'; + + for (sptr = string; *sptr != '\0'; sptr++) { + if (*sptr == '\\' && *(sptr+1) == '$') { /* skip escaped $ */ + if ((s = append_string(s, sptr+1, 1)) == NULL) + return NULL; + sptr++; /* move passed the escaped char */ + continue; + } + if (*sptr == '$') { + if (mark == NULL) /* start of an expansion section */ + mark = sptr+1; + else { /* end of an expansion section */ + /* look through atom list & if not there search globally */ + strncpy(localbuf, mark, sptr - mark); + localbuf[sptr - mark] = '\0'; + mark = NULL; +#ifdef PMIECONF_DEBUG + fprintf(stderr, "debug - expand localbuf: %s\n", localbuf); +#endif + if ((tmp = get_attribute(localbuf, &rule->self)) == NULL) { + for (aptr = &rule->self; tmp == NULL && aptr != NULL; aptr = aptr->next) + if (strcmp(get_aname(rule, aptr), localbuf) == 0) + tmp = value_string(aptr, pp); + for (aptr = globals->self.next; tmp == NULL && aptr != NULL; aptr = aptr->next) + if (strcmp(get_aname(globals, aptr), localbuf) == 0) + tmp = value_string(aptr, pp); +#ifdef PMIECONF_DEBUG + fprintf(stderr, "debug - expanded localbuf? %s\n", tmp); +#endif + if (tmp == NULL) { + snprintf(errmsg, sizeof(errmsg), "variable \"$%s$\" in %s is undefined", + localbuf, rule->self.name); + free(s); + return NULL; + } + } + if (tmp != NULL) { + if ((r = dollar_expand(rule, tmp, pp)) == NULL) { + free(s); + return NULL; + } + if ((s = append_string(s, r, strlen(r))) == NULL) { + free(r); + return NULL; + } + free(r); + } + } + } + else if (mark == NULL) { /* need memory to hold this character */ + if ((s = append_string(s, sptr, 1)) == NULL) + return NULL; + } + } + if (mark != NULL) /* no terminating '$' */ + s = append_string(s, mark, strlen(mark)); +#ifdef PMIECONF_DEBUG + fprintf(stderr, "debug - expanded '%s' to '%s'\n", string, s); +#endif + return s; +} + + +/* + * #### main parsing routines ### + */ + +/* need a SIGWINCH-aware read routine for interactive pmieconf */ +static int +mygetc(FILE *f) +{ + int c; + + for (;;) { + setoserror(0); + c = getc(f); + /* did we get told to resize the window during read? */ + if (c == -1 && oserror() == EINTR && resized() == 1) + continue; /* signal handled, try reading again */ + break; + } + return c; +} + +/* + * skip leading white space and comments, return first character in next token + * or zero on end of file + */ +static int +prime_next_pread(FILE *f, int end) +{ + int c; + + do { + c = mygetc(f); + if (c == '#') + do { + c = mygetc(f); + } while (c != '\n' && c != end && c != EOF); + if (c == end) + return 0; + else if (c == EOF) + return -2; + if (c == '\n' && end != '\n') + linenum++; + } while (!isgraph(c)); + return c; +} + +/* + * read next input token; returns 1 ok, 0 end, -1 error, -2 EOF (if end!=EOF) + * nb: `end' can be either EOL or EOF, depending on use of this routine + */ +int +read_token(FILE *f, char *token, int token_length, int end) +{ + int c; + int n = 0; + + switch (c = prime_next_pread(f, end)) { + case 0: /* end */ + case -2: /* EOF */ + return c; + case '"': /* scan string */ + c = mygetc(f); + while (c != '"') { + if (c == '\\') + c = mygetc(f); + if (c == end || c == EOF || n == token_length) { + token[n] = '\0'; + parse_error("end-of-string", token); + return -1; + } + if (c == '\n' && end != '\n') + linenum++; + token[n++] = c; + c = mygetc(f); + } + break; + case ';': + case '=': + token[n++] = c; /* single char token */ + break; + default: /* some other token */ + while (isgraph(c)) { + if (c == '=' || c == ';') { + ungetc(c, f); + break; + } + if (n == token_length) { + token[n] = '\0'; + parse_error("end-of-token", token); + return -1; + } + token[n++] = c; + c = mygetc(f); + if (c == end || c == EOF) + ungetc(c, f); + } + if (c == '\n' && end != '\n') + linenum++; + break; + } + + token[n] = '\0'; + return 1; +} + +/* + * get attribute list part of an atom; returns -1 on error, 0 on reaching + * the end of the attribute list, and 1 at end of each attribute. + */ +static int +read_next_attribute(FILE *f, char **attr, char **value) +{ + int sts; + + if ((sts = read_token(f, token, TOKEN_LENGTH, EOF)) <= 0) { + if (sts == 0) + parse_error("attribute or ';'", "end-of-file"); + return -1; + } + if (token[0] == ';') + return 0; + if (map_symbol(attribs, numattribs, token) < 0) { + parse_error("attribute keyword", token); + return -1; + } + if ((*attr = alloc_string(strlen(token)+1)) == NULL) + return -1; + strcpy(*attr, token); + if ((sts = read_token(f, token, TOKEN_LENGTH, EOF)) <= 0 + || token[0] != '=') { + if (sts == 0) + parse_error("=", "end-of-file"); + else + parse_error("=", token); + free(*attr); + return -1; + } + if ((sts = read_token(f, token, TOKEN_LENGTH, EOF)) <= 0) { + if (sts == 0) + parse_error("attribute value", "end-of-file"); + else + parse_error("attribute value", token); + free(*attr); + return -1; + } + if ((*value = alloc_string(strlen(token)+1)) == NULL) { + free(*attr); + return -1; + } + strcpy(*value, token); + return 1; +} + + +/* parse an atom, return NULL/failure message */ +static char * +read_atom(FILE *f, rule_t *r, char *name, int type, int global) +{ + int sts; + int attrib; + char *attr; + char *value; + atom_t atom; + + memset(&atom, 0, sizeof(atom_t)); + atom.name = name; + atom.type = type; + atom.enabled = atom.display = atom.modify = 1; /* defaults */ + for (;;) { + if ((sts = read_next_attribute(f, &attr, &value)) < 0) + return errmsg; + else if (sts == 0) { /* end of parameter list */ + if (alloc_atom(r, atom, global) == NULL) + return errmsg; + break; + } + else { + if ((attrib = map_symbol(attribs, numattribs, attr)) < 0) { + parse_error("attribute keyword", attr); + goto fail; + } + if (set_attribute(r, &atom, attrib, value, 0) != NULL) + goto fail; + free(attr); + free(value); + } + } + return NULL; + +fail: + free(attr); + free(value); + return errmsg; +} + + +/* parse type-identifier pair, return NULL/failure message */ +static char * +read_type(FILE *f, rule_t *r, int *type, int *global, char **name) +{ + int sts; + + /* read type - rule, percent, double, unsigned, string... */ + if ((sts = read_token(f, token, TOKEN_LENGTH, EOF)) < 0) + return errmsg; + else if (sts == 0) + return NULL; + if ((*type = map_symbol(types, numtypes, token)) < 0) { + parse_error("type keyword", token); + return errmsg; + } + + /* read name identifying this rule/atom of type '*type' */ + if ((sts = read_token(f, token, TOKEN_LENGTH, EOF)) <= 0) + return errmsg; + if ((*name = alloc_string(strlen(token)+1)) == NULL) + return errmsg; + strcpy(*name, token); + *global = (strncmp(*name, "global.", GLOBAL_LEN) == 0)? 1 : 0; + + /* do some simple validity checks */ + if (IS_RULE(*type) && strncmp(*name, global_name, GLOBAL_LEN-1) == 0) { + snprintf(errmsg, sizeof(errmsg), "rule name may not be \"%s\" - this is reserved", + global_name); + free(*name); + return errmsg; + } + if (r == NULL) { /* any rule defined yet? - simple validity checks */ + if (*global && IS_RULE(*type)) { + snprintf(errmsg, sizeof(errmsg), "rules not allowed in global group: \"%s\"", *name); + free(*name); + return errmsg; + } + else if (!*global && !IS_RULE(*type)) { /* not global, and no rule */ + snprintf(errmsg, sizeof(errmsg), "no rule defined, cannot make sense of %s \"%s\"" + " without one\n line number: %u (\"%s\")\n", + types[*type].symbol, *name, linenum, filename); + free(*name); + return errmsg; + } + } + return NULL; +} + +/* set an attribute field in an atom; returns NULL/failure message */ +static char * +set_rule_attribute(rule_t *rule, int attrib, char *value) +{ + char *s; + + if (attrib == ATTRIB_PREDICATE) { + if ((s = alloc_string(strlen(value)+1)) == NULL) + return errmsg; + rule->predicate = strcpy(s, value); + return NULL; + } + if (attrib == ATTRIB_ENUMERATE) { + if ((s = alloc_string(strlen(value)+1)) == NULL) + return errmsg; + rule->enumerate = strcpy(s, value); + return NULL; + } + else if (attrib == ATTRIB_VERSION) { + rule->version = strtoul(value, &s, 10); + if (*s != '\0') { + parse_error("version number", "be a positive integer number"); + return errmsg; + } + return NULL; + } + /* else */ + return set_attribute(rule, &rule->self, attrib, value, 0); +} + + +/* parse a single "rule" expression, return NULL/failure message */ +static char * +read_rule(FILE *f, rule_t **r, char *name) +{ + int sts; + int attrib; + char *attr; + char *value; + rule_t rule; + + memset(&rule, 0, sizeof(rule_t)); + rule.self.name = name; + rule.self.type = TYPE_RULE; + rule.version = rule.self.enabled = rule.self.display = 1; /* defaults */ + for (;;) { + if ((sts = read_next_attribute(f, &attr, &value)) < 0) + return errmsg; + else if (sts == 0) { /* end of attribute list */ + if ((*r = alloc_rule(rule)) == NULL) + return errmsg; + break; + } + else { + if ((attrib = map_symbol(attribs, numattribs, attr)) < 0) { + parse_error("rule attribute keyword", attr); + goto fail; + } + if (set_rule_attribute(&rule, attrib, value) != NULL) + goto fail; + free(attr); + free(value); + } + } + return NULL; + +fail: + free(attr); + free(value); + return errmsg; +} + + +/* parse rule description file; returns NULL/failure message */ +static char * +read_all_rules(FILE *f) +{ + rule_t *rule = NULL; /* current rule */ + char *name = NULL; + int type = 0; + int global = 0; + + /* rule files have quite a simple grammar, along these lines: + TYPE identifier [ ATTRIB '=' value ]* ';' + */ + for (;;) { + if (read_type(f, rule, &type, &global, &name) != NULL) + return errmsg; + if (feof(f)) /* end of file reached without error */ + break; + if (type == TYPE_RULE) { + if (read_rule(f, &rule, name) != NULL) + return errmsg; + } + else { + if (read_atom(f, rule, name, type, global) != NULL) + return errmsg; + } + } + + return NULL; +} + + +/* + * validate header of rule description file, return NULL/failure message + */ +static char * +read_pheader(FILE *f) +{ + int c; + + c = getc(f); + if (c != '#' || read_token(f, token, TOKEN_LENGTH, EOF) != 1 || + strcmp(token, RULES_FILE) != 0 || + read_token(f, token, TOKEN_LENGTH, EOF) != 1) { + snprintf(errmsg, sizeof(errmsg), "%s is not a rule description file (bad header)\n" + "found \"%s\", expected \"%s\"", filename, + token, RULES_FILE); + return errmsg; + } + else if (strcmp(token, RULES_VERSION) != 0) { /* one version only */ + snprintf(errmsg, sizeof(errmsg), "unknown version number in %s: \"%s\" (expected %s)", + filename, token, RULES_VERSION); + return errmsg; + } + return NULL; +} + + +/* + * builds up rule data structures for all rule files in given directory + * and all its subdirectories, returns NULL/failure message + */ +char * +read_rule_subdir(char *subdir) +{ + struct stat sbuf; + struct dirent *dp; + FILE *fp; + DIR *dirp; + char fullpath[MAXPATHLEN+1]; + + if (stat(subdir, &sbuf) < 0) { + snprintf(errmsg, sizeof(errmsg), "cannot stat %s: %s", + subdir, osstrerror()); + return errmsg; + } + if (!S_ISDIR(sbuf.st_mode)) { + if ((fp = fopen(subdir, "r")) == NULL) { + snprintf(errmsg, sizeof(errmsg), "cannot open %s: %s", + subdir, osstrerror()); + return errmsg; + } + linenum = 1; + filename = subdir; + if (read_pheader(fp) == NULL) { + if (read_all_rules(fp) != NULL) { + fclose(fp); + return errmsg; + } + } +#ifdef PMIECONF_DEBUG + else { + fprintf(stderr, "debug - %s isn't a pmie rule file: %s\n", + filename, errmsg); + } +#endif + fclose(fp); + } + else { + /* iterate through the rules directory and for each subdirectory */ + /* fetch all the rules along with associated parameters & values */ + + if ((dirp = opendir(subdir)) == NULL) { + snprintf(errmsg, sizeof(errmsg), "cannot opendir %s: %s", subdir, osstrerror()); + return errmsg; + } + while ((dp = readdir(dirp)) != NULL) { /* groups */ + if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0) + continue; + snprintf(fullpath, sizeof(fullpath), "%s%c%s", subdir, SEP, dp->d_name); + if (read_rule_subdir(fullpath) != NULL) { /* recurse */ + closedir(dirp); + return errmsg; + } + } + closedir(dirp); + } + return NULL; +} + + +/* ##### pmiefile parsing routines #### */ + + +/* returns NULL on successfully adding rule to list, else failure message */ +char * +deprecate_rule(char *name, unsigned int version, int type) +{ + int index; + + /* first check to see if this rule is deprecated already */ + for (index = 0; index < dcount; index++) { + if (strcmp(dlist[index].name, name) == 0 + && dlist[index].version == version) + return NULL; + } + + /* get the memory we need & then keep a copy of deprecated rule info */ + if ((dlist = (dep_t *)realloc(dlist, (dcount+1)*sizeof(dep_t))) == NULL + || (dlist[dcount].name = strdup(name)) == NULL) { + snprintf(errmsg, sizeof(errmsg), "insufficient memory to deprecate rule %s", name); + return errmsg; + } + dlist[dcount].type = type; + dlist[dcount].version = version; + if (type == DEPRECATE_NORULE) + dlist[dcount].reason = drulestring; + else + dlist[dcount].reason = dverstring; + dcount++; + return NULL; +} + +/* + * makes list of deprecated rules available to caller (for warning message) + * nb: called following a pmiefile write to see what was deprecated during + * that write - subsequent writing of this pmiefile should not deprecate + * anything (deprecations done once & not again). caller must free list. + */ +int +fetch_deprecated(dep_t **list) +{ + int sts; + + *list = dlist; + sts = dcount; + dcount = 0; + return sts; +} + +/* merges local customisations back into the rules atom list */ +static char * +merge_local(unsigned int version, char *name, char *attrib, char *value) +{ + atom_t *aptr; + rule_t *rule; + int a; + + /* + first find the rule to which this local belongs, then figure + out what sort of attribute this really is, and finally merge + the customisation back into the values in the rules attribs. + */ + + if (find_rule(name, &rule) != NULL) /* in pmiefile but not rules */ + return NULL; /* this will be deprecated later */ + else if (rule->version != version) /* no rule for this version */ + return NULL; /* this will be deprecated later */ + + if ((a = is_attribute(attrib)) != -1) + return set_attribute(rule, &rule->self, a, value, 1); + else { /* search through this rules list of atoms */ + for (aptr = rule->self.next; aptr != NULL; aptr = aptr->next) { + if (strcmp(get_aname(rule, aptr), attrib) == 0) + return set_attribute(rule, aptr, ATTRIB_DEFAULT, value, 1); + } + for (aptr = globals->self.next; aptr != NULL; aptr = aptr->next) { + if (strcmp(get_aname(globals, aptr), attrib) == 0) { + /* promote global to become a local */ + if ((aptr = alloc_atom(rule, *aptr, 0)) == NULL) + return errmsg; + if ((aptr->name = strdup(get_aname(globals, aptr))) == NULL) { + snprintf(errmsg, sizeof(errmsg), "insufficient memory to change value"); + return errmsg; + } + return set_attribute(globals, aptr, ATTRIB_DEFAULT, value, 1); + } + } + } + snprintf(errmsg, sizeof(errmsg), "variable \"%s\" is undefined for rule %s", + attrib, name); + return errmsg; +} + + +/* + * #### produce pmie configuration file from rules ### + */ + +char * +action_string(int type) +{ + switch (type) { + case TYPE_PRINT: return "print"; + case TYPE_SHELL: return "shell"; + case TYPE_ALARM: return "alarm"; + case TYPE_SYSLOG: return "syslog"; + } + return NULL; +} + +static int +expand_action(FILE *f, int count, rule_t *rule, atom_t *atom) +{ + char *p; + char *str; + + if (IS_ACTION(atom->type) && atom->enabled) { + if ((str = dollar_expand(rule, " $holdoff$ ", 0)) == NULL) + return count; + if (count == 0) + fprintf(f, " -> "); + else + fprintf(f, "\n & "); + fprintf(f, "%s", action_string(atom->type)); + fprintf(f, "%s", str); + free(str); + if ((str = dollar_expand(rule, atom->data, 0)) == NULL) { + fprintf(stderr, "Warning - failed to expand action for rule %s\n" + " string: \"%s\"\n", rule->self.name, atom->data); + /* keep going - too late to bail out without file corruption */ + } +#ifdef PMIECONF_DEBUG + else { + fprintf(stderr, "expanded action= \"%s\"\n", str); + } +#endif + fputc('"', f); + for (p = str; p != NULL && *p; p++) { /* expand the '^' character */ + if (*p == '/' && *(p+1) == '^') { + fputc(*p, f); p++; + fputc(*p, f); p++; + } + else if (*p == '^') + fputs("\" \"", f); + else fputc(*p, f); + } + fputc('"', f); + if (str != NULL) + free(str); + return count + 1; + } + return count; +} + + +/* + * this struct and the enumerate function are used only in generate_rules() + * and the enumerate() routines, for enumeration of either a hostlist or an + * instlist + */ + +typedef struct { + atom_t *atom; + int nvalues; + char **valuelist; + char *restore; +} enumlist_t; + +static enumlist_t *list; +static int nlistitems; +static int writecount; + +/* + * expands and writes out a single rule, and optionally the delta + * note: in the single rule case (not enumerated), we absolutely + * must write out the delta every time + */ +static char * +write_rule(FILE *f, rule_t *rule) +{ + atom_t *aptr; + char *dgen = NULL; /* holds generated "delta" */ + char *pgen; /* holds generated "predicate" */ + int actions = 0; + +#ifdef PMIECONF_DEBUG + fprintf(stderr, "debug - writing rule %s\n", rule->self.name); +#endif + + if (writecount == 0 && (dgen = dollar_expand(rule, "$delta$", 0)) == NULL) { + snprintf(errmsg, sizeof(errmsg), "\"$delta$\" variable expansion failed for rule %s", + rule->self.name); + return errmsg; + } + if ((pgen = dollar_expand(rule, rule->predicate, 0)) == NULL) { + snprintf(errmsg, sizeof(errmsg), "\"$predicate$\" variable expansion failed " + "for rule %s", rule->self.name); + return errmsg; + } + if (writecount == 0) { + fprintf(f, "// %u %s\ndelta = %s;\n%s = \n", rule->version, + rule->self.name, dgen, rule->self.name); + free(dgen); + } + else /* we're enumerating, need to differentiate rule names */ + fprintf(f, "%s%u = \n", rule->self.name, writecount); + fputs(pgen, f); + free(pgen); + for (aptr = rule->self.next; aptr != NULL; aptr = aptr->next) + actions = expand_action(f, actions, rule, aptr); + for (aptr = globals->self.next; aptr != NULL; aptr = aptr->next) + actions = expand_action(f, actions, rule, aptr); + fprintf(f, ";\n\n"); + + writecount++; + return NULL; +} + +/* parses the "enumerate" value string passed in thru the rules file */ +char * +parse_enumerate(rule_t *rule) +{ + atom_t *ap; + char *p = rule->enumerate; + int needsave = 0; /* should we save this variable name yet? */ + int i = 0; + +#ifdef PMIECONF_DEBUG + fprintf(stderr, "debug - parse_enumerate called for %s\n", rule->self.name); +#endif + + nlistitems = 0; + list = NULL; + while (*p != '\0') { + if (!isspace((int)*p)) { + needsave = 1; + token[i++] = *p; + } + p++; + if ((isspace((int)*p) && needsave) || *p == '\0') { + token[i] = '\0'; + i = 0; + if (map_symbol(attribs, numattribs, token) != -1) { + snprintf(errmsg, sizeof(errmsg), "cannot enumerate rule %s using attribute" + " \"%s\"", rule->self.name, token); + return errmsg; + } + else { + for (ap = rule->self.next; ap != NULL; ap = ap->next) + if (strcmp(get_aname(rule, ap), token) == 0) + goto foundname; + for (ap = globals->self.next; ap != NULL; ap = ap->next) + if (strcmp(get_aname(globals, ap), token) == 0) + goto foundname; + snprintf(errmsg, sizeof(errmsg), "variable \"%s\" undefined for enumerated" + " rule %s", token, rule->self.name); + return errmsg; + } +foundname: + if (ap->type != TYPE_HOSTLIST && ap->type != TYPE_INSTLIST) { + snprintf(errmsg, sizeof(errmsg), "rules file error - \"$%s$\" in \"enumerate\" " + "clause of rule %s is not of type hostlist or instlist", + token, rule->self.name); + return errmsg; + } + /* increase size of list & keep a copy of the variable name */ + if ((list = realloc(list, (nlistitems+1)*sizeof(enumlist_t))) == NULL) { + snprintf(errmsg, sizeof(errmsg), "insufficient memory to write rules"); + return errmsg; + } + list[nlistitems].atom = ap; + list[nlistitems].nvalues = 0; + list[nlistitems].valuelist = NULL; + list[nlistitems].restore = NULL; +#ifdef PMIECONF_DEBUG + fprintf(stderr, "debug - variable %s added to enum list (#%d)\n", + list[nlistitems].atom->name, nlistitems); +#endif + nlistitems++; + needsave = 0; + } + } + return NULL; +} + +/* + * converts a host/inst list into individual elements, overwrites liststr + * (turns all spaces to NULLs to mark string ends - reduces mallocing) + */ +char ** +get_listitems(char *liststr, int *count) +{ + char **result = NULL; + char *p = liststr; + int keepwhite = 0; + int startagain = 0; /* set to signify new list item has started */ + int ptrcount = 0; + + if ((result = realloc(result, (ptrcount+1) * sizeof(char *))) == NULL) { + snprintf(errmsg, sizeof(errmsg), "insufficient memory to get list elements"); + return NULL; + } + result[ptrcount++] = p; + while (*p != '\0') { + if (!isspace((int)*p) || keepwhite) { + if (startagain) { + result = realloc(result, (ptrcount+1) * sizeof(char *)); + if (result == NULL) { + snprintf(errmsg, sizeof(errmsg), "insufficient memory to get list elements"); + return NULL; + } + result[ptrcount++] = p; + startagain = 0; + } + if (*p == '\\') + p++; + else if (*p == '\'') + keepwhite = !keepwhite; + } + else { + *p = '\0'; + startagain = 1; + } + p++; + } +#ifdef PMIECONF_DEBUG + fputs("debug - instances are:", stderr); + for (keepwhite = 0; keepwhite < ptrcount; keepwhite++) + fprintf(stderr, " %s", result[keepwhite]); + fputs("\n", stderr); +#endif + *count = ptrcount; + return result; +} + +/* expands variables from the "enumerate" string in the rules file */ +char * +expand_enumerate(rule_t *rule) +{ + int i, j; + char *p; + +#ifdef PMIECONF_DEBUG + fprintf(stderr, "debug - expanding enum variables for rule %s\n", + rule->self.name); +#endif + + for (i = 0; i < nlistitems; i++) { + if ((p = dollar_expand(rule, list[i].atom->data, 0)) == NULL) + return errmsg; + if ((list[i].valuelist = realloc(list[i].valuelist, + sizeof(char *) * (list[i].nvalues + 1))) == NULL) { + snprintf(errmsg, sizeof(errmsg), "insufficient memory for rule enumeration"); + free(p); + return errmsg; + } + if ((list[i].valuelist = get_listitems(p, &j)) == NULL) { + free(p); + return errmsg; + } + list[i].nvalues = j; +#ifdef PMIECONF_DEBUG + fprintf(stderr, "debug - %s value list:", list[i].atom->name); + for (j = 0; j < list[i].nvalues; j++) + fprintf(stderr, " %s", list[i].valuelist[j]); + fprintf(stderr, "\n"); +#endif + } + return NULL; +} + +void +enumerate(FILE *f, rule_t *r, enumlist_t *listoffset, int wssize, char **wset) +{ + int i; + + if (wssize < nlistitems) { + for (i = 0; i < listoffset->nvalues; i++) { + /* add current word to word set, and move down a level */ + wset[wssize] = listoffset->valuelist[i]; + enumerate(f, r, &list[wssize+1], wssize+1, wset); + } + } + else { /* have a full set, generate rule */ +#ifdef PMIECONF_DEBUG + for (i = 0; i < wssize; i++) + printf("%s=%s ", list[i].atom->name, wset[i]); + printf("\n"); +#endif + for (i = 0; i < wssize; i++) { + list[i].restore = list[i].atom->data; + list[i].atom->data = wset[i]; + } + + write_rule(f, r); + + for (i = 0; i < wssize; i++) + list[i].atom->data = list[i].restore; + } +} + +/* generate pmie rules for rule, returns rule string/NULL */ +static char * +generate_rules(FILE *f, rule_t *rule) +{ + int i; + + if (rule->self.enabled == 0) + return NULL; + if (rule->enumerate == NULL) { + writecount = 0; + write_rule(f, rule); + } + else { + char **workingset; /* holds current variable values set */ + +#ifdef PMIECONF_DEBUG + fprintf(stderr, "debug - generating enumerated rule %s\n", + rule->self.name); +#endif + + /* "enumerate" attrib is a space-separated list of variables */ + /* 1.create a list of variable info structs (name->valuelist) */ + /* 2.recurse thru lists, when each set built, write out rule */ + + if ((parse_enumerate(rule)) != NULL) + return errmsg; + if ((expand_enumerate(rule)) != NULL) + return errmsg; + if ((workingset = malloc(nlistitems * sizeof(char*))) == NULL) { + snprintf(errmsg, sizeof(errmsg), "insufficient memory to generate rules"); + return errmsg; + } + writecount = 0; + enumerate(f, rule, list, 0, workingset); + free(workingset); + for (i = 0; i < nlistitems; i++) + free(list[i].valuelist); /* alloc'd by dollar_expand */ + free(list); + } + + return NULL; +} + +/* generate local configuration changes, returns rule string/NULL */ +static char * +generate_pmiefile(FILE *f, rule_t *rule) +{ + atom_t *aptr; + + for (aptr = &rule->self; aptr != NULL; aptr = aptr->next) { + if (aptr->changed == 0) + continue; + if (IS_RULE(aptr->type)) { + fprintf(f, "// %u %s %s = %s\n", rule->version, + get_aname(rule, aptr), "enabled", + rule->self.enabled? "yes" : "no"); + } + else { + fprintf(f, "// %u %s %s = %s\n", rule->version, rule->self.name, + get_aname(rule, aptr), value_string(aptr, 1)); + } + } + return NULL; +} + +/* generate pmie rules and write to file, returns NULL/failure message */ +char * +write_pmiefile(char *program, int autocreate) +{ + time_t now = time(NULL); + char *p, *msg = NULL; + char buf[MAXPATHLEN+10]; + char *fname = get_pmiefile(); + FILE *fp; + int i; + + /* create full path to file if it doesn't exist */ + if ((p = strrchr(fname, '/')) != NULL) { + struct stat sbuf; + + *p = '\0'; /* p is the dirname of fname */ + if (stat(fname, &sbuf) < 0) { + snprintf(buf, sizeof(buf), "/bin/mkdir -p %s", fname); + if (system(buf) < 0) { + snprintf(errmsg, sizeof(errmsg), "failed to create directory \"%s\"", p); + return errmsg; + } + } + else if (!S_ISDIR(sbuf.st_mode)) { + snprintf(errmsg, sizeof(errmsg), "\"%s\" exists and is not a directory", p); + return errmsg; + } + fname[strlen(fname)] = '/'; /* stitch together */ + } + + if ((fp = fopen(fname, "w")) == NULL) { + snprintf(errmsg, sizeof(errmsg), "cannot write file %s: %s", fname, osstrerror()); + return errmsg; + } + else if (!gotpath) { + strcpy(token, fname); + if (realpath(token, pmiefile) == NULL) { + fclose(fp); + snprintf(errmsg, sizeof(errmsg), "failed to resolve %s realpath: %s", token, osstrerror()); + return errmsg; + } + gotpath = 1; + } + + fprintf(fp, "// %s %s %s\n", PMIE_FILE, PMIE_VERSION, get_rules()); + for (i = 0; i < rulecount; ++i) + if ((msg = generate_pmiefile(fp, &rulelist[i])) != NULL) + goto imouttahere; + fputs("// end\n//\n", fp); + + fprintf(fp, "%s// %sgenerated by %s on: %s//\n\n", + START_STRING, autocreate ? "Auto-" : " ", program, ctime(&now)); + for (i = 1; i < rulecount; ++i) /* 1: start _after_ globals */ + if ((msg = generate_rules(fp, &rulelist[i])) != NULL) + goto imouttahere; + + /* write user-modifications area */ + fprintf(fp, END_STRING); + /* finally any other local changes */ + if (save_area != NULL) + fputs(save_area, fp); + +imouttahere: + fclose(fp); + return msg; +} + + +/* + * #### pmiefile manipulation routines ### + */ + +/* + * skip leading white space and comments, return first character in next token + * or zero on end of file + */ +static int +prime_next_lread(FILE *f) +{ + int c; + + do { + c = getc(f); + if (c == EOF) + return 0; + if (c == '\n') { + linenum++; + if (getc(f) != '/') return 0; + if (getc(f) != '/') return 0; + } + } while (! isgraph(c)); + return c; +} + +/* read next input token; returns 1 ok, 0 eof, -1 error */ +static int +read_ltoken(FILE *f) +{ + int c; + int n = 0; + + switch (c = prime_next_lread(f)) { + case 0: /* EOF */ + return 0; + case '"': /* scan string */ + c = getc(f); + while (c != '"') { + if (c == '\\') + c = getc(f); + if (c == EOF || n == TOKEN_LENGTH) { + token[n] = '\0'; + parse_error("end-of-string", token); + return -1; + } + if (c == '\n') { + token[n] = '\0'; + parse_error("end-of-string", "end-of-line"); + return -1; + } + token[n++] = c; + c = getc(f); + } + break; + case '=': + token[n++] = c; /* single char token */ + break; + default: /* some other token */ + while (isgraph(c)) { + if (c == '=') { + ungetc(c, f); + break; + } + if (n == TOKEN_LENGTH) { + token[n] = '\0'; + parse_error("end-of-token", token); + return -1; + } + token[n++] = c; + c = getc(f); + } + if (c == '\n') { + linenum++; + if (strncmp(token, "end", 3) == 0) break; + if (getc(f) != '/') return 0; + if (getc(f) != '/') return 0; + } + break; + } + + token[n] = '\0'; + return 1; +} + + +/* allocates memory & appends a string to the save area */ +char * +save_area_append(char *str) +{ + int size = strlen(str); + + while ( (size+1) >= (sa_size-sa_mark) ) { + sa_size += 256; /* increase area by 256 bytes at a time */ + if ((save_area = (char *)realloc(save_area, sa_size)) == NULL) + return NULL; + } + if (sa_mark == 1) + save_area = strcpy(save_area, str); + else + save_area = strcat(save_area, str); + sa_mark += size; + return save_area; +} + +/* read and save text which is to be restored on pmiefile write */ +static char * +read_restore(FILE *f) +{ + unsigned int version; + rule_t *rule; + char buf[LINE_LENGTH]; + int saverule = 0; + int saveall = 0; + + do { + if (fgets(buf, LINE_LENGTH, f) == NULL) + break; + if (!saveall) { /* not yet at start of explicit "save" position */ + if (strcmp(buf, END_STRING) == 0) + saveall = 1; + else if (sscanf(buf, "// %u %s\n", &version, token) == 2) { + /* + * where the rule has disappeared or its version does not match + * the one in the pmiefile, add the rule name & version to list + * of rules to be deprecated (i.e. moved to the "save area") + */ + /* check that we still have this rule definition */ + if (find_rule(token, &rule) != NULL) { /* not found! */ + snprintf(buf, sizeof(buf), "// %u %s (deprecated, %s)\n", + version, token, drulestring); + deprecate_rule(token, version, DEPRECATE_NORULE); + saverule = 1; + } + else if (rule->version != version) { /* not supported! */ + snprintf(buf, sizeof(buf), "// %u %s (deprecated, %s)\n", + version, token, dverstring); + deprecate_rule(token, version, DEPRECATE_VERSION); + saverule = 1; + } + else + saverule = 0; + } + if (!saveall && saverule) { + if (save_area_append("// ") == NULL || + save_area_append(buf) == NULL) { + snprintf(errmsg, sizeof(errmsg), "insufficient memory to deprecate a rule"); + return errmsg; + } + } + } + else if (save_area_append(buf) == NULL) { + snprintf(errmsg, sizeof(errmsg), "insufficient memory to preserve save area"); + return errmsg; + } + } while (!feof(f)); + return NULL; +} + + +/* read custom values, return NULL/failure message */ +static char * +read_locals(FILE *f) +{ + int sts; + char *rule; + char *attrib; + char *value; + unsigned int version; + + /* general pmiefile format: version rulename attribute = value */ + + for (;;) { + if ((sts = read_ltoken(f)) < 0) + return errmsg; + else if (sts == 0) { + parse_error("rule identifier or \"end\" symbol", "end-of-file"); + return errmsg; + } + if (strcmp("end", token) == 0) + break; + + /* read the version number for this rule */ + version = strtoul(token, &value, 10); + if (*value != '\0') { + parse_error("version number", token); + return errmsg; + } + + /* read the name of the rule */ + if ((sts = read_ltoken(f)) != 1 || + (rule = alloc_string(strlen(token)+1)) == NULL) { + if (sts == 0) + parse_error("rule name", token); + return errmsg; + } + strcpy(rule, token); + + /* read the rule attribute component */ + if ((sts = read_ltoken(f)) != 1 || + (attrib = alloc_string(strlen(token)+1)) == NULL) { + free(rule); + if (sts == 0) + parse_error("rule attribute", token); + return errmsg; + } + strcpy(attrib, token); + + if ((sts = read_ltoken(f)) != 1 || strcmp("=", token) != 0) { + free(rule); free(attrib); + if (sts == 0) + parse_error("'=' symbol", "end-of-file"); + return errmsg; + } + + /* read the modified value of this attribute */ + if ((sts = read_ltoken(f)) != 1 || + (value = alloc_string(strlen(token)+1)) == NULL) { + free(rule); free(attrib); + if (sts == 0) + parse_error("rule attribute value", "end-of-file"); + return errmsg; + } + strcpy(value, token); + + if (merge_local(version, rule, attrib, value) != NULL) { + free(rule); free(attrib); free(value); + return errmsg; + } + free(rule); free(attrib); free(value); /* no longer need these */ + } + return NULL; +} + +/* validate header of rule customizations file, return NULL/failure message */ +static char * +read_lheader(FILE *f, char **proot) +{ + if (read_ltoken(f) != 1 || strcmp(token, "//") || read_ltoken(f) != 1 + || strcmp(token, PMIE_FILE) || read_ltoken(f) != 1) { + snprintf(errmsg, sizeof(errmsg), "%s is not a rule customization file (bad header)", + filename); + return errmsg; + } + else if (strcmp(token, PMIE_VERSION) != 0) { /* one version only */ + snprintf(errmsg, sizeof(errmsg), "unknown version number in %s: \"%s\" (expected %s)", + filename, token, PMIE_VERSION); + return errmsg; + } + else if (read_ltoken(f) != 1) { + snprintf(errmsg, sizeof(errmsg), "no rules path specified in %s after version number", + filename); + return errmsg; + } + *proot = token; + return NULL; +} + + +/* + * read the pmiefile format into global data structures + */ +char * +read_pmiefile(char *warning, size_t warnlen) +{ + char *tmp = NULL; + char *p, *home; + FILE *f; + char *rule_path_sep; + + if ((f = fopen(get_pmiefile(), "r")) == NULL) { + if (oserror() == ENOENT) + return NULL; + snprintf(errmsg, sizeof(errmsg), "cannot open %s: %s", + get_pmiefile(), osstrerror()); + return errmsg; + } + + linenum = 1; + filename = get_pmiefile(); + if (read_lheader(f, &tmp) != NULL) { + fclose(f); + return errmsg; + } + + /* check that we have access to all components of the path */ + if ((home = strdup(tmp)) == NULL) { + snprintf(errmsg, sizeof(errmsg), "insufficient memory for pmie file parsing"); + return errmsg; + } +#ifdef IS_MINGW + rule_path_sep = ";"; +#else + rule_path_sep = ":"; +#endif + p = strtok(home, rule_path_sep); + while (p != NULL) { + if (access(p, F_OK) < 0) { + free(home); + snprintf(errmsg, sizeof(errmsg), "cannot access rules path component: \"%s\"", p); + return errmsg; + } + p = strtok(NULL, rule_path_sep); + } + free(home); + + if (strcmp(get_rules(), tmp) != 0) + snprintf(warning, warnlen, "warning - pmie configuration file \"%s\"\n" + " may not have been built using rules path:\n\t\"%s\"\n" + " (originally built using \"%s\")", filename, get_rules(), tmp); + + tmp = NULL; + if (read_locals(f) != NULL || read_restore(f) != NULL) + tmp = errmsg; + fclose(f); + return tmp; +} + + +/* #### setup global data structures; return NULL/failure message #### */ +char * +initialise(char *in_rules, char *in_pmie, char *warning, size_t warnlen) +{ + char *p; + char *home; + rule_t global; + char *rule_path_sep; + + /* setup pointers to the configuration files */ +#ifdef IS_MINGW + if ((home = getenv("USERPROFILE")) == NULL) { + snprintf(errmsg, sizeof(errmsg), "USERPROFILE undefined in environment"); + return errmsg; + } + if (in_pmie == NULL) + snprintf(pmiefile, sizeof(pmiefile), "%s\\%s", home, DEFAULT_USER_PMIE); + else + strcpy(pmiefile, in_pmie); + rule_path_sep = ";"; +#else + if (getuid() == 0) { + if (in_pmie == NULL) + snprintf(pmiefile, sizeof(pmiefile), "%s%c%s", pmGetConfig("PCP_SYSCONF_DIR"), SEP, DEFAULT_ROOT_PMIE); + else if (realpath(in_pmie, pmiefile) == NULL && oserror() != ENOENT) { + snprintf(errmsg, sizeof(errmsg), "failed to resolve realpath for %s: %s", + in_pmie, osstrerror()); + return errmsg; + } + else if (oserror() != ENOENT) + gotpath = 1; + } + else { + if ((home = getenv("HOME")) == NULL) { + snprintf(errmsg, sizeof(errmsg), "$HOME undefined in environment"); + return errmsg; + } + if (in_pmie == NULL) + snprintf(pmiefile, sizeof(pmiefile), "%s%c%s", home, SEP, DEFAULT_USER_PMIE); + else + strcpy(pmiefile, in_pmie); + } + rule_path_sep = ":"; +#endif + + if (in_rules == NULL) { + if ((p = getenv("PMIECONF_PATH")) == NULL) + snprintf(rulepath, sizeof(rulepath), "%s%c%s", pmGetConfig("PCP_VAR_DIR"), SEP, DEFAULT_RULES); + else + strcpy(rulepath, p); + } + else + snprintf(rulepath, sizeof(rulepath), "%s", in_rules); + + memset(&global, 0, sizeof(rule_t)); + global.self.name = global_name; + global.self.data = global_data; + global.self.help = global_help; + global.self.global = 1; + if (alloc_rule(global) == NULL) { /* 1st rule holds global (fake rule) */ + snprintf(errmsg, sizeof(errmsg), "insufficient memory for global parameters"); + return errmsg; + } + + if ((home = strdup(rulepath)) == NULL) { + snprintf(errmsg, sizeof(errmsg), "insufficient memory for rules path parsing"); + return errmsg; + } + p = strtok(home, rule_path_sep); + while (p != NULL) { + if (read_rule_subdir(p) != NULL) { + free(home); + return errmsg; + } + p = strtok(NULL, rule_path_sep); + } + free(home); + + if (read_pmiefile(warning, warnlen) != NULL) + return errmsg; + linenum = 0; /* finished all parsing */ + return NULL; +} + + +/* iterate through the pmie status directory and find running pmies */ +char * +lookup_processes(int *count, char ***processes) +{ + int fd; + int running = 0; + DIR *dirp; + void *ptr; + char proc[MAXPATHLEN+1]; + char **proc_list = NULL; + size_t size; + pmiestats_t *stats; + struct dirent *dp; + struct stat statbuf; + int sep = __pmPathSeparator(); + + snprintf(proc, sizeof(proc), "%s%c%s", + pmGetConfig("PCP_TMP_DIR"), sep, PMIE_SUBDIR); + if ((dirp = opendir(proc)) == NULL) { + snprintf(errmsg, sizeof(errmsg), "cannot opendir %s: %s", + proc, osstrerror()); + return NULL; + } + while ((dp = readdir(dirp)) != NULL) { + /* bunch of checks to find valid pmie data files... */ + if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0) + continue; + snprintf(proc, sizeof(proc), "%s%c%s", + PROC_DIR, sep, dp->d_name); /* check /proc */ + if (access(proc, F_OK) < 0) + continue; /* process has exited */ + snprintf(proc, sizeof(proc), "%s%c%s%c%s", + pmGetConfig("PCP_TMP_DIR"), sep, PMIE_SUBDIR, sep, dp->d_name); + if (stat(proc, &statbuf) < 0) + continue; + if (statbuf.st_size != sizeof(pmiestats_t)) + continue; + if ((fd = open(proc, O_RDONLY)) < 0) + continue; + ptr = __pmMemoryMap(fd, statbuf.st_size, 0); + close(fd); + if (ptr == NULL) + continue; + stats = (pmiestats_t *)ptr; + if (strcmp(stats->config, get_pmiefile()) != 0) + continue; + + size = (1 + running) * sizeof(char *); + if ((proc_list = (char **)realloc(proc_list, size)) == NULL + || (proc_list[running] = strdup(dp->d_name)) == NULL) { + snprintf(errmsg, sizeof(errmsg), "insufficient memory for process search"); + if (proc_list) free(proc_list); + closedir(dirp); + close(fd); + return errmsg; + } + running++; + } + closedir(dirp); + *count = running; + *processes = proc_list; + return NULL; +} |