mirror_zfs/cmd/zed/zed_event.c
Richard Yao 97143b9d31 Introduce kmem_scnprintf()
`snprintf()` is meant to protect against buffer overflows, but operating
on the buffer using its return value, possibly by calling it again, can
cause a buffer overflow, because it will return how many characters it
would have written if it had enough space even when it did not. In a
number of places, we repeatedly call snprintf() by successively
incrementing a buffer offset and decrementing a buffer length, by its
return value. This is a potentially unsafe usage of `snprintf()`
whenever the buffer length is reached. CodeQL complained about this.

To fix this, we introduce `kmem_scnprintf()`, which will return 0 when
the buffer is zero or the number of written characters, minus 1 to
exclude the NULL character, when the buffer was too small. In all other
cases, it behaves like snprintf(). The name is inspired by the Linux and
XNU kernels' `scnprintf()`. The implementation was written before I
thought to look at `scnprintf()` and had a good name for it, but it
turned out to have identical semantics to the Linux kernel version.
That lead to the name, `kmem_scnprintf()`.

CodeQL only catches this issue in loops, so repeated use of snprintf()
outside of a loop was not caught. As a result, a thorough audit of the
codebase was done to examine all instances of `snprintf()` usage for
potential problems and a few were caught. Fixes for them are included in
this patch.

Unfortunately, ZED is one of the places where `snprintf()` is
potentially used incorrectly. Since using `kmem_scnprintf()` in it would
require changing how it is linked, we modify its usage to make it safe,
no matter what buffer length is used. In addition, there was a bug in
the use of the return value where the NULL format character was not
being written by pwrite(). That has been fixed.

Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Signed-off-by: Richard Yao <richard.yao@alumni.stonybrook.edu>
Closes #14098
2022-10-29 13:05:11 -07:00

1003 lines
25 KiB
C

/*
* This file is part of the ZFS Event Daemon (ZED).
*
* Developed at Lawrence Livermore National Laboratory (LLNL-CODE-403049).
* Copyright (C) 2013-2014 Lawrence Livermore National Security, LLC.
* Refer to the OpenZFS git commit log for authoritative copyright attribution.
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License Version 1.0 (CDDL-1.0).
* You can obtain a copy of the license from the top-level file
* "OPENSOLARIS.LICENSE" or at <http://opensource.org/licenses/CDDL-1.0>.
* You may not use this file except in compliance with the license.
*/
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <libzfs_core.h>
#include <paths.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/zfs_ioctl.h>
#include <time.h>
#include <unistd.h>
#include <sys/fm/fs/zfs.h>
#include "zed.h"
#include "zed_conf.h"
#include "zed_disk_event.h"
#include "zed_event.h"
#include "zed_exec.h"
#include "zed_file.h"
#include "zed_log.h"
#include "zed_strings.h"
#include "agents/zfs_agents.h"
#define MAXBUF 4096
static int max_zevent_buf_len = 1 << 20;
/*
* Open the libzfs interface.
*/
int
zed_event_init(struct zed_conf *zcp)
{
if (!zcp)
zed_log_die("Failed zed_event_init: %s", strerror(EINVAL));
zcp->zfs_hdl = libzfs_init();
if (!zcp->zfs_hdl) {
if (zcp->do_idle)
return (-1);
zed_log_die("Failed to initialize libzfs");
}
zcp->zevent_fd = open(ZFS_DEV, O_RDWR | O_CLOEXEC);
if (zcp->zevent_fd < 0) {
if (zcp->do_idle)
return (-1);
zed_log_die("Failed to open \"%s\": %s",
ZFS_DEV, strerror(errno));
}
zfs_agent_init(zcp->zfs_hdl);
if (zed_disk_event_init() != 0) {
if (zcp->do_idle)
return (-1);
zed_log_die("Failed to initialize disk events");
}
if (zcp->max_zevent_buf_len != 0)
max_zevent_buf_len = zcp->max_zevent_buf_len;
return (0);
}
/*
* Close the libzfs interface.
*/
void
zed_event_fini(struct zed_conf *zcp)
{
if (!zcp)
zed_log_die("Failed zed_event_fini: %s", strerror(EINVAL));
zed_disk_event_fini();
zfs_agent_fini();
if (zcp->zevent_fd >= 0) {
if (close(zcp->zevent_fd) < 0)
zed_log_msg(LOG_WARNING, "Failed to close \"%s\": %s",
ZFS_DEV, strerror(errno));
zcp->zevent_fd = -1;
}
if (zcp->zfs_hdl) {
libzfs_fini(zcp->zfs_hdl);
zcp->zfs_hdl = NULL;
}
zed_exec_fini();
}
static void
_bump_event_queue_length(void)
{
int zzlm = -1, wr;
char qlen_buf[12] = {0}; /* parameter is int => max "-2147483647\n" */
long int qlen, orig_qlen;
zzlm = open("/sys/module/zfs/parameters/zfs_zevent_len_max", O_RDWR);
if (zzlm < 0)
goto done;
if (read(zzlm, qlen_buf, sizeof (qlen_buf)) < 0)
goto done;
qlen_buf[sizeof (qlen_buf) - 1] = '\0';
errno = 0;
orig_qlen = qlen = strtol(qlen_buf, NULL, 10);
if (errno == ERANGE)
goto done;
if (qlen <= 0)
qlen = 512; /* default zfs_zevent_len_max value */
else
qlen *= 2;
/*
* Don't consume all of kernel memory with event logs if something
* goes wrong.
*/
if (qlen > max_zevent_buf_len)
qlen = max_zevent_buf_len;
if (qlen == orig_qlen)
goto done;
wr = snprintf(qlen_buf, sizeof (qlen_buf), "%ld", qlen);
if (wr >= sizeof (qlen_buf)) {
wr = sizeof (qlen_buf) - 1;
zed_log_msg(LOG_WARNING, "Truncation in %s()", __func__);
}
if (pwrite(zzlm, qlen_buf, wr + 1, 0) < 0)
goto done;
zed_log_msg(LOG_WARNING, "Bumping queue length to %ld", qlen);
done:
if (zzlm > -1)
(void) close(zzlm);
}
/*
* Seek to the event specified by [saved_eid] and [saved_etime].
* This protects against processing a given event more than once.
* Return 0 upon a successful seek to the specified event, or -1 otherwise.
*
* A zevent is considered to be uniquely specified by its (eid,time) tuple.
* The unsigned 64b eid is set to 1 when the kernel module is loaded, and
* incremented by 1 for each new event. Since the state file can persist
* across a kernel module reload, the time must be checked to ensure a match.
*/
int
zed_event_seek(struct zed_conf *zcp, uint64_t saved_eid, int64_t saved_etime[])
{
uint64_t eid;
int found;
nvlist_t *nvl;
int n_dropped;
int64_t *etime;
uint_t nelem;
int rv;
if (!zcp) {
errno = EINVAL;
zed_log_msg(LOG_ERR, "Failed to seek zevent: %s",
strerror(errno));
return (-1);
}
eid = 0;
found = 0;
while ((eid < saved_eid) && !found) {
rv = zpool_events_next(zcp->zfs_hdl, &nvl, &n_dropped,
ZEVENT_NONBLOCK, zcp->zevent_fd);
if ((rv != 0) || !nvl)
break;
if (n_dropped > 0) {
zed_log_msg(LOG_WARNING, "Missed %d events", n_dropped);
_bump_event_queue_length();
}
if (nvlist_lookup_uint64(nvl, "eid", &eid) != 0) {
zed_log_msg(LOG_WARNING, "Failed to lookup zevent eid");
} else if (nvlist_lookup_int64_array(nvl, "time",
&etime, &nelem) != 0) {
zed_log_msg(LOG_WARNING,
"Failed to lookup zevent time (eid=%llu)", eid);
} else if (nelem != 2) {
zed_log_msg(LOG_WARNING,
"Failed to lookup zevent time (eid=%llu, nelem=%u)",
eid, nelem);
} else if ((eid != saved_eid) ||
(etime[0] != saved_etime[0]) ||
(etime[1] != saved_etime[1])) {
/* no-op */
} else {
found = 1;
}
free(nvl);
}
if (!found && (saved_eid > 0)) {
if (zpool_events_seek(zcp->zfs_hdl, ZEVENT_SEEK_START,
zcp->zevent_fd) < 0)
zed_log_msg(LOG_WARNING, "Failed to seek to eid=0");
else
eid = 0;
}
zed_log_msg(LOG_NOTICE, "Processing events since eid=%llu", eid);
return (found ? 0 : -1);
}
/*
* Return non-zero if nvpair [name] should be formatted in hex; o/w, return 0.
*/
static int
_zed_event_value_is_hex(const char *name)
{
const char *hex_suffix[] = {
"_guid",
"_guids",
NULL
};
const char **pp;
char *p;
if (!name)
return (0);
for (pp = hex_suffix; *pp; pp++) {
p = strstr(name, *pp);
if (p && strlen(p) == strlen(*pp))
return (1);
}
return (0);
}
/*
* Add an environment variable for [eid] to the container [zsp].
*
* The variable name is the concatenation of [prefix] and [name] converted to
* uppercase with non-alphanumeric characters converted to underscores;
* [prefix] is optional, and [name] must begin with an alphabetic character.
* If the converted variable name already exists within the container [zsp],
* its existing value will be replaced with the new value.
*
* The variable value is specified by the format string [fmt].
*
* Returns 0 on success, and -1 on error (with errno set).
*
* All environment variables in [zsp] should be added through this function.
*/
static __attribute__((format(printf, 5, 6))) int
_zed_event_add_var(uint64_t eid, zed_strings_t *zsp,
const char *prefix, const char *name, const char *fmt, ...)
{
char keybuf[MAXBUF];
char valbuf[MAXBUF];
char *dstp;
const char *srcp;
const char *lastp;
int n;
int buflen;
va_list vargs;
assert(zsp != NULL);
assert(fmt != NULL);
if (!name) {
errno = EINVAL;
zed_log_msg(LOG_WARNING,
"Failed to add variable for eid=%llu: Name is empty", eid);
return (-1);
} else if (!isalpha(name[0])) {
errno = EINVAL;
zed_log_msg(LOG_WARNING,
"Failed to add variable for eid=%llu: "
"Name \"%s\" is invalid", eid, name);
return (-1);
}
/*
* Construct the string key by converting PREFIX (if present) and NAME.
*/
dstp = keybuf;
lastp = keybuf + sizeof (keybuf);
if (prefix) {
for (srcp = prefix; *srcp && (dstp < lastp); srcp++)
*dstp++ = isalnum(*srcp) ? toupper(*srcp) : '_';
}
for (srcp = name; *srcp && (dstp < lastp); srcp++)
*dstp++ = isalnum(*srcp) ? toupper(*srcp) : '_';
if (dstp == lastp) {
errno = ENAMETOOLONG;
zed_log_msg(LOG_WARNING,
"Failed to add variable for eid=%llu: Name too long", eid);
return (-1);
}
*dstp = '\0';
/*
* Construct the string specified by "[PREFIX][NAME]=[FMT]".
*/
dstp = valbuf;
buflen = sizeof (valbuf);
n = strlcpy(dstp, keybuf, buflen);
if (n >= sizeof (valbuf)) {
errno = EMSGSIZE;
zed_log_msg(LOG_WARNING, "Failed to add %s for eid=%llu: %s",
keybuf, eid, "Exceeded buffer size");
return (-1);
}
dstp += n;
buflen -= n;
*dstp++ = '=';
buflen--;
if (buflen <= 0) {
errno = EMSGSIZE;
zed_log_msg(LOG_WARNING, "Failed to add %s for eid=%llu: %s",
keybuf, eid, "Exceeded buffer size");
return (-1);
}
va_start(vargs, fmt);
n = vsnprintf(dstp, buflen, fmt, vargs);
va_end(vargs);
if ((n < 0) || (n >= buflen)) {
errno = EMSGSIZE;
zed_log_msg(LOG_WARNING, "Failed to add %s for eid=%llu: %s",
keybuf, eid, "Exceeded buffer size");
return (-1);
} else if (zed_strings_add(zsp, keybuf, valbuf) < 0) {
zed_log_msg(LOG_WARNING, "Failed to add %s for eid=%llu: %s",
keybuf, eid, strerror(errno));
return (-1);
}
return (0);
}
static int
_zed_event_add_array_err(uint64_t eid, const char *name)
{
errno = EMSGSIZE;
zed_log_msg(LOG_WARNING,
"Failed to convert nvpair \"%s\" for eid=%llu: "
"Exceeded buffer size", name, eid);
return (-1);
}
static int
_zed_event_add_int8_array(uint64_t eid, zed_strings_t *zsp,
const char *prefix, nvpair_t *nvp)
{
char buf[MAXBUF];
int buflen = sizeof (buf);
const char *name;
int8_t *i8p;
uint_t nelem;
uint_t i;
char *p;
int n;
assert((nvp != NULL) && (nvpair_type(nvp) == DATA_TYPE_INT8_ARRAY));
name = nvpair_name(nvp);
(void) nvpair_value_int8_array(nvp, &i8p, &nelem);
for (i = 0, p = buf; (i < nelem) && (buflen > 0); i++) {
n = snprintf(p, buflen, "%d ", i8p[i]);
if ((n < 0) || (n >= buflen))
return (_zed_event_add_array_err(eid, name));
p += n;
buflen -= n;
}
if (nelem > 0)
*--p = '\0';
return (_zed_event_add_var(eid, zsp, prefix, name, "%s", buf));
}
static int
_zed_event_add_uint8_array(uint64_t eid, zed_strings_t *zsp,
const char *prefix, nvpair_t *nvp)
{
char buf[MAXBUF];
int buflen = sizeof (buf);
const char *name;
uint8_t *u8p;
uint_t nelem;
uint_t i;
char *p;
int n;
assert((nvp != NULL) && (nvpair_type(nvp) == DATA_TYPE_UINT8_ARRAY));
name = nvpair_name(nvp);
(void) nvpair_value_uint8_array(nvp, &u8p, &nelem);
for (i = 0, p = buf; (i < nelem) && (buflen > 0); i++) {
n = snprintf(p, buflen, "%u ", u8p[i]);
if ((n < 0) || (n >= buflen))
return (_zed_event_add_array_err(eid, name));
p += n;
buflen -= n;
}
if (nelem > 0)
*--p = '\0';
return (_zed_event_add_var(eid, zsp, prefix, name, "%s", buf));
}
static int
_zed_event_add_int16_array(uint64_t eid, zed_strings_t *zsp,
const char *prefix, nvpair_t *nvp)
{
char buf[MAXBUF];
int buflen = sizeof (buf);
const char *name;
int16_t *i16p;
uint_t nelem;
uint_t i;
char *p;
int n;
assert((nvp != NULL) && (nvpair_type(nvp) == DATA_TYPE_INT16_ARRAY));
name = nvpair_name(nvp);
(void) nvpair_value_int16_array(nvp, &i16p, &nelem);
for (i = 0, p = buf; (i < nelem) && (buflen > 0); i++) {
n = snprintf(p, buflen, "%d ", i16p[i]);
if ((n < 0) || (n >= buflen))
return (_zed_event_add_array_err(eid, name));
p += n;
buflen -= n;
}
if (nelem > 0)
*--p = '\0';
return (_zed_event_add_var(eid, zsp, prefix, name, "%s", buf));
}
static int
_zed_event_add_uint16_array(uint64_t eid, zed_strings_t *zsp,
const char *prefix, nvpair_t *nvp)
{
char buf[MAXBUF];
int buflen = sizeof (buf);
const char *name;
uint16_t *u16p;
uint_t nelem;
uint_t i;
char *p;
int n;
assert((nvp != NULL) && (nvpair_type(nvp) == DATA_TYPE_UINT16_ARRAY));
name = nvpair_name(nvp);
(void) nvpair_value_uint16_array(nvp, &u16p, &nelem);
for (i = 0, p = buf; (i < nelem) && (buflen > 0); i++) {
n = snprintf(p, buflen, "%u ", u16p[i]);
if ((n < 0) || (n >= buflen))
return (_zed_event_add_array_err(eid, name));
p += n;
buflen -= n;
}
if (nelem > 0)
*--p = '\0';
return (_zed_event_add_var(eid, zsp, prefix, name, "%s", buf));
}
static int
_zed_event_add_int32_array(uint64_t eid, zed_strings_t *zsp,
const char *prefix, nvpair_t *nvp)
{
char buf[MAXBUF];
int buflen = sizeof (buf);
const char *name;
int32_t *i32p;
uint_t nelem;
uint_t i;
char *p;
int n;
assert((nvp != NULL) && (nvpair_type(nvp) == DATA_TYPE_INT32_ARRAY));
name = nvpair_name(nvp);
(void) nvpair_value_int32_array(nvp, &i32p, &nelem);
for (i = 0, p = buf; (i < nelem) && (buflen > 0); i++) {
n = snprintf(p, buflen, "%d ", i32p[i]);
if ((n < 0) || (n >= buflen))
return (_zed_event_add_array_err(eid, name));
p += n;
buflen -= n;
}
if (nelem > 0)
*--p = '\0';
return (_zed_event_add_var(eid, zsp, prefix, name, "%s", buf));
}
static int
_zed_event_add_uint32_array(uint64_t eid, zed_strings_t *zsp,
const char *prefix, nvpair_t *nvp)
{
char buf[MAXBUF];
int buflen = sizeof (buf);
const char *name;
uint32_t *u32p;
uint_t nelem;
uint_t i;
char *p;
int n;
assert((nvp != NULL) && (nvpair_type(nvp) == DATA_TYPE_UINT32_ARRAY));
name = nvpair_name(nvp);
(void) nvpair_value_uint32_array(nvp, &u32p, &nelem);
for (i = 0, p = buf; (i < nelem) && (buflen > 0); i++) {
n = snprintf(p, buflen, "%u ", u32p[i]);
if ((n < 0) || (n >= buflen))
return (_zed_event_add_array_err(eid, name));
p += n;
buflen -= n;
}
if (nelem > 0)
*--p = '\0';
return (_zed_event_add_var(eid, zsp, prefix, name, "%s", buf));
}
static int
_zed_event_add_int64_array(uint64_t eid, zed_strings_t *zsp,
const char *prefix, nvpair_t *nvp)
{
char buf[MAXBUF];
int buflen = sizeof (buf);
const char *name;
int64_t *i64p;
uint_t nelem;
uint_t i;
char *p;
int n;
assert((nvp != NULL) && (nvpair_type(nvp) == DATA_TYPE_INT64_ARRAY));
name = nvpair_name(nvp);
(void) nvpair_value_int64_array(nvp, &i64p, &nelem);
for (i = 0, p = buf; (i < nelem) && (buflen > 0); i++) {
n = snprintf(p, buflen, "%lld ", (u_longlong_t)i64p[i]);
if ((n < 0) || (n >= buflen))
return (_zed_event_add_array_err(eid, name));
p += n;
buflen -= n;
}
if (nelem > 0)
*--p = '\0';
return (_zed_event_add_var(eid, zsp, prefix, name, "%s", buf));
}
static int
_zed_event_add_uint64_array(uint64_t eid, zed_strings_t *zsp,
const char *prefix, nvpair_t *nvp)
{
char buf[MAXBUF];
int buflen = sizeof (buf);
const char *name;
const char *fmt;
uint64_t *u64p;
uint_t nelem;
uint_t i;
char *p;
int n;
assert((nvp != NULL) && (nvpair_type(nvp) == DATA_TYPE_UINT64_ARRAY));
name = nvpair_name(nvp);
fmt = _zed_event_value_is_hex(name) ? "0x%.16llX " : "%llu ";
(void) nvpair_value_uint64_array(nvp, &u64p, &nelem);
for (i = 0, p = buf; (i < nelem) && (buflen > 0); i++) {
n = snprintf(p, buflen, fmt, (u_longlong_t)u64p[i]);
if ((n < 0) || (n >= buflen))
return (_zed_event_add_array_err(eid, name));
p += n;
buflen -= n;
}
if (nelem > 0)
*--p = '\0';
return (_zed_event_add_var(eid, zsp, prefix, name, "%s", buf));
}
static int
_zed_event_add_string_array(uint64_t eid, zed_strings_t *zsp,
const char *prefix, nvpair_t *nvp)
{
char buf[MAXBUF];
int buflen = sizeof (buf);
const char *name;
char **strp;
uint_t nelem;
uint_t i;
char *p;
int n;
assert((nvp != NULL) && (nvpair_type(nvp) == DATA_TYPE_STRING_ARRAY));
name = nvpair_name(nvp);
(void) nvpair_value_string_array(nvp, &strp, &nelem);
for (i = 0, p = buf; (i < nelem) && (buflen > 0); i++) {
n = snprintf(p, buflen, "%s ", strp[i] ? strp[i] : "<NULL>");
if ((n < 0) || (n >= buflen))
return (_zed_event_add_array_err(eid, name));
p += n;
buflen -= n;
}
if (nelem > 0)
*--p = '\0';
return (_zed_event_add_var(eid, zsp, prefix, name, "%s", buf));
}
/*
* Convert the nvpair [nvp] to a string which is added to the environment
* of the child process.
* Return 0 on success, -1 on error.
*/
static void
_zed_event_add_nvpair(uint64_t eid, zed_strings_t *zsp, nvpair_t *nvp)
{
const char *name;
data_type_t type;
const char *prefix = ZEVENT_VAR_PREFIX;
boolean_t b;
double d;
uint8_t i8;
uint16_t i16;
uint32_t i32;
uint64_t i64;
char *str;
assert(zsp != NULL);
assert(nvp != NULL);
name = nvpair_name(nvp);
type = nvpair_type(nvp);
switch (type) {
case DATA_TYPE_BOOLEAN:
_zed_event_add_var(eid, zsp, prefix, name, "%s", "1");
break;
case DATA_TYPE_BOOLEAN_VALUE:
(void) nvpair_value_boolean_value(nvp, &b);
_zed_event_add_var(eid, zsp, prefix, name, "%s", b ? "1" : "0");
break;
case DATA_TYPE_BYTE:
(void) nvpair_value_byte(nvp, &i8);
_zed_event_add_var(eid, zsp, prefix, name, "%d", i8);
break;
case DATA_TYPE_INT8:
(void) nvpair_value_int8(nvp, (int8_t *)&i8);
_zed_event_add_var(eid, zsp, prefix, name, "%d", i8);
break;
case DATA_TYPE_UINT8:
(void) nvpair_value_uint8(nvp, &i8);
_zed_event_add_var(eid, zsp, prefix, name, "%u", i8);
break;
case DATA_TYPE_INT16:
(void) nvpair_value_int16(nvp, (int16_t *)&i16);
_zed_event_add_var(eid, zsp, prefix, name, "%d", i16);
break;
case DATA_TYPE_UINT16:
(void) nvpair_value_uint16(nvp, &i16);
_zed_event_add_var(eid, zsp, prefix, name, "%u", i16);
break;
case DATA_TYPE_INT32:
(void) nvpair_value_int32(nvp, (int32_t *)&i32);
_zed_event_add_var(eid, zsp, prefix, name, "%d", i32);
break;
case DATA_TYPE_UINT32:
(void) nvpair_value_uint32(nvp, &i32);
_zed_event_add_var(eid, zsp, prefix, name, "%u", i32);
break;
case DATA_TYPE_INT64:
(void) nvpair_value_int64(nvp, (int64_t *)&i64);
_zed_event_add_var(eid, zsp, prefix, name,
"%lld", (longlong_t)i64);
break;
case DATA_TYPE_UINT64:
(void) nvpair_value_uint64(nvp, &i64);
_zed_event_add_var(eid, zsp, prefix, name,
(_zed_event_value_is_hex(name) ? "0x%.16llX" : "%llu"),
(u_longlong_t)i64);
/*
* shadow readable strings for vdev state pairs
*/
if (strcmp(name, FM_EREPORT_PAYLOAD_ZFS_VDEV_STATE) == 0 ||
strcmp(name, FM_EREPORT_PAYLOAD_ZFS_VDEV_LASTSTATE) == 0) {
char alt[32];
(void) snprintf(alt, sizeof (alt), "%s_str", name);
_zed_event_add_var(eid, zsp, prefix, alt, "%s",
zpool_state_to_name(i64, VDEV_AUX_NONE));
} else
/*
* shadow readable strings for pool state
*/
if (strcmp(name, FM_EREPORT_PAYLOAD_ZFS_POOL_STATE) == 0) {
char alt[32];
(void) snprintf(alt, sizeof (alt), "%s_str", name);
_zed_event_add_var(eid, zsp, prefix, alt, "%s",
zpool_pool_state_to_name(i64));
}
break;
case DATA_TYPE_DOUBLE:
(void) nvpair_value_double(nvp, &d);
_zed_event_add_var(eid, zsp, prefix, name, "%g", d);
break;
case DATA_TYPE_HRTIME:
(void) nvpair_value_hrtime(nvp, (hrtime_t *)&i64);
_zed_event_add_var(eid, zsp, prefix, name,
"%llu", (u_longlong_t)i64);
break;
case DATA_TYPE_STRING:
(void) nvpair_value_string(nvp, &str);
_zed_event_add_var(eid, zsp, prefix, name,
"%s", (str ? str : "<NULL>"));
break;
case DATA_TYPE_INT8_ARRAY:
_zed_event_add_int8_array(eid, zsp, prefix, nvp);
break;
case DATA_TYPE_UINT8_ARRAY:
_zed_event_add_uint8_array(eid, zsp, prefix, nvp);
break;
case DATA_TYPE_INT16_ARRAY:
_zed_event_add_int16_array(eid, zsp, prefix, nvp);
break;
case DATA_TYPE_UINT16_ARRAY:
_zed_event_add_uint16_array(eid, zsp, prefix, nvp);
break;
case DATA_TYPE_INT32_ARRAY:
_zed_event_add_int32_array(eid, zsp, prefix, nvp);
break;
case DATA_TYPE_UINT32_ARRAY:
_zed_event_add_uint32_array(eid, zsp, prefix, nvp);
break;
case DATA_TYPE_INT64_ARRAY:
_zed_event_add_int64_array(eid, zsp, prefix, nvp);
break;
case DATA_TYPE_UINT64_ARRAY:
_zed_event_add_uint64_array(eid, zsp, prefix, nvp);
break;
case DATA_TYPE_STRING_ARRAY:
_zed_event_add_string_array(eid, zsp, prefix, nvp);
break;
case DATA_TYPE_NVLIST:
case DATA_TYPE_BOOLEAN_ARRAY:
case DATA_TYPE_BYTE_ARRAY:
case DATA_TYPE_NVLIST_ARRAY:
_zed_event_add_var(eid, zsp, prefix, name, "_NOT_IMPLEMENTED_");
break;
default:
errno = EINVAL;
zed_log_msg(LOG_WARNING,
"Failed to convert nvpair \"%s\" for eid=%llu: "
"Unrecognized type=%u", name, eid, (unsigned int) type);
break;
}
}
/*
* Restrict various environment variables to safe and sane values
* when constructing the environment for the child process, unless
* we're running with a custom $PATH (like under the ZFS test suite).
*
* Reference: Secure Programming Cookbook by Viega & Messier, Section 1.1.
*/
static void
_zed_event_add_env_restrict(uint64_t eid, zed_strings_t *zsp,
const char *path)
{
const char *env_restrict[][2] = {
{ "IFS", " \t\n" },
{ "PATH", _PATH_STDPATH },
{ "ZDB", SBINDIR "/zdb" },
{ "ZED", SBINDIR "/zed" },
{ "ZFS", SBINDIR "/zfs" },
{ "ZINJECT", SBINDIR "/zinject" },
{ "ZPOOL", SBINDIR "/zpool" },
{ "ZFS_ALIAS", ZFS_META_ALIAS },
{ "ZFS_VERSION", ZFS_META_VERSION },
{ "ZFS_RELEASE", ZFS_META_RELEASE },
{ NULL, NULL }
};
/*
* If we have a custom $PATH, use the default ZFS binary locations
* instead of the hard-coded ones.
*/
const char *env_path[][2] = {
{ "IFS", " \t\n" },
{ "PATH", NULL }, /* $PATH copied in later on */
{ "ZDB", "zdb" },
{ "ZED", "zed" },
{ "ZFS", "zfs" },
{ "ZINJECT", "zinject" },
{ "ZPOOL", "zpool" },
{ "ZFS_ALIAS", ZFS_META_ALIAS },
{ "ZFS_VERSION", ZFS_META_VERSION },
{ "ZFS_RELEASE", ZFS_META_RELEASE },
{ NULL, NULL }
};
const char *(*pa)[2];
assert(zsp != NULL);
pa = path != NULL ? env_path : env_restrict;
for (; *(*pa); pa++) {
/* Use our custom $PATH if we have one */
if (path != NULL && strcmp((*pa)[0], "PATH") == 0)
(*pa)[1] = path;
_zed_event_add_var(eid, zsp, NULL, (*pa)[0], "%s", (*pa)[1]);
}
}
/*
* Preserve specified variables from the parent environment
* when constructing the environment for the child process.
*
* Reference: Secure Programming Cookbook by Viega & Messier, Section 1.1.
*/
static void
_zed_event_add_env_preserve(uint64_t eid, zed_strings_t *zsp)
{
const char *env_preserve[] = {
"TZ",
NULL
};
const char **keyp;
const char *val;
assert(zsp != NULL);
for (keyp = env_preserve; *keyp; keyp++) {
if ((val = getenv(*keyp)))
_zed_event_add_var(eid, zsp, NULL, *keyp, "%s", val);
}
}
/*
* Compute the "subclass" by removing the first 3 components of [class]
* (which will always be of the form "*.fs.zfs"). Return a pointer inside
* the string [class], or NULL if insufficient components exist.
*/
static const char *
_zed_event_get_subclass(const char *class)
{
const char *p;
int i;
if (!class)
return (NULL);
p = class;
for (i = 0; i < 3; i++) {
p = strchr(p, '.');
if (!p)
break;
p++;
}
return (p);
}
/*
* Convert the zevent time from a 2-element array of 64b integers
* into a more convenient form:
* - TIME_SECS is the second component of the time.
* - TIME_NSECS is the nanosecond component of the time.
* - TIME_STRING is an almost-RFC3339-compliant string representation.
*/
static void
_zed_event_add_time_strings(uint64_t eid, zed_strings_t *zsp, int64_t etime[])
{
struct tm stp;
char buf[32];
assert(zsp != NULL);
assert(etime != NULL);
_zed_event_add_var(eid, zsp, ZEVENT_VAR_PREFIX, "TIME_SECS",
"%" PRId64, etime[0]);
_zed_event_add_var(eid, zsp, ZEVENT_VAR_PREFIX, "TIME_NSECS",
"%" PRId64, etime[1]);
if (!localtime_r((const time_t *) &etime[0], &stp)) {
zed_log_msg(LOG_WARNING, "Failed to add %s%s for eid=%llu: %s",
ZEVENT_VAR_PREFIX, "TIME_STRING", eid, "localtime error");
} else if (!strftime(buf, sizeof (buf), "%Y-%m-%d %H:%M:%S%z", &stp)) {
zed_log_msg(LOG_WARNING, "Failed to add %s%s for eid=%llu: %s",
ZEVENT_VAR_PREFIX, "TIME_STRING", eid, "strftime error");
} else {
_zed_event_add_var(eid, zsp, ZEVENT_VAR_PREFIX, "TIME_STRING",
"%s", buf);
}
}
/*
* Service the next zevent, blocking until one is available.
*/
int
zed_event_service(struct zed_conf *zcp)
{
nvlist_t *nvl;
nvpair_t *nvp;
int n_dropped;
zed_strings_t *zsp;
uint64_t eid;
int64_t *etime;
uint_t nelem;
char *class;
const char *subclass;
int rv;
if (!zcp) {
errno = EINVAL;
zed_log_msg(LOG_ERR, "Failed to service zevent: %s",
strerror(errno));
return (EINVAL);
}
rv = zpool_events_next(zcp->zfs_hdl, &nvl, &n_dropped, ZEVENT_NONE,
zcp->zevent_fd);
if ((rv != 0) || !nvl)
return (errno);
if (n_dropped > 0) {
zed_log_msg(LOG_WARNING, "Missed %d events", n_dropped);
_bump_event_queue_length();
}
if (nvlist_lookup_uint64(nvl, "eid", &eid) != 0) {
zed_log_msg(LOG_WARNING, "Failed to lookup zevent eid");
} else if (nvlist_lookup_int64_array(
nvl, "time", &etime, &nelem) != 0) {
zed_log_msg(LOG_WARNING,
"Failed to lookup zevent time (eid=%llu)", eid);
} else if (nelem != 2) {
zed_log_msg(LOG_WARNING,
"Failed to lookup zevent time (eid=%llu, nelem=%u)",
eid, nelem);
} else if (nvlist_lookup_string(nvl, "class", &class) != 0) {
zed_log_msg(LOG_WARNING,
"Failed to lookup zevent class (eid=%llu)", eid);
} else {
/* let internal modules see this event first */
zfs_agent_post_event(class, NULL, nvl);
zsp = zed_strings_create();
nvp = NULL;
while ((nvp = nvlist_next_nvpair(nvl, nvp)))
_zed_event_add_nvpair(eid, zsp, nvp);
_zed_event_add_env_restrict(eid, zsp, zcp->path);
_zed_event_add_env_preserve(eid, zsp);
_zed_event_add_var(eid, zsp, ZED_VAR_PREFIX, "PID",
"%d", (int)getpid());
_zed_event_add_var(eid, zsp, ZED_VAR_PREFIX, "ZEDLET_DIR",
"%s", zcp->zedlet_dir);
subclass = _zed_event_get_subclass(class);
_zed_event_add_var(eid, zsp, ZEVENT_VAR_PREFIX, "SUBCLASS",
"%s", (subclass ? subclass : class));
_zed_event_add_time_strings(eid, zsp, etime);
zed_exec_process(eid, class, subclass, zcp, zsp);
zed_conf_write_state(zcp, eid, etime);
zed_strings_destroy(zsp);
}
nvlist_free(nvl);
return (0);
}