| /* |
| * 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 |
| |
| /* |
| * 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"); |
| } |
| |
| 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; |
| |
| 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; |
| 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; |
| |
| if (qlen > INT_MAX) |
| qlen = INT_MAX; |
| wr = snprintf(qlen_buf, sizeof (qlen_buf), "%ld", qlen); |
| |
| if (pwrite(zzlm, qlen_buf, wr, 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", |
| "%lld", (long long int) etime[0]); |
| _zed_event_add_var(eid, zsp, ZEVENT_VAR_PREFIX, "TIME_NSECS", |
| "%lld", (long long int) etime[1]); |
| |
| if (!(stp = localtime((const time_t *) &etime[0]))) { |
| 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); |
| } |