#include <stdio.h>
#include <unistd.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <limits.h>
#include <thread.h>
#include <synch.h>
#include <errno.h>
#include <fcntl.h>
#include <ctype.h>
#include <pwd.h>
#include <syslog.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/sunddi.h>
#include <sys/sysevent.h>
#include <libsysevent.h>
#include <libnvpair.h>
#include <dirent.h>
#include <locale.h>
#include <signal.h>
#include <wait.h>
#include "syseventd.h"
#include "syseventconfd_door.h"
#include "sysevent_conf_mod.h"
#include "message_conf_mod.h"
static char *whoami = "sysevent_conf_mod";
static int ev_nretries;
static uint64_t ev_seq;
static hrtime_t ev_ts;
static int first_event;
static conftab_t *conftab = NULL;
static syseventtab_t *syseventtab = NULL;
static syseventtab_t *syseventtab_tail = NULL;
static sysevent_handle_t *confd_handle = NULL;
static cmdqueue_t *cmdq = NULL;
static cmdqueue_t *cmdq_tail = NULL;
static mutex_t cmdq_lock;
static cond_t cmdq_cv;
static int cmdq_cnt;
static thread_t cmdq_thr_id;
static cond_t cmdq_thr_cv;
static int want_fini;
static int confd_state = CONFD_STATE_NOT_RUNNING;
static int confd_retries;
static int transport_retries;
static int confd_err_msg_emitted;
static int sysevent_conf_dummy_event(sysevent_t *, int);
extern int debug_level;
extern char *root_dir;
extern void syseventd_print(int level, char *format, ...);
extern void syseventd_err_print(char *format, ...);
static struct slm_mod_ops sysevent_conf_mod_ops = {
SE_MAJOR_VERSION,
SE_MINOR_VERSION,
SE_MAX_RETRY_LIMIT,
&sysevent_conf_event
};
static struct slm_mod_ops sysevent_conf_dummy_mod_ops = {
SE_MAJOR_VERSION,
SE_MINOR_VERSION,
0,
&sysevent_conf_dummy_event
};
static char *
skip_spaces(char **cpp)
{
char *cp = *cpp;
while (*cp == ' ' || *cp == '\t')
cp++;
if (*cp == 0) {
*cpp = 0;
return (NULL);
}
return (cp);
}
static char *
next_field(char **cpp)
{
char *cp = *cpp;
char *start;
while (*cp == ' ' || *cp == '\t')
cp++;
if (*cp == 0) {
*cpp = 0;
return (NULL);
}
start = cp;
while (*cp && *cp != ' ' && *cp != '\t')
cp++;
if (*cp != 0)
*cp++ = 0;
*cpp = cp;
return (start);
}
static void *
sc_malloc(size_t n)
{
void *p;
p = malloc(n);
if (p == NULL) {
syslog(LOG_ERR, OUT_OF_MEMORY_ERR);
}
return (p);
}
static void *
sc_realloc(void *p, size_t current, size_t n)
{
p = realloc(p, n);
if (p == NULL) {
syslog(LOG_ERR, OUT_OF_MEMORY_ERR);
}
return (p);
}
static void
sc_free(void *p, size_t n)
{
free(p);
}
static char *
sc_strdup(char *cp)
{
char *new;
new = malloc((unsigned)(strlen(cp) + 1));
if (new == NULL) {
syslog(LOG_ERR, OUT_OF_MEMORY_ERR);
return (NULL);
}
(void) strcpy(new, cp);
return (new);
}
static void
sc_strfree(char *s)
{
if (s)
free(s);
}
static str_t *
initstr(int hint)
{
str_t *str;
if ((str = sc_malloc(sizeof (str_t))) == NULL)
return (NULL);
str->s_str = NULL;
str->s_len = 0;
str->s_alloc = 0;
str->s_hint = hint;
return (str);
}
static void
freestr(str_t *str)
{
if (str->s_str) {
sc_free(str->s_str, str->s_alloc);
}
sc_free(str, sizeof (str_t));
}
static void
resetstr(str_t *str)
{
str->s_len = 0;
}
static int
strcopys(str_t *str, char *s)
{
char *new_str;
int len = strlen(s) + 1;
if (str->s_alloc < len) {
new_str = (str->s_str == NULL) ?
sc_malloc(len+str->s_hint) :
sc_realloc(str->s_str, str->s_alloc, len+str->s_hint);
if (new_str == NULL) {
return (1);
}
str->s_str = new_str;
str->s_alloc = len + str->s_hint;
}
(void) strcpy(str->s_str, s);
str->s_len = len - 1;
return (0);
}
static int
strcats(str_t *str, char *s)
{
char *new_str;
int len = str->s_len + strlen(s) + 1;
if (str->s_alloc < len) {
new_str = (str->s_str == NULL) ? sc_malloc(len+str->s_hint) :
sc_realloc(str->s_str, str->s_alloc, len+str->s_hint);
if (new_str == NULL) {
return (1);
}
str->s_str = new_str;
str->s_alloc = len + str->s_hint;
}
(void) strcpy(str->s_str + str->s_len, s);
str->s_len = len - 1;
return (0);
}
static int
strcatc(str_t *str, int c)
{
char *new_str;
int len = str->s_len + 2;
if (str->s_alloc < len) {
new_str = (str->s_str == NULL) ? sc_malloc(len+str->s_hint) :
sc_realloc(str->s_str, str->s_alloc, len+str->s_hint);
if (new_str == NULL) {
return (1);
}
str->s_str = new_str;
str->s_alloc = len + str->s_hint;
}
*(str->s_str + str->s_len) = (char)c;
*(str->s_str + str->s_len + 1) = 0;
str->s_len++;
return (0);
}
static char *
fstrgets(str_t *line, FILE *fp)
{
int c;
resetstr(line);
while ((c = fgetc(fp)) != EOF) {
if (strcatc(line, c))
return (NULL);
if (c == '\n')
break;
}
if (line->s_len == 0)
return (NULL);
return (line->s_str);
}
static void
strtrunc(str_t *str, int pos)
{
if (str->s_len > pos) {
str->s_len = pos;
*(str->s_str + pos) = 0;
}
}
static void
parse_conf_file(char *conf_file)
{
char conf_path[PATH_MAX];
FILE *fp;
char *lp;
str_t *line;
int lineno = 0;
char *vendor, *publisher;
char *class, *subclass;
char *user;
char *reserved1, *reserved2;
char *path, *args;
syseventtab_t *sep;
struct passwd pwd;
struct passwd *pwdp;
char pwdbuf[1024];
int do_setuid;
pid_t saved_uid;
gid_t saved_gid;
int i, err;
(void) snprintf(conf_path, PATH_MAX, "%s/%s",
SYSEVENT_CONFIG_DIR, conf_file);
syseventd_print(DBG_CONF_FILE, "%s: reading %s\n", whoami, conf_path);
if ((fp = fopen(conf_path, "r")) == NULL) {
syslog(LOG_ERR, CANNOT_OPEN_ERR, conf_file, strerror(errno));
return;
}
if ((line = initstr(128)) == NULL)
return;
while ((lp = fstrgets(line, fp)) != NULL) {
lineno++;
if (*lp == '\n' || *lp == '#')
continue;
*(lp + strlen(lp)-1) = 0;
syseventd_print(DBG_CONF_FILE, "[%d]: %s\n",
lineno, lp);
if ((class = next_field(&lp)) == NULL)
goto mal_formed;
if ((subclass = next_field(&lp)) == NULL)
goto mal_formed;
if ((vendor = next_field(&lp)) == NULL)
goto mal_formed;
if ((publisher = next_field(&lp)) == NULL)
goto mal_formed;
if ((user = next_field(&lp)) == NULL)
goto mal_formed;
if ((reserved1 = next_field(&lp)) == NULL)
goto mal_formed;
if ((reserved2 = next_field(&lp)) == NULL)
goto mal_formed;
if ((path = next_field(&lp)) == NULL)
goto mal_formed;
args = skip_spaces(&lp);
do_setuid = 0;
if ((strcmp(user, "-") != 0) && (strcmp(user, "root") != 0)) {
i = getpwnam_r(user, &pwd, pwdbuf,
sizeof (pwdbuf), &pwdp);
if (i != 0 || pwdp == NULL) {
syslog(LOG_ERR, NO_USER_ERR,
conf_file, lineno, user);
continue;
}
do_setuid = 1;
}
if (strcmp(reserved1, "-") != 0) {
syslog(LOG_ERR, RESERVED_FIELD_ERR,
conf_file, lineno, reserved1);
continue;
}
if (strcmp(reserved2, "-") != 0) {
syslog(LOG_ERR, RESERVED_FIELD_ERR,
conf_file, lineno, reserved2);
continue;
}
err = 0;
if (do_setuid) {
saved_uid = getuid();
saved_gid = getgid();
if (setregid(pwdp->pw_gid, -1) == -1) {
syslog(LOG_ERR, SETREGID_ERR,
whoami, pwdp->pw_gid, strerror(errno));
err = -1;
}
if (setreuid(pwdp->pw_uid, -1) == -1) {
syslog(LOG_ERR, SETREUID_ERR,
whoami, pwdp->pw_uid, strerror(errno));
err = -1;
}
}
if ((i = access(path, X_OK)) == -1) {
syslog(LOG_ERR, CANNOT_EXECUTE_ERR,
conf_file, lineno, path, strerror(errno));
}
if (do_setuid) {
if (setreuid(saved_uid, -1) == -1) {
syslog(LOG_ERR, SETREUID_ERR,
whoami, saved_uid, strerror(errno));
err = -1;
}
if (setregid(saved_gid, -1) == -1) {
syslog(LOG_ERR, SETREGID_ERR,
whoami, saved_gid, strerror(errno));
err = -1;
}
}
if (i == -1 || err == -1)
continue;
if ((sep = sc_malloc(sizeof (syseventtab_t))) == NULL)
break;
sep->se_conf_file = conf_file;
sep->se_lineno = lineno;
sep->se_vendor = sc_strdup(vendor);
sep->se_publisher = sc_strdup(publisher);
sep->se_class = sc_strdup(class);
sep->se_subclass = sc_strdup(subclass);
sep->se_user = sc_strdup(user);
if (do_setuid) {
sep->se_uid = pwdp->pw_uid;
sep->se_gid = pwdp->pw_gid;
} else {
sep->se_uid = 0;
sep->se_gid = 0;
}
sep->se_reserved1 = sc_strdup(reserved1);
sep->se_reserved2 = sc_strdup(reserved2);
sep->se_path = sc_strdup(path);
sep->se_args = (args == NULL) ? NULL : sc_strdup(args);
sep->se_next = NULL;
if (sep->se_vendor == NULL || sep->se_publisher == NULL ||
sep->se_class == NULL || sep->se_subclass == NULL ||
sep->se_user == NULL || sep->se_reserved1 == NULL ||
sep->se_reserved2 == NULL || sep->se_path == NULL ||
(args && sep->se_args == NULL)) {
sc_strfree(sep->se_vendor);
sc_strfree(sep->se_publisher);
sc_strfree(sep->se_class);
sc_strfree(sep->se_subclass);
sc_strfree(sep->se_user);
sc_strfree(sep->se_reserved1);
sc_strfree(sep->se_reserved2);
sc_strfree(sep->se_path);
sc_strfree(sep->se_args);
sc_free(sep, sizeof (syseventtab_t));
break;
}
if (syseventtab == NULL) {
syseventtab = sep;
syseventtab_tail = sep;
} else {
syseventtab_tail->se_next = sep;
syseventtab_tail = sep;
}
if (debug_level >= DBG_DETAILED) {
syseventtab_t *sp;
for (sp = syseventtab; sp; sp = sp->se_next) {
syseventd_print(DBG_DETAILED,
" vendor=%s\n", sp->se_vendor);
syseventd_print(DBG_DETAILED,
" publisher=%s\n", sp->se_publisher);
syseventd_print(DBG_DETAILED,
" class=%s\n", sp->se_class);
syseventd_print(DBG_DETAILED,
" subclass=%s\n", sp->se_subclass);
syseventd_print(DBG_DETAILED,
" user=%s uid=%d gid=%d\n",
sp->se_user, sp->se_uid, sp->se_gid);
syseventd_print(DBG_DETAILED,
" reserved1=%s\n", sp->se_reserved1);
syseventd_print(DBG_DETAILED,
" reserved2=%s\n", sp->se_reserved2);
syseventd_print(DBG_DETAILED,
" path=%s\n", sp->se_path);
if (sp->se_args != NULL) {
syseventd_print(DBG_DETAILED,
" args=%s\n", sp->se_args);
}
}
}
continue;
mal_formed:
syslog(LOG_ERR, SYNTAX_ERR, conf_file, lineno);
}
freestr(line);
(void) fclose(fp);
}
static void
build_event_table()
{
conftab_t *cfp = NULL;
DIR *dir;
struct dirent *result;
conftab_t *new_cfp;
char *str;
if ((dir = opendir(SYSEVENT_CONFIG_DIR)) == NULL) {
syslog(LOG_ERR, CANNOT_OPEN_ERR,
SYSEVENT_CONFIG_DIR, strerror(errno));
return;
}
while ((result = readdir(dir)) != NULL) {
if (result->d_name[0] == '.')
continue;
if ((str = strrchr(result->d_name, ',')) != NULL) {
str++;
} else {
str = result->d_name;
}
if (strcmp(str, "sysevent.conf") != 0) {
syseventd_print(DBG_CONF_FILE,
"%s: ignoring %s\n", whoami, str);
continue;
}
if ((str = sc_strdup(result->d_name)) == NULL)
goto err;
if ((new_cfp = sc_malloc(sizeof (conftab_t))) == NULL) {
sc_strfree(str);
goto err;
}
if (conftab == NULL) {
conftab = new_cfp;
} else {
for (cfp = conftab; cfp->cf_next; cfp = cfp->cf_next)
;
cfp->cf_next = new_cfp;
}
cfp = new_cfp;
cfp->cf_conf_file = str;
cfp->cf_next = NULL;
parse_conf_file(cfp->cf_conf_file);
}
err:
if (closedir(dir) == -1) {
if (errno == EAGAIN)
goto err;
syslog(LOG_ERR, CLOSEDIR_ERR,
SYSEVENT_CONFIG_DIR, strerror(errno));
}
}
static int
enter_lock(char *lock_file)
{
struct flock lock;
int lock_fd;
(void) strlcpy(lock_file, LOCK_FILENAME, PATH_MAX);
lock_fd = open(lock_file, O_CREAT|O_RDWR, 0644);
if (lock_fd < 0) {
syslog(LOG_ERR, MSG_LOCK_CREATE_ERR,
whoami, lock_file, strerror(errno));
return (-1);
}
lock.l_type = F_WRLCK;
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 0;
retry:
if (fcntl(lock_fd, F_SETLKW, &lock) == -1) {
if (errno == EAGAIN || errno == EINTR)
goto retry;
(void) close(lock_fd);
syslog(LOG_ERR, MSG_LOCK_SET_ERR,
whoami, lock_file, strerror(errno));
return (-1);
}
return (lock_fd);
}
static void
exit_lock(int lock_fd, char *lock_file)
{
struct flock lock;
lock.l_type = F_UNLCK;
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 0;
if (fcntl(lock_fd, F_SETLK, &lock) == -1) {
syslog(LOG_ERR, MSG_LOCK_CLR_ERR,
whoami, lock_file, strerror(errno));
}
if (close(lock_fd) == -1) {
syslog(LOG_ERR, MSG_LOCK_CLOSE_ERR,
whoami, lock_file, strerror(errno));
}
}
static void
free_event_table()
{
syseventtab_t *sep;
syseventtab_t *sep_next;
conftab_t *cfp;
conftab_t *cfp_next;
sep = syseventtab;
while (sep) {
sc_strfree(sep->se_vendor);
sc_strfree(sep->se_publisher);
sc_strfree(sep->se_class);
sc_strfree(sep->se_subclass);
sc_strfree(sep->se_user);
sc_strfree(sep->se_reserved1);
sc_strfree(sep->se_reserved2);
sc_strfree(sep->se_path);
if (sep->se_args)
sc_strfree(sep->se_args);
sep_next = sep->se_next;
sc_free(sep, sizeof (syseventtab_t));
sep = sep_next;
}
syseventtab = NULL;
cfp = conftab;
while (cfp) {
sc_strfree(cfp->cf_conf_file);
cfp_next = cfp->cf_next;
sc_free(cfp, sizeof (conftab_t));
cfp = cfp_next;
}
conftab = NULL;
}
static char ident_chars[] = "_";
static str_t *
snip_identifier(char *id, char **end)
{
str_t *token;
if ((token = initstr(32)) == NULL)
return (NULL);
while (*id != 0) {
if (isascii(*id) &&
(isalnum(*id) || strchr(ident_chars, *id) != NULL)) {
if (strcatc(token, *id++)) {
freestr(token);
return (NULL);
}
} else {
*end = id;
return (token);
}
}
*end = id;
return (token);
}
static str_t *
snip_delimited_identifier(char *id, char **end)
{
str_t *token;
if ((token = initstr(32)) == NULL)
return (NULL);
while (*id != 0) {
if (*id == '}') {
*end = id+1;
return (token);
}
if (strcatc(token, *id++)) {
freestr(token);
return (NULL);
}
}
if (*id == 0) {
freestr(token);
return (NULL);
}
*end = id;
return (token);
}
static char *nv_attr_type_strings[] = {
"unknown",
"boolean",
"byte",
"int16",
"uint16",
"int32",
"uint32",
"int64",
"uint64",
"string",
"byte-array",
"int16-array",
"uint16-array",
"int32-array",
"uint32-array",
"int64-array",
"uint64-array",
"string-array",
"hrtime"
};
static char *
se_attr_type_to_str(int se_attr_type)
{
if (se_attr_type >= 0 &&
se_attr_type < sizeof (nv_attr_type_strings) / sizeof (char *)) {
return (nv_attr_type_strings[se_attr_type]);
}
return (nv_attr_type_strings[DATA_TYPE_UNKNOWN]);
}
static str_t *
find_macro_definition(sysevent_t *ev, nvlist_t *nvlist, syseventtab_t *sep,
char *token, sysevent_hdr_info_t *hdr)
{
nvpair_t *nvp;
int nmatches;
char num[64];
str_t *replacement;
int i;
uint_t nelems;
union {
uchar_t x_byte;
int16_t x_int16;
uint16_t x_uint16;
int32_t x_int32;
uint32_t x_uint32;
int64_t x_int64;
uint64_t x_uint64;
hrtime_t x_time;
char *x_string;
uchar_t *x_byte_array;
int16_t *x_int16_array;
int32_t *x_int32_array;
int64_t *x_int64_array;
uint16_t *x_uint16_array;
uint32_t *x_uint32_array;
uint64_t *x_uint64_array;
char **x_string_array;
} x;
if ((replacement = initstr(128)) == NULL) {
return (NULL);
}
if (strcmp(token, "vendor") == 0) {
if (strcopys(replacement, hdr->vendor)) {
freestr(replacement);
return (NULL);
}
return (replacement);
}
if (strcmp(token, "publisher") == 0) {
if (strcopys(replacement, hdr->publisher)) {
freestr(replacement);
return (NULL);
}
return (replacement);
}
if (strcmp(token, "class") == 0) {
if (strcopys(replacement, hdr->class)) {
freestr(replacement);
return (NULL);
}
return (replacement);
}
if (strcmp(token, "subclass") == 0) {
if (strcopys(replacement, hdr->subclass)) {
freestr(replacement);
return (NULL);
}
return (replacement);
}
if ((strcmp(token, "sequence") == 0) ||
(strcmp(token, "timestamp") == 0)) {
if (strcmp(token, "sequence") == 0) {
(void) snprintf(num, sizeof (num),
"0x%llx", sysevent_get_seq(ev));
} else {
hrtime_t ts;
sysevent_get_time(ev, &ts);
(void) snprintf(num, sizeof (num), "0x%llx", ts);
}
if (strcopys(replacement, num)) {
freestr(replacement);
return (NULL);
}
return (replacement);
}
nmatches = 0;
if (nvlist) {
nvpair_t *nvp_match;
nvp = NULL;
while ((nvp = nvlist_next_nvpair(nvlist, nvp)) != NULL) {
if (debug_level >= DBG_DETAILED) {
syseventd_print(DBG_DETAILED,
" attribute: %s %s\n", nvpair_name(nvp),
se_attr_type_to_str(nvpair_type(nvp)));
}
if (strcmp(token, nvpair_name(nvp)) == 0) {
nmatches++;
nvp_match = nvp;
}
}
nvp = nvp_match;
}
if (nmatches == 0) {
syslog(LOG_ERR, MACRO_UNDEF_ERR,
sep->se_conf_file, sep->se_lineno, token);
freestr(replacement);
return (NULL);
} else if (nmatches > 1) {
syslog(LOG_ERR, MACRO_MULT_DEF_ERR,
sep->se_conf_file, sep->se_lineno, token);
freestr(replacement);
return (NULL);
}
switch (nvpair_type(nvp)) {
case DATA_TYPE_BYTE:
(void) nvpair_value_byte(nvp, &x.x_byte);
(void) snprintf(num, sizeof (num), "0x%x", x.x_byte);
if (strcats(replacement, num)) {
freestr(replacement);
return (NULL);
}
break;
case DATA_TYPE_INT16:
(void) nvpair_value_int16(nvp, &x.x_int16);
(void) snprintf(num, sizeof (num), "%d", x.x_int16);
if (strcats(replacement, num)) {
freestr(replacement);
return (NULL);
}
break;
case DATA_TYPE_UINT16:
(void) nvpair_value_uint16(nvp, &x.x_uint16);
(void) snprintf(num, sizeof (num), "0x%x", x.x_uint16);
if (strcats(replacement, num)) {
freestr(replacement);
return (NULL);
}
break;
case DATA_TYPE_INT32:
(void) nvpair_value_int32(nvp, &x.x_int32);
(void) snprintf(num, sizeof (num), "%d", x.x_int32);
if (strcats(replacement, num)) {
freestr(replacement);
return (NULL);
}
break;
case DATA_TYPE_UINT32:
(void) nvpair_value_uint32(nvp, &x.x_uint32);
(void) snprintf(num, sizeof (num), "0x%x", x.x_uint32);
if (strcats(replacement, num)) {
freestr(replacement);
return (NULL);
}
break;
case DATA_TYPE_INT64:
(void) nvpair_value_int64(nvp, &x.x_int64);
(void) snprintf(num, sizeof (num), "%lld", x.x_int64);
if (strcats(replacement, num)) {
freestr(replacement);
return (NULL);
}
break;
case DATA_TYPE_UINT64:
(void) nvpair_value_uint64(nvp, &x.x_uint64);
(void) snprintf(num, sizeof (num), "0x%llx", x.x_uint64);
if (strcats(replacement, num)) {
freestr(replacement);
return (NULL);
}
break;
case DATA_TYPE_STRING:
(void) nvpair_value_string(nvp, &x.x_string);
if (strcats(replacement, x.x_string)) {
freestr(replacement);
return (NULL);
}
break;
case DATA_TYPE_BYTE_ARRAY: {
uchar_t *p;
(void) nvpair_value_byte_array(nvp,
&x.x_byte_array, &nelems);
p = x.x_byte_array;
for (i = 0; i < nelems; i++) {
(void) snprintf(num, sizeof (num),
"0x%x ", *p++ & 0xff);
if (strcats(replacement, num)) {
freestr(replacement);
return (NULL);
}
}
}
break;
case DATA_TYPE_INT16_ARRAY: {
int16_t *p;
(void) nvpair_value_int16_array(nvp,
&x.x_int16_array, &nelems);
p = x.x_int16_array;
for (i = 0; i < nelems; i++) {
(void) snprintf(num, sizeof (num), "%d ", *p++);
if (strcats(replacement, num)) {
freestr(replacement);
return (NULL);
}
}
}
break;
case DATA_TYPE_UINT16_ARRAY: {
uint16_t *p;
(void) nvpair_value_uint16_array(nvp,
&x.x_uint16_array, &nelems);
p = x.x_uint16_array;
for (i = 0; i < nelems; i++) {
(void) snprintf(num, sizeof (num),
"0x%x ", *p++);
if (strcats(replacement, num)) {
freestr(replacement);
return (NULL);
}
}
}
break;
case DATA_TYPE_INT32_ARRAY: {
int32_t *p;
(void) nvpair_value_int32_array(nvp,
&x.x_int32_array, &nelems);
p = x.x_int32_array;
for (i = 0; i < nelems; i++) {
(void) snprintf(num, sizeof (num), "%d ", *p++);
if (strcats(replacement, num)) {
freestr(replacement);
return (NULL);
}
}
}
break;
case DATA_TYPE_UINT32_ARRAY: {
uint32_t *p;
(void) nvpair_value_uint32_array(nvp,
&x.x_uint32_array, &nelems);
p = x.x_uint32_array;
for (i = 0; i < nelems; i++) {
(void) snprintf(num, sizeof (num),
"0x%x ", *p++);
if (strcats(replacement, num)) {
freestr(replacement);
return (NULL);
}
}
}
break;
case DATA_TYPE_INT64_ARRAY: {
int64_t *p;
(void) nvpair_value_int64_array(nvp,
&x.x_int64_array, &nelems);
p = x.x_int64_array;
for (i = 0; i < nelems; i++) {
(void) snprintf(num, sizeof (num),
"%lld ", *p++);
if (strcats(replacement, num)) {
freestr(replacement);
return (NULL);
}
}
}
break;
case DATA_TYPE_UINT64_ARRAY: {
uint64_t *p;
(void) nvpair_value_uint64_array(nvp,
&x.x_uint64_array, &nelems);
p = x.x_uint64_array;
for (i = 0; i < nelems; i++) {
(void) snprintf(num, sizeof (num),
"0x%llx ", *p++);
if (strcats(replacement, num)) {
freestr(replacement);
return (NULL);
}
}
}
break;
case DATA_TYPE_STRING_ARRAY: {
char **p;
(void) nvpair_value_string_array(nvp,
&x.x_string_array, &nelems);
p = x.x_string_array;
for (i = 0; i < nelems; i++) {
if (strcats(replacement, *p++) ||
strcats(replacement, " ")) {
freestr(replacement);
return (NULL);
}
}
}
break;
case DATA_TYPE_HRTIME:
(void) nvpair_value_hrtime(nvp, &x.x_time);
(void) snprintf(num, sizeof (num), "0x%llx", x.x_time);
if (strcats(replacement, num)) {
freestr(replacement);
return (NULL);
}
break;
default:
syslog(LOG_ERR, ATTR_UNSUPPORTED_ERR,
sep->se_conf_file, sep->se_lineno,
nvpair_type(nvp), token);
freestr(replacement);
return (NULL);
}
return (replacement);
}
static int
expand_macros(sysevent_t *ev, nvlist_t *nvlist, syseventtab_t *sep,
str_t *line, sysevent_hdr_info_t *hdr)
{
char *p;
int state;
char *end;
str_t *token;
str_t *remainder;
str_t *replacement;
int count;
int dollar_position;
syseventd_print(DBG_MACRO, " expanding macros: '%s'\n", line->s_str);
reset:
state = 0;
count = 0;
for (p = line->s_str; *p != 0; p++, count++) {
switch (state) {
case 0:
if (*p == '\\') {
state = 1;
} else if (*p == '$') {
dollar_position = count;
state = 2;
}
break;
case 1:
state = 0;
break;
case 2:
if (*p == '{') {
token = snip_delimited_identifier(p+1, &end);
} else {
token = snip_identifier(p, &end);
}
if (token == NULL)
goto failed;
if ((remainder = initstr(128)) == NULL) {
freestr(token);
return (1);
}
if (strcopys(remainder, end)) {
freestr(token);
freestr(remainder);
return (1);
}
replacement = find_macro_definition(ev, nvlist,
sep, token->s_str, hdr);
if (replacement == NULL) {
freestr(token);
freestr(remainder);
return (1);
}
syseventd_print(DBG_MACRO,
" '%s' expands to '%s'\n",
token->s_str, replacement->s_str);
strtrunc(line, dollar_position);
if (strcats(line, replacement->s_str)) {
freestr(token);
freestr(replacement);
freestr(remainder);
return (1);
}
if (strcats(line, remainder->s_str)) {
freestr(token);
freestr(replacement);
freestr(remainder);
return (1);
}
syseventd_print(DBG_MACRO,
" with macro expanded: '%s'\n", line->s_str);
freestr(token);
freestr(replacement);
freestr(remainder);
goto reset;
}
}
failed:
if (state != 0) {
syslog(LOG_ERR, SYNTAX_ERR, sep->se_conf_file, sep->se_lineno);
return (1);
}
return (0);
}
static void
start_syseventconfd()
{
int err;
err = system1("/usr/lib/sysevent/syseventconfd",
"/usr/lib/sysevent/syseventconfd");
if (err != 0 && confd_err_msg_emitted == 0) {
if (confd_state == CONFD_STATE_NOT_RUNNING) {
syslog(LOG_ERR, SYSEVENTCONFD_START_ERR,
strerror(errno));
} else {
syslog(LOG_ERR, SYSEVENTCONFD_RESTART_ERR,
strerror(errno));
}
}
}
static int
system1(const char *s_path, const char *s)
{
struct sigaction cbuf, ibuf, qbuf, ignore, dfl;
sigset_t mask;
sigset_t savemask;
struct stat st;
pid_t pid;
int status, w;
if (s == NULL) {
errno = EINVAL;
return (-1);
}
if (stat(s_path, &st) < 0) {
return (-1);
}
if (((geteuid() == st.st_uid) && ((st.st_mode & S_IXUSR) == 0)) ||
((getegid() == st.st_gid) && ((st.st_mode & S_IXGRP) == 0)) ||
((st.st_mode & S_IXOTH) == 0)) {
errno = EPERM;
return (-1);
}
(void) sigemptyset(&mask);
(void) sigaddset(&mask, SIGCHLD);
(void) sigprocmask(SIG_BLOCK, &mask, &savemask);
(void) memset(&dfl, 0, sizeof (dfl));
dfl.sa_handler = SIG_DFL;
(void) sigaction(SIGCHLD, &dfl, &cbuf);
switch (pid = fork1()) {
case -1:
(void) sigaction(SIGCHLD, &cbuf, NULL);
(void) sigprocmask(SIG_SETMASK, &savemask, NULL);
return (-1);
case 0:
(void) sigemptyset(&mask);
(void) sigprocmask(SIG_SETMASK, &mask, NULL);
closefrom(3);
(void) execl(s_path, s, (char *)0);
_exit(-1);
break;
default:
break;
}
(void) memset(&ignore, 0, sizeof (ignore));
ignore.sa_handler = SIG_IGN;
(void) sigaction(SIGINT, &ignore, &ibuf);
(void) sigaction(SIGQUIT, &ignore, &qbuf);
do {
w = waitpid(pid, &status, 0);
} while (w == -1 && errno == EINTR);
(void) sigaction(SIGINT, &ibuf, NULL);
(void) sigaction(SIGQUIT, &qbuf, NULL);
(void) sigaction(SIGCHLD, &cbuf, NULL);
(void) sigprocmask(SIG_SETMASK, &savemask, NULL);
return ((w == -1)? w: status);
}
static void
abort_cmd_queue()
{
cmdqueue_t *cmd;
cmdqueue_t *next;
int nevents = 0;
while ((cmd = cmdq) != NULL) {
next = cmd->next;
cmdq_cnt--;
sysevent_free(cmd->event);
sc_free(cmd, sizeof (cmdqueue_t));
cmdq = next;
nevents++;
}
cmdq_tail = NULL;
if (nevents > 0) {
syslog(LOG_ERR, N_EVENTS_DISCARDED_ERR, nevents);
}
if (want_fini == 0) {
confd_state = CONFD_STATE_DISABLED;
syslog(LOG_ERR, SERVICE_DISABLED_MSG);
}
}
static int
queue_event(sysevent_t *ev, syseventtab_t *sep, sysevent_hdr_info_t *hdr)
{
str_t *line;
nvlist_t *nvlist;
char *argv0;
sysevent_t *cmd_event;
nvlist_t *cmd_nvlist;
cmdqueue_t *new_cmd;
if ((line = initstr(128)) == NULL)
return (1);
if ((argv0 = strrchr(sep->se_path, '/')) == NULL) {
argv0 = sep->se_path;
} else {
argv0++;
}
if (strcopys(line, argv0)) {
freestr(line);
return (1);
}
if (sep->se_args) {
if (strcats(line, " ")) {
freestr(line);
return (1);
}
if (strcats(line, sep->se_args)) {
freestr(line);
return (1);
}
if (sysevent_get_attr_list(ev, &nvlist) != 0) {
syslog(LOG_ERR, GET_ATTR_LIST_ERR,
sep->se_conf_file, sep->se_lineno,
strerror(errno));
freestr(line);
return (1);
}
if (expand_macros(ev, nvlist, sep, line, hdr)) {
freestr(line);
nvlist_free(nvlist);
return (1);
}
nvlist_free(nvlist);
}
if (debug_level >= DBG_EXEC) {
syseventd_print(DBG_EXEC, "%s, line %d: path = %s\n",
sep->se_conf_file, sep->se_lineno, sep->se_path);
syseventd_print(DBG_EXEC, " cmd = %s\n", line->s_str);
}
cmd_nvlist = NULL;
if ((errno = nvlist_alloc(&cmd_nvlist, NV_UNIQUE_NAME, 0)) != 0) {
freestr(line);
syslog(LOG_ERR, NVLIST_ALLOC_ERR,
sep->se_conf_file, sep->se_lineno,
strerror(errno));
return (1);
}
if ((errno = nvlist_add_string(cmd_nvlist, "path", sep->se_path)) != 0)
goto err;
if ((errno = nvlist_add_string(cmd_nvlist, "cmd", line->s_str)) != 0)
goto err;
if ((errno = nvlist_add_string(cmd_nvlist, "file",
sep->se_conf_file)) != 0)
goto err;
if ((errno = nvlist_add_int32(cmd_nvlist, "line", sep->se_lineno)) != 0)
goto err;
if ((errno = nvlist_add_string(cmd_nvlist, "user", sep->se_user)) != 0)
goto err;
if (sep->se_uid != (uid_t)0) {
if ((errno = nvlist_add_int32(cmd_nvlist, "uid",
sep->se_uid)) != 0)
goto err;
if ((errno = nvlist_add_int32(cmd_nvlist, "gid",
sep->se_gid)) != 0)
goto err;
}
cmd_event = sysevent_alloc_event(hdr->class, hdr->subclass, hdr->vendor,
hdr->publisher, cmd_nvlist);
if (cmd_event == NULL) {
syslog(LOG_ERR, SYSEVENT_ALLOC_ERR,
sep->se_conf_file, sep->se_lineno,
strerror(errno));
nvlist_free(cmd_nvlist);
freestr(line);
return (1);
}
nvlist_free(cmd_nvlist);
freestr(line);
if ((new_cmd = sc_malloc(sizeof (cmdqueue_t))) == NULL) {
sysevent_free(cmd_event);
return (1);
}
new_cmd->event = cmd_event;
new_cmd->next = NULL;
(void) mutex_lock(&cmdq_lock);
if (cmdq == NULL) {
cmdq = new_cmd;
} else {
cmdq_tail->next = new_cmd;
}
cmdq_cnt++;
cmdq_tail = new_cmd;
(void) cond_signal(&cmdq_cv);
(void) mutex_unlock(&cmdq_lock);
return (0);
err:
syslog(LOG_ERR, NVLIST_BUILD_ERR,
sep->se_conf_file, sep->se_lineno, strerror(errno));
nvlist_free(cmd_nvlist);
freestr(line);
return (1);
}
static int
transport_event(sysevent_t *event)
{
int rval;
rval = sysevent_send_event(confd_handle, event);
if (rval != 0) {
switch (errno) {
case EAGAIN:
case EINTR:
rval = errno;
break;
case ENOENT:
case EBADF:
if (confd_state == CONFD_STATE_NOT_RUNNING ||
confd_state == CONFD_STATE_OK) {
confd_state = CONFD_STATE_STARTED;
start_syseventconfd();
confd_retries = 0;
rval = EAGAIN;
} else if (confd_state == CONFD_STATE_STARTED &&
confd_retries < 16) {
if (++confd_retries == 16) {
confd_state = CONFD_STATE_ERR;
if (confd_err_msg_emitted == 0) {
syslog(LOG_ERR,
SYSEVENTCONFD_ERR);
confd_err_msg_emitted = 1;
}
}
rval = EAGAIN;
} else {
rval = errno;
}
break;
default:
syslog(LOG_ERR, SYSEVENTCONFD_TRAN_ERR,
strerror(errno));
rval = errno;
break;
}
} else if (confd_state != CONFD_STATE_OK) {
if (confd_state == CONFD_STATE_ERR) {
syslog(LOG_ERR, SYSEVENTCONFD_OK);
confd_err_msg_emitted = 0;
}
confd_state = CONFD_STATE_OK;
confd_retries = 0;
confd_err_msg_emitted = 0;
}
return (rval);
}
static void
transport_queued_events()
{
int rval;
cmdqueue_t *cmd;
(void) mutex_lock(&cmdq_lock);
while (cmdq != NULL) {
cmd = cmdq;
(void) mutex_unlock(&cmdq_lock);
rval = transport_event(cmd->event);
(void) mutex_lock(&cmdq_lock);
if (rval != 0) {
switch (rval) {
case EAGAIN:
case EINTR:
if (want_fini) {
if (++transport_retries == 16) {
abort_cmd_queue();
}
}
(void) mutex_unlock(&cmdq_lock);
return;
case EBADF:
if (want_fini || ++transport_retries == 16) {
abort_cmd_queue();
}
(void) mutex_unlock(&cmdq_lock);
return;
default:
if (++transport_retries == 16) {
abort_cmd_queue();
(void) mutex_unlock(&cmdq_lock);
return;
}
break;
}
} else {
transport_retries = 0;
}
cmdq_cnt--;
cmdq = cmdq->next;
if (cmdq == NULL) {
cmdq_tail = NULL;
}
(void) mutex_unlock(&cmdq_lock);
sysevent_free(cmd->event);
sc_free(cmd, sizeof (cmdqueue_t));
(void) mutex_lock(&cmdq_lock);
}
(void) mutex_unlock(&cmdq_lock);
}
static void *
queue_flush_thr(void *arg __unused)
{
int n;
(void) mutex_lock(&cmdq_lock);
for (;;) {
while (cmdq_cnt == 0 && want_fini == 0) {
(void) cond_wait(&cmdq_cv, &cmdq_lock);
}
if (cmdq_cnt == 0 && want_fini) {
(void) cond_signal(&cmdq_thr_cv);
(void) mutex_unlock(&cmdq_lock);
thr_exit(NULL);
}
(void) mutex_unlock(&cmdq_lock);
transport_queued_events();
(void) mutex_lock(&cmdq_lock);
if (cmdq_cnt != 0) {
(void) mutex_unlock(&cmdq_lock);
if (want_fini == 0 && confd_err_msg_emitted) {
for (n = 0; n < 60; n++) {
(void) sleep(1);
if (want_fini)
break;
}
} else {
(void) sleep(1);
}
(void) mutex_lock(&cmdq_lock);
}
}
}
static int
sysevent_conf_event(sysevent_t *ev, int flag)
{
int ret = 0;
char *vendor;
char *publisher;
char *class;
char *subclass;
syseventtab_t *sep;
sysevent_hdr_info_t hdr;
uint64_t seq;
hrtime_t ts;
if (confd_state == CONFD_STATE_DISABLED) {
return (0);
}
if (debug_level >= DBG_EVENTS) {
if (sysevent_get_seq(ev) == ev_seq && ev_nretries > 0) {
syseventd_print(DBG_EVENTS,
"sequence: %lld/%lld, retry %d\n",
sysevent_get_seq(ev), ev_seq, ev_nretries);
} else if (sysevent_get_seq(ev) > ev_seq) {
syseventd_print(DBG_EVENTS,
"sequence: %lld/%lld\n",
sysevent_get_seq(ev), ev_seq);
}
}
seq = sysevent_get_seq(ev);
sysevent_get_time(ev, &ts);
if (seq > ev_seq || ts > ev_ts) {
ev_nretries = 0;
} else if (first_event == 0 &&
(((seq < ev_seq) || (seq == 0 && ts > ev_ts)) ||
(seq == ev_seq && ev_nretries == 0))) {
syseventd_print(DBG_TEST,
"out-of-order sequence: received %lld/0x%llx, "
"expected %lld/0x%llx\n", seq, ts, ev_seq+1, ev_ts);
return (ret);
}
ev_ts = ts;
ev_seq = seq;
first_event = 0;
vendor = sysevent_get_vendor_name(ev);
publisher = sysevent_get_pub_name(ev);
class = sysevent_get_class_name(ev);
subclass = sysevent_get_subclass_name(ev);
if (vendor == NULL || publisher == NULL) {
syseventd_print(DBG_EVENTS, "Short on memory with vendor "
"and/or publisher string generation\n");
ev_nretries++;
free(publisher);
free(vendor);
return (EAGAIN);
}
syseventd_print(DBG_EVENTS,
"%s event %lld: vendor='%s' publisher='%s' class='%s' "
"subclass='%s'\n", whoami, sysevent_get_seq(ev), vendor,
publisher, class, subclass);
for (sep = syseventtab; sep; sep = sep->se_next) {
if (strcmp(sep->se_vendor, "-") != 0) {
if (strcmp(sep->se_vendor, vendor) != 0)
continue;
}
if (strcmp(sep->se_publisher, "-") != 0) {
if (strcmp(sep->se_publisher, publisher) != 0)
continue;
}
if (strcmp(sep->se_class, "-") != 0) {
if (strcmp(sep->se_class, class) != 0)
continue;
}
if (strcmp(sep->se_subclass, "-") != 0) {
if (strcmp(sep->se_subclass, subclass) != 0)
continue;
}
syseventd_print(DBG_MATCHES, " event match: %s, line %d\n",
sep->se_conf_file, sep->se_lineno);
hdr.class = class;
hdr.subclass = subclass;
hdr.vendor = vendor;
hdr.publisher = publisher;
if ((ret = queue_event(ev, sep, &hdr)) != 0)
break;
}
if (ret == 0) {
ev_nretries = 0;
} else {
if (ev_nretries == SE_MAX_RETRY_LIMIT) {
syslog(LOG_ERR, SYSEVENT_SEND_ERR,
sep->se_conf_file, sep->se_lineno, errno);
} else {
syseventd_print(DBG_TEST, "%s event %lld: "
"'%s' '%s' '%s' '%s - errno %d, retry %d\n",
whoami, sysevent_get_seq(ev), vendor,
publisher, class, subclass, errno, ev_nretries);
}
ret = EAGAIN;
ev_nretries++;
}
free(publisher);
free(vendor);
return (ret);
}
struct slm_mod_ops *
slm_init()
{
char lock_file[PATH_MAX+1];
int lock_fd;
int err;
if (strcmp(root_dir, "") != 0) {
return (&sysevent_conf_dummy_mod_ops);
}
ev_nretries = 0;
first_event = 1;
confd_handle = sysevent_open_channel_alt(SYSEVENTCONFD_SERVICE_DOOR);
if (confd_handle == NULL) {
syslog(LOG_ERR, CHANNEL_OPEN_ERR);
return (NULL);
}
if (sysevent_bind_publisher(confd_handle) != 0) {
if (errno == EBUSY) {
sysevent_cleanup_publishers(confd_handle);
if (sysevent_bind_publisher(confd_handle) != 0) {
sysevent_close_channel(confd_handle);
return (NULL);
}
}
}
sysevent_cleanup_subscribers(confd_handle);
cmdq = NULL;
cmdq_tail = NULL;
cmdq_cnt = 0;
want_fini = 0;
confd_err_msg_emitted = 0;
if (confd_state != CONFD_STATE_OK) {
confd_state = CONFD_STATE_NOT_RUNNING;
}
confd_retries = 0;
transport_retries = 0;
(void) mutex_init(&cmdq_lock, USYNC_THREAD, NULL);
(void) cond_init(&cmdq_cv, USYNC_THREAD, NULL);
(void) cond_init(&cmdq_thr_cv, USYNC_THREAD, NULL);
if ((err = thr_create(NULL, 0, queue_flush_thr,
NULL, 0, &cmdq_thr_id)) != 0) {
syslog(LOG_ERR, THR_CREATE_ERR, strerror(err));
sysevent_close_channel(confd_handle);
confd_handle = NULL;
(void) mutex_destroy(&cmdq_lock);
(void) cond_destroy(&cmdq_cv);
(void) cond_destroy(&cmdq_thr_cv);
return (NULL);
}
if ((lock_fd = enter_lock(lock_file)) == -1) {
(void) thr_join(cmdq_thr_id, NULL, NULL);
sysevent_close_channel(confd_handle);
confd_handle = NULL;
(void) mutex_destroy(&cmdq_lock);
(void) cond_destroy(&cmdq_cv);
(void) cond_destroy(&cmdq_thr_cv);
return (NULL);
}
build_event_table();
exit_lock(lock_fd, lock_file);
return (&sysevent_conf_mod_ops);
}
void
slm_fini()
{
int err;
if (strcmp(root_dir, "") != 0) {
return;
}
(void) mutex_lock(&cmdq_lock);
want_fini = 1;
(void) cond_signal(&cmdq_cv);
(void) cond_wait(&cmdq_thr_cv, &cmdq_lock);
(void) mutex_unlock(&cmdq_lock);
if ((err = thr_join(cmdq_thr_id, NULL, NULL)) != 0) {
syslog(LOG_ERR, THR_JOIN_ERR, strerror(err));
}
sysevent_close_channel(confd_handle);
confd_handle = NULL;
(void) mutex_destroy(&cmdq_lock);
(void) cond_destroy(&cmdq_cv);
(void) cond_destroy(&cmdq_thr_cv);
free_event_table();
}
static int
sysevent_conf_dummy_event(sysevent_t *ev, int flag)
{
return (0);
}