diff --git a/cmd/Makefile.am b/cmd/Makefile.am index 5fc9e83971d8..3994d1434e99 100644 --- a/cmd/Makefile.am +++ b/cmd/Makefile.am @@ -1,27 +1,28 @@ include $(top_srcdir)/config/Shellcheck.am SUBDIRS = zfs zpool zdb zhack zinject zstream ztest SUBDIRS += fsck_zfs vdev_id raidz_test zfs_ids_to_path SUBDIRS += zpool_influxdb CPPCHECKDIRS = zfs zpool zdb zhack zinject zstream ztest CPPCHECKDIRS += raidz_test zfs_ids_to_path zpool_influxdb -# TODO: #12084: SHELLCHECKDIRS = fsck_zfs vdev_id zpool -SHELLCHECKDIRS = fsck_zfs zpool +# TODO: #12084: SHELLCHECKDIRS += vdev_id +SHELLCHECKDIRS = fsck_zfs zed zpool zvol_wait +SHELLCHECK_OPTS = --enable=all if USING_PYTHON SUBDIRS += arcstat arc_summary dbufstat endif if BUILD_LINUX SUBDIRS += mount_zfs zed zgenhostid zvol_id zvol_wait CPPCHECKDIRS += mount_zfs zed zgenhostid zvol_id SHELLCHECKDIRS += zed endif PHONY = cppcheck cppcheck: $(CPPCHECKDIRS) set -e ; for dir in $(CPPCHECKDIRS) ; do \ $(MAKE) -C $$dir cppcheck ; \ done diff --git a/cmd/fsck_zfs/Makefile.am b/cmd/fsck_zfs/Makefile.am index f8139f117ff2..d86ea1f786f4 100644 --- a/cmd/fsck_zfs/Makefile.am +++ b/cmd/fsck_zfs/Makefile.am @@ -1,6 +1,8 @@ include $(top_srcdir)/config/Substfiles.am include $(top_srcdir)/config/Shellcheck.am dist_sbin_SCRIPTS = fsck.zfs SUBSTFILES += $(dist_sbin_SCRIPTS) + +SHELLCHECK_OPTS = --enable=all diff --git a/cmd/zed/Makefile.am b/cmd/zed/Makefile.am index 7b662994d1c6..1492123e1696 100644 --- a/cmd/zed/Makefile.am +++ b/cmd/zed/Makefile.am @@ -1,53 +1,54 @@ include $(top_srcdir)/config/Rules.am include $(top_srcdir)/config/Shellcheck.am AM_CFLAGS += $(LIBUDEV_CFLAGS) $(LIBUUID_CFLAGS) SUBDIRS = zed.d SHELLCHECKDIRS = $(SUBDIRS) +SHELLCHECK_OPTS = --enable=all sbin_PROGRAMS = zed ZED_SRC = \ zed.c \ zed.h \ zed_conf.c \ zed_conf.h \ zed_disk_event.c \ zed_disk_event.h \ zed_event.c \ zed_event.h \ zed_exec.c \ zed_exec.h \ zed_file.c \ zed_file.h \ zed_log.c \ zed_log.h \ zed_strings.c \ zed_strings.h FMA_SRC = \ agents/zfs_agents.c \ agents/zfs_agents.h \ agents/zfs_diagnosis.c \ agents/zfs_mod.c \ agents/zfs_retire.c \ agents/fmd_api.c \ agents/fmd_api.h \ agents/fmd_serd.c \ agents/fmd_serd.h zed_SOURCES = $(ZED_SRC) $(FMA_SRC) zed_LDADD = \ $(abs_top_builddir)/lib/libzfs/libzfs.la \ $(abs_top_builddir)/lib/libzfs_core/libzfs_core.la \ $(abs_top_builddir)/lib/libnvpair/libnvpair.la \ $(abs_top_builddir)/lib/libuutil/libuutil.la zed_LDADD += -lrt $(LIBATOMIC_LIBS) $(LIBUDEV_LIBS) $(LIBUUID_LIBS) zed_LDFLAGS = -pthread EXTRA_DIST = agents/README.md include $(top_srcdir)/config/CppCheck.am diff --git a/cmd/zed/zed.d/Makefile.am b/cmd/zed/zed.d/Makefile.am index 2c8173b3e769..24efaa74f154 100644 --- a/cmd/zed/zed.d/Makefile.am +++ b/cmd/zed/zed.d/Makefile.am @@ -1,57 +1,61 @@ include $(top_srcdir)/config/Rules.am include $(top_srcdir)/config/Substfiles.am include $(top_srcdir)/config/Shellcheck.am EXTRA_DIST += README zedconfdir = $(sysconfdir)/zfs/zed.d dist_zedconf_DATA = \ zed-functions.sh \ zed.rc +SHELLCHECKSCRIPTS = zed-functions.sh zed.rc +SHELLCHECK_OPTS = --enable=all +SHELLCHECK_SHELL = dash + zedexecdir = $(zfsexecdir)/zed.d dist_zedexec_SCRIPTS = \ all-debug.sh \ all-syslog.sh \ data-notify.sh \ generic-notify.sh \ resilver_finish-notify.sh \ scrub_finish-notify.sh \ statechange-led.sh \ statechange-notify.sh \ vdev_clear-led.sh \ vdev_attach-led.sh \ pool_import-led.sh \ resilver_finish-start-scrub.sh \ trim_finish-notify.sh nodist_zedexec_SCRIPTS = history_event-zfs-list-cacher.sh SUBSTFILES += $(nodist_zedexec_SCRIPTS) zedconfdefaults = \ all-syslog.sh \ data-notify.sh \ history_event-zfs-list-cacher.sh \ resilver_finish-notify.sh \ scrub_finish-notify.sh \ statechange-led.sh \ statechange-notify.sh \ vdev_clear-led.sh \ vdev_attach-led.sh \ pool_import-led.sh \ resilver_finish-start-scrub.sh install-data-hook: $(MKDIR_P) "$(DESTDIR)$(zedconfdir)" for f in $(zedconfdefaults); do \ test -f "$(DESTDIR)$(zedconfdir)/$${f}" -o \ -L "$(DESTDIR)$(zedconfdir)/$${f}" || \ ln -s "$(zedexecdir)/$${f}" "$(DESTDIR)$(zedconfdir)"; \ done chmod 0600 "$(DESTDIR)$(zedconfdir)/zed.rc" # False positive: 1>&"${ZED_FLOCK_FD}" looks suspiciously similar to a >&filename bash extension CHECKBASHISMS_IGNORE = -e 'should be >word 2>&1' -e '&"$${ZED_FLOCK_FD}"' diff --git a/cmd/zed/zed.d/all-debug.sh b/cmd/zed/zed.d/all-debug.sh index 824c9fe423d7..ba19b96b082f 100755 --- a/cmd/zed/zed.d/all-debug.sh +++ b/cmd/zed/zed.d/all-debug.sh @@ -1,22 +1,23 @@ #!/bin/sh +# shellcheck disable=SC2154 # # Log all environment variables to ZED_DEBUG_LOG. # # This can be a useful aid when developing/debugging ZEDLETs since it shows the # environment variables defined for each zevent. [ -f "${ZED_ZEDLET_DIR}/zed.rc" ] && . "${ZED_ZEDLET_DIR}/zed.rc" . "${ZED_ZEDLET_DIR}/zed-functions.sh" : "${ZED_DEBUG_LOG:="${TMPDIR:="/tmp"}/zed.debug.log"}" zed_exit_if_ignoring_this_event zed_lock "${ZED_DEBUG_LOG}" { printenv | sort echo } 1>&"${ZED_FLOCK_FD}" zed_unlock "${ZED_DEBUG_LOG}" exit 0 diff --git a/cmd/zed/zed.d/all-syslog.sh b/cmd/zed/zed.d/all-syslog.sh index ea108c47b779..5f601144a97c 100755 --- a/cmd/zed/zed.d/all-syslog.sh +++ b/cmd/zed/zed.d/all-syslog.sh @@ -1,51 +1,52 @@ #!/bin/sh +# shellcheck disable=SC2154 # # Copyright (C) 2013-2014 Lawrence Livermore National Security, LLC. # Copyright (c) 2020 by Delphix. All rights reserved. # # # Log the zevent via syslog. # [ -f "${ZED_ZEDLET_DIR}/zed.rc" ] && . "${ZED_ZEDLET_DIR}/zed.rc" . "${ZED_ZEDLET_DIR}/zed-functions.sh" zed_exit_if_ignoring_this_event # build a string of name=value pairs for this event msg="eid=${ZEVENT_EID} class=${ZEVENT_SUBCLASS}" if [ "${ZED_SYSLOG_DISPLAY_GUIDS}" = "1" ]; then [ -n "${ZEVENT_POOL_GUID}" ] && msg="${msg} pool_guid=${ZEVENT_POOL_GUID}" [ -n "${ZEVENT_VDEV_GUID}" ] && msg="${msg} vdev_guid=${ZEVENT_VDEV_GUID}" else [ -n "${ZEVENT_POOL}" ] && msg="${msg} pool='${ZEVENT_POOL}'" [ -n "${ZEVENT_VDEV_PATH}" ] && msg="${msg} vdev=${ZEVENT_VDEV_PATH##*/}" fi # log pool state if state is anything other than 'ACTIVE' [ -n "${ZEVENT_POOL_STATE_STR}" ] && [ "$ZEVENT_POOL_STATE" -ne 0 ] && \ msg="${msg} pool_state=${ZEVENT_POOL_STATE_STR}" # Log the following payload nvpairs if they are present [ -n "${ZEVENT_VDEV_STATE_STR}" ] && msg="${msg} vdev_state=${ZEVENT_VDEV_STATE_STR}" [ -n "${ZEVENT_CKSUM_ALGORITHM}" ] && msg="${msg} algorithm=${ZEVENT_CKSUM_ALGORITHM}" [ -n "${ZEVENT_ZIO_SIZE}" ] && msg="${msg} size=${ZEVENT_ZIO_SIZE}" [ -n "${ZEVENT_ZIO_OFFSET}" ] && msg="${msg} offset=${ZEVENT_ZIO_OFFSET}" [ -n "${ZEVENT_ZIO_PRIORITY}" ] && msg="${msg} priority=${ZEVENT_ZIO_PRIORITY}" [ -n "${ZEVENT_ZIO_ERR}" ] && msg="${msg} err=${ZEVENT_ZIO_ERR}" [ -n "${ZEVENT_ZIO_FLAGS}" ] && msg="${msg} flags=$(printf '0x%x' "${ZEVENT_ZIO_FLAGS}")" # log delays that are >= 10 milisec [ -n "${ZEVENT_ZIO_DELAY}" ] && [ "$ZEVENT_ZIO_DELAY" -gt 10000000 ] && \ msg="${msg} delay=$((ZEVENT_ZIO_DELAY / 1000000))ms" # list the bookmark data together # shellcheck disable=SC2153 [ -n "${ZEVENT_ZIO_OBJSET}" ] && \ msg="${msg} bookmark=${ZEVENT_ZIO_OBJSET}:${ZEVENT_ZIO_OBJECT}:${ZEVENT_ZIO_LEVEL}:${ZEVENT_ZIO_BLKID}" zed_log_msg "${msg}" exit 0 diff --git a/cmd/zed/zed.d/data-notify.sh b/cmd/zed/zed.d/data-notify.sh index 792d30a66d23..9846769b1101 100755 --- a/cmd/zed/zed.d/data-notify.sh +++ b/cmd/zed/zed.d/data-notify.sh @@ -1,43 +1,44 @@ #!/bin/sh +# shellcheck disable=SC2154 # # Send notification in response to a DATA error. # # Only one notification per ZED_NOTIFY_INTERVAL_SECS will be sent for a given # class/pool/[vdev] combination. This protects against spamming the recipient # should multiple events occur together in time for the same pool/[vdev]. # # Exit codes: # 0: notification sent # 1: notification failed # 2: notification not configured # 3: notification suppressed # 9: internal error [ -f "${ZED_ZEDLET_DIR}/zed.rc" ] && . "${ZED_ZEDLET_DIR}/zed.rc" . "${ZED_ZEDLET_DIR}/zed-functions.sh" [ -n "${ZEVENT_POOL}" ] || exit 9 [ -n "${ZEVENT_SUBCLASS}" ] || exit 9 [ -n "${ZED_NOTIFY_DATA}" ] || exit 3 rate_limit_tag="${ZEVENT_POOL};${ZEVENT_VDEV_GUID:-0};${ZEVENT_SUBCLASS};notify" zed_rate_limit "${rate_limit_tag}" || exit 3 umask 077 note_subject="ZFS ${ZEVENT_SUBCLASS} error for ${ZEVENT_POOL} on $(hostname)" note_pathname="$(mktemp)" { echo "ZFS has detected a data error:" echo echo " eid: ${ZEVENT_EID}" echo " class: ${ZEVENT_SUBCLASS}" echo " host: $(hostname)" echo " time: ${ZEVENT_TIME_STRING}" echo " error: ${ZEVENT_ZIO_ERR}" echo " objid: ${ZEVENT_ZIO_OBJSET}:${ZEVENT_ZIO_OBJECT}" echo " pool: ${ZEVENT_POOL}" } > "${note_pathname}" zed_notify "${note_subject}" "${note_pathname}"; rv=$? rm -f "${note_pathname}" exit "${rv}" diff --git a/cmd/zed/zed.d/generic-notify.sh b/cmd/zed/zed.d/generic-notify.sh index 9cf657e39970..e73b053902fa 100755 --- a/cmd/zed/zed.d/generic-notify.sh +++ b/cmd/zed/zed.d/generic-notify.sh @@ -1,54 +1,55 @@ #!/bin/sh +# shellcheck disable=SC2154 # # Send notification in response to a given zevent. # # This is a generic script than can be symlinked to a file in the # enabled-zedlets directory to have a notification sent when a particular # class of zevents occurs. The symlink filename must begin with the zevent # (sub)class string (e.g., "probe_failure-notify.sh" for the "probe_failure" # subclass). Refer to the zed(8) manpage for details. # # Only one notification per ZED_NOTIFY_INTERVAL_SECS will be sent for a given # class/pool combination. This protects against spamming the recipient # should multiple events occur together in time for the same pool. # # Exit codes: # 0: notification sent # 1: notification failed # 2: notification not configured # 3: notification suppressed [ -f "${ZED_ZEDLET_DIR}/zed.rc" ] && . "${ZED_ZEDLET_DIR}/zed.rc" . "${ZED_ZEDLET_DIR}/zed-functions.sh" # Rate-limit the notification based in part on the filename. # rate_limit_tag="${ZEVENT_POOL};${ZEVENT_SUBCLASS};${0##*/}" rate_limit_interval="${ZED_NOTIFY_INTERVAL_SECS}" zed_rate_limit "${rate_limit_tag}" "${rate_limit_interval}" || exit 3 umask 077 pool_str="${ZEVENT_POOL:+" for ${ZEVENT_POOL}"}" host_str=" on $(hostname)" note_subject="ZFS ${ZEVENT_SUBCLASS} event${pool_str}${host_str}" note_pathname="$(mktemp)" { echo "ZFS has posted the following event:" echo echo " eid: ${ZEVENT_EID}" echo " class: ${ZEVENT_SUBCLASS}" echo " host: $(hostname)" echo " time: ${ZEVENT_TIME_STRING}" [ -n "${ZEVENT_VDEV_TYPE}" ] && echo " vtype: ${ZEVENT_VDEV_TYPE}" [ -n "${ZEVENT_VDEV_PATH}" ] && echo " vpath: ${ZEVENT_VDEV_PATH}" [ -n "${ZEVENT_VDEV_GUID}" ] && echo " vguid: ${ZEVENT_VDEV_GUID}" [ -n "${ZEVENT_POOL}" ] && [ -x "${ZPOOL}" ] \ && "${ZPOOL}" status "${ZEVENT_POOL}" } > "${note_pathname}" zed_notify "${note_subject}" "${note_pathname}"; rv=$? rm -f "${note_pathname}" exit "${rv}" diff --git a/cmd/zed/zed.d/history_event-zfs-list-cacher.sh.in b/cmd/zed/zed.d/history_event-zfs-list-cacher.sh.in index db40fa36d668..8c5031a38c6a 100755 --- a/cmd/zed/zed.d/history_event-zfs-list-cacher.sh.in +++ b/cmd/zed/zed.d/history_event-zfs-list-cacher.sh.in @@ -1,84 +1,85 @@ #!/bin/sh +# shellcheck disable=SC2154 # # Track changes to enumerated pools for use in early-boot set -ef FSLIST="@sysconfdir@/zfs/zfs-list.cache/${ZEVENT_POOL}" FSLIST_TMP="@runstatedir@/zfs-list.cache@${ZEVENT_POOL}" # If the pool specific cache file is not writeable, abort [ -w "${FSLIST}" ] || exit 0 [ -f "${ZED_ZEDLET_DIR}/zed.rc" ] && . "${ZED_ZEDLET_DIR}/zed.rc" . "${ZED_ZEDLET_DIR}/zed-functions.sh" [ "$ZEVENT_SUBCLASS" != "history_event" ] && exit 0 zed_check_cmd "${ZFS}" sort diff # If we are acting on a snapshot, we have nothing to do [ "${ZEVENT_HISTORY_DSNAME%@*}" = "${ZEVENT_HISTORY_DSNAME}" ] || exit 0 # We lock the output file to avoid simultaneous writes. # If we run into trouble, log and drop the lock abort_alter() { zed_log_msg "Error updating zfs-list.cache for ${ZEVENT_POOL}!" zed_unlock "${FSLIST}" } finished() { zed_unlock "${FSLIST}" trap - EXIT exit 0 } case "${ZEVENT_HISTORY_INTERNAL_NAME}" in create|"finish receiving"|import|destroy|rename) ;; export) zed_lock "${FSLIST}" trap abort_alter EXIT echo > "${FSLIST}" finished ;; set|inherit) # Only act if one of the tracked properties is altered. case "${ZEVENT_HISTORY_INTERNAL_STR%%=*}" in canmount|mountpoint|atime|relatime|devices|exec|readonly| \ setuid|nbmand|encroot|keylocation|org.openzfs.systemd:requires| \ org.openzfs.systemd:requires-mounts-for| \ org.openzfs.systemd:before|org.openzfs.systemd:after| \ org.openzfs.systemd:wanted-by|org.openzfs.systemd:required-by| \ org.openzfs.systemd:nofail|org.openzfs.systemd:ignore \ ) ;; *) exit 0 ;; esac ;; *) # Ignore all other events. exit 0 ;; esac zed_lock "${FSLIST}" trap abort_alter EXIT PROPS="name,mountpoint,canmount,atime,relatime,devices,exec\ ,readonly,setuid,nbmand,encroot,keylocation\ ,org.openzfs.systemd:requires,org.openzfs.systemd:requires-mounts-for\ ,org.openzfs.systemd:before,org.openzfs.systemd:after\ ,org.openzfs.systemd:wanted-by,org.openzfs.systemd:required-by\ ,org.openzfs.systemd:nofail,org.openzfs.systemd:ignore" -"${ZFS}" list -H -t filesystem -o $PROPS -r "${ZEVENT_POOL}" > "${FSLIST_TMP}" +"${ZFS}" list -H -t filesystem -o "${PROPS}" -r "${ZEVENT_POOL}" > "${FSLIST_TMP}" # Sort the output so that it is stable sort "${FSLIST_TMP}" -o "${FSLIST_TMP}" # Don't modify the file if it hasn't changed diff -q "${FSLIST_TMP}" "${FSLIST}" || cat "${FSLIST_TMP}" > "${FSLIST}" rm -f "${FSLIST_TMP}" finished diff --git a/cmd/zed/zed.d/resilver_finish-start-scrub.sh b/cmd/zed/zed.d/resilver_finish-start-scrub.sh index c7cfd1ddba80..cafce6fde54b 100755 --- a/cmd/zed/zed.d/resilver_finish-start-scrub.sh +++ b/cmd/zed/zed.d/resilver_finish-start-scrub.sh @@ -1,19 +1,20 @@ #!/bin/sh +# shellcheck disable=SC2154 # resilver_finish-start-scrub.sh # Run a scrub after a resilver # # Exit codes: # 1: Internal error # 2: Script wasn't enabled in zed.rc # 3: Scrubs are automatically started for sequential resilvers [ -f "${ZED_ZEDLET_DIR}/zed.rc" ] && . "${ZED_ZEDLET_DIR}/zed.rc" . "${ZED_ZEDLET_DIR}/zed-functions.sh" [ "${ZED_SCRUB_AFTER_RESILVER}" = "1" ] || exit 2 [ "${ZEVENT_RESILVER_TYPE}" != "sequential" ] || exit 3 [ -n "${ZEVENT_POOL}" ] || exit 1 [ -n "${ZEVENT_SUBCLASS}" ] || exit 1 zed_check_cmd "${ZPOOL}" || exit 1 zed_log_msg "Starting scrub after resilver on ${ZEVENT_POOL}" "${ZPOOL}" scrub "${ZEVENT_POOL}" diff --git a/cmd/zed/zed.d/scrub_finish-notify.sh b/cmd/zed/zed.d/scrub_finish-notify.sh index 5c0124b8d7e7..fc9dc23e04aa 100755 --- a/cmd/zed/zed.d/scrub_finish-notify.sh +++ b/cmd/zed/zed.d/scrub_finish-notify.sh @@ -1,59 +1,60 @@ #!/bin/sh +# shellcheck disable=SC2154 # # Send notification in response to a RESILVER_FINISH or SCRUB_FINISH. # # By default, "zpool status" output will only be included for a scrub_finish # zevent if the pool is not healthy; to always include its output, set # ZED_NOTIFY_VERBOSE=1. # # Exit codes: # 0: notification sent # 1: notification failed # 2: notification not configured # 3: notification suppressed # 9: internal error [ -f "${ZED_ZEDLET_DIR}/zed.rc" ] && . "${ZED_ZEDLET_DIR}/zed.rc" . "${ZED_ZEDLET_DIR}/zed-functions.sh" [ -n "${ZEVENT_POOL}" ] || exit 9 [ -n "${ZEVENT_SUBCLASS}" ] || exit 9 if [ "${ZEVENT_SUBCLASS}" = "resilver_finish" ]; then action="resilver" elif [ "${ZEVENT_SUBCLASS}" = "scrub_finish" ]; then action="scrub" else zed_log_err "unsupported event class \"${ZEVENT_SUBCLASS}\"" exit 9 fi zed_check_cmd "${ZPOOL}" || exit 9 # For scrub, suppress notification if the pool is healthy # and verbosity is not enabled. # if [ "${ZEVENT_SUBCLASS}" = "scrub_finish" ]; then healthy="$("${ZPOOL}" status -x "${ZEVENT_POOL}" \ | grep "'${ZEVENT_POOL}' is healthy")" [ -n "${healthy}" ] && [ "${ZED_NOTIFY_VERBOSE}" -eq 0 ] && exit 3 fi umask 077 note_subject="ZFS ${ZEVENT_SUBCLASS} event for ${ZEVENT_POOL} on $(hostname)" note_pathname="$(mktemp)" { echo "ZFS has finished a ${action}:" echo echo " eid: ${ZEVENT_EID}" echo " class: ${ZEVENT_SUBCLASS}" echo " host: $(hostname)" echo " time: ${ZEVENT_TIME_STRING}" "${ZPOOL}" status "${ZEVENT_POOL}" } > "${note_pathname}" zed_notify "${note_subject}" "${note_pathname}"; rv=$? rm -f "${note_pathname}" exit "${rv}" diff --git a/cmd/zed/zed.d/statechange-led.sh b/cmd/zed/zed.d/statechange-led.sh index 26e6064fa94a..46bfc1b866f1 100755 --- a/cmd/zed/zed.d/statechange-led.sh +++ b/cmd/zed/zed.d/statechange-led.sh @@ -1,240 +1,242 @@ #!/bin/sh +# shellcheck disable=SC2154 # # Turn off/on vdevs' enclosure fault LEDs when their pool's state changes. # # Turn a vdev's fault LED on if it becomes FAULTED, DEGRADED or UNAVAIL. # Turn its LED off when it's back ONLINE again. # # This script run in two basic modes: # # 1. If $ZEVENT_VDEV_ENC_SYSFS_PATH and $ZEVENT_VDEV_STATE_STR are set, then # only set the LED for that particular vdev. This is the case for statechange # events and some vdev_* events. # # 2. If those vars are not set, then check the state of all vdevs in the pool # and set the LEDs accordingly. This is the case for pool_import events. # # Note that this script requires that your enclosure be supported by the # Linux SCSI Enclosure services (SES) driver. The script will do nothing # if you have no enclosure, or if your enclosure isn't supported. # # Exit codes: # 0: enclosure led successfully set # 1: enclosure leds not available # 2: enclosure leds administratively disabled # 3: The led sysfs path passed from ZFS does not exist # 4: $ZPOOL not set # 5: awk is not installed [ -f "${ZED_ZEDLET_DIR}/zed.rc" ] && . "${ZED_ZEDLET_DIR}/zed.rc" . "${ZED_ZEDLET_DIR}/zed-functions.sh" if [ ! -d /sys/class/enclosure ] && [ ! -d /sys/bus/pci/slots ] ; then # No JBOD enclosure or NVMe slots exit 1 fi if [ "${ZED_USE_ENCLOSURE_LEDS}" != "1" ] ; then exit 2 fi zed_check_cmd "$ZPOOL" || exit 4 zed_check_cmd awk || exit 5 # Global used in set_led debug print vdev="" # check_and_set_led (file, val) # # Read an enclosure sysfs file, and write it if it's not already set to 'val' # # Arguments # file: sysfs file to set (like /sys/class/enclosure/0:0:1:0/SLOT 10/fault) # val: value to set it to # # Return # 0 on success, 3 on missing sysfs path # check_and_set_led() { file="$1" val="$2" if [ -z "$val" ]; then return 0 fi if [ ! -e "$file" ] ; then return 3 fi # If another process is accessing the LED when we attempt to update it, # the update will be lost so retry until the LED actually changes or we # timeout. for _ in 1 2 3 4 5; do # We want to check the current state first, since writing to the # 'fault' entry always causes a SES command, even if the # current state is already what you want. read -r current < "${file}" # On some enclosures if you write 1 to fault, and read it back, # it will return 2. Treat all non-zero values as 1 for # simplicity. if [ "$current" != "0" ] ; then current=1 fi if [ "$current" != "$val" ] ; then echo "$val" > "$file" zed_log_msg "vdev $vdev set '$file' LED to $val" else break fi done } # Fault LEDs for JBODs and NVMe drives are handled a little differently. # # On JBODs the fault LED is called 'fault' and on a path like this: # # /sys/class/enclosure/0:0:1:0/SLOT 10/fault # # On NVMe it's called 'attention' and on a path like this: # # /sys/bus/pci/slot/0/attention # # This function returns the full path to the fault LED file for a given # enclosure/slot directory. # path_to_led() { dir=$1 if [ -f "$dir/fault" ] ; then echo "$dir/fault" elif [ -f "$dir/attention" ] ; then echo "$dir/attention" fi } state_to_val() { state="$1" case "$state" in FAULTED|DEGRADED|UNAVAIL) echo 1 ;; ONLINE) echo 0 ;; + *) + echo "invalid state: $state" + ;; esac } # # Given a nvme name like 'nvme0n1', pass back its slot directory # like "/sys/bus/pci/slots/0" # nvme_dev_to_slot() { dev="$1" # Get the address "0000:01:00.0" - address=$(cat "/sys/class/block/$dev/device/address") - - # For each /sys/bus/pci/slots subdir that is an actual number - # (rather than weird directories like "1-3/"). - # shellcheck disable=SC2010 - for i in $(ls /sys/bus/pci/slots/ | grep -E "^[0-9]+$") ; do - this_address=$(cat "/sys/bus/pci/slots/$i/address") - - # The format of address is a little different between - # /sys/class/block/$dev/device/address and - # /sys/bus/pci/slots/ - # - # address= "0000:01:00.0" - # this_address = "0000:01:00" - # - if echo "$address" | grep -Eq ^"$this_address" ; then - echo "/sys/bus/pci/slots/$i" - break - fi - done + read -r address < "/sys/class/block/$dev/device/address" + + find /sys/bus/pci/slots -regex '.*/[0-9]+/address$' | \ + while read -r sys_addr; do + read -r this_address < "$sys_addr" + + # The format of address is a little different between + # /sys/class/block/$dev/device/address and + # /sys/bus/pci/slots/ + # + # address= "0000:01:00.0" + # this_address = "0000:01:00" + # + if echo "$address" | grep -Eq ^"$this_address" ; then + echo "${sys_addr%/*}" + break + fi + done } # process_pool (pool) # # Iterate through a pool and set the vdevs' enclosure slot LEDs to # those vdevs' state. # # Arguments # pool: Pool name. # # Return # 0 on success, 3 on missing sysfs path # process_pool() { pool="$1" # The output will be the vdevs only (from "grep '/dev/'"): # # U45 ONLINE 0 0 0 /dev/sdk 0 # U46 ONLINE 0 0 0 /dev/sdm 0 # U47 ONLINE 0 0 0 /dev/sdn 0 # U50 ONLINE 0 0 0 /dev/sdbn 0 # ZPOOL_SCRIPTS_AS_ROOT=1 $ZPOOL status -c upath,fault_led "$pool" | grep '/dev/' | ( rc=0 while read -r vdev state _ _ _ therest; do # Read out current LED value and path # Get dev name (like 'sda') dev=$(basename "$(echo "$therest" | awk '{print $(NF-1)}')") vdev_enc_sysfs_path=$(realpath "/sys/class/block/$dev/device/enclosure_device"*) if [ ! -d "$vdev_enc_sysfs_path" ] ; then # This is not a JBOD disk, but it could be a PCI NVMe drive vdev_enc_sysfs_path=$(nvme_dev_to_slot "$dev") fi current_val=$(echo "$therest" | awk '{print $NF}') if [ "$current_val" != "0" ] ; then current_val=1 fi if [ -z "$vdev_enc_sysfs_path" ] ; then # Skip anything with no sysfs LED entries continue fi led_path=$(path_to_led "$vdev_enc_sysfs_path") if [ ! -e "$led_path" ] ; then rc=3 zed_log_msg "vdev $vdev '$led_path' doesn't exist" continue fi val=$(state_to_val "$state") if [ "$current_val" = "$val" ] ; then # LED is already set correctly continue fi if ! check_and_set_led "$led_path" "$val"; then rc=3 fi done exit "$rc"; ) } if [ -n "$ZEVENT_VDEV_ENC_SYSFS_PATH" ] && [ -n "$ZEVENT_VDEV_STATE_STR" ] ; then # Got a statechange for an individual vdev val=$(state_to_val "$ZEVENT_VDEV_STATE_STR") vdev=$(basename "$ZEVENT_VDEV_PATH") ledpath=$(path_to_led "$ZEVENT_VDEV_ENC_SYSFS_PATH") check_and_set_led "$ledpath" "$val" else # Process the entire pool poolname=$(zed_guid_to_pool "$ZEVENT_POOL_GUID") process_pool "$poolname" fi diff --git a/cmd/zed/zed.d/statechange-notify.sh b/cmd/zed/zed.d/statechange-notify.sh index ab11dfbc99d5..c475fdb36660 100755 --- a/cmd/zed/zed.d/statechange-notify.sh +++ b/cmd/zed/zed.d/statechange-notify.sh @@ -1,75 +1,76 @@ #!/bin/sh +# shellcheck disable=SC2154 # # CDDL HEADER START # # 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 . # You may not use this file except in compliance with the license. # # CDDL HEADER END # # # Send notification in response to a fault induced statechange # # ZEVENT_SUBCLASS: 'statechange' # ZEVENT_VDEV_STATE_STR: 'DEGRADED', 'FAULTED', 'REMOVED', or 'UNAVAIL' # # Exit codes: # 0: notification sent # 1: notification failed # 2: notification not configured # 3: statechange not relevant # 4: statechange string missing (unexpected) [ -f "${ZED_ZEDLET_DIR}/zed.rc" ] && . "${ZED_ZEDLET_DIR}/zed.rc" . "${ZED_ZEDLET_DIR}/zed-functions.sh" [ -n "${ZEVENT_VDEV_STATE_STR}" ] || exit 4 if [ "${ZEVENT_VDEV_STATE_STR}" != "FAULTED" ] \ && [ "${ZEVENT_VDEV_STATE_STR}" != "DEGRADED" ] \ && [ "${ZEVENT_VDEV_STATE_STR}" != "REMOVED" ] \ && [ "${ZEVENT_VDEV_STATE_STR}" != "UNAVAIL" ]; then exit 3 fi umask 077 note_subject="ZFS device fault for pool ${ZEVENT_POOL_GUID} on $(hostname)" note_pathname="$(mktemp)" { if [ "${ZEVENT_VDEV_STATE_STR}" = "FAULTED" ] ; then echo "The number of I/O errors associated with a ZFS device exceeded" echo "acceptable levels. ZFS has marked the device as faulted." elif [ "${ZEVENT_VDEV_STATE_STR}" = "DEGRADED" ] ; then echo "The number of checksum errors associated with a ZFS device" echo "exceeded acceptable levels. ZFS has marked the device as" echo "degraded." else echo "ZFS has detected that a device was removed." fi echo echo " impact: Fault tolerance of the pool may be compromised." echo " eid: ${ZEVENT_EID}" echo " class: ${ZEVENT_SUBCLASS}" echo " state: ${ZEVENT_VDEV_STATE_STR}" echo " host: $(hostname)" echo " time: ${ZEVENT_TIME_STRING}" [ -n "${ZEVENT_VDEV_TYPE}" ] && echo " vtype: ${ZEVENT_VDEV_TYPE}" [ -n "${ZEVENT_VDEV_PATH}" ] && echo " vpath: ${ZEVENT_VDEV_PATH}" [ -n "${ZEVENT_VDEV_PHYSPATH}" ] && echo " vphys: ${ZEVENT_VDEV_PHYSPATH}" [ -n "${ZEVENT_VDEV_GUID}" ] && echo " vguid: ${ZEVENT_VDEV_GUID}" [ -n "${ZEVENT_VDEV_DEVID}" ] && echo " devid: ${ZEVENT_VDEV_DEVID}" echo " pool: ${ZEVENT_POOL_GUID}" } > "${note_pathname}" zed_notify "${note_subject}" "${note_pathname}"; rv=$? rm -f "${note_pathname}" exit "${rv}" diff --git a/cmd/zed/zed.d/trim_finish-notify.sh b/cmd/zed/zed.d/trim_finish-notify.sh index 8fdb64531d0a..a9ea489622c7 100755 --- a/cmd/zed/zed.d/trim_finish-notify.sh +++ b/cmd/zed/zed.d/trim_finish-notify.sh @@ -1,37 +1,38 @@ #!/bin/sh +# shellcheck disable=SC2154 # # Send notification in response to a TRIM_FINISH. The event # will be received for each vdev in the pool which was trimmed. # # Exit codes: # 0: notification sent # 1: notification failed # 2: notification not configured # 9: internal error [ -f "${ZED_ZEDLET_DIR}/zed.rc" ] && . "${ZED_ZEDLET_DIR}/zed.rc" . "${ZED_ZEDLET_DIR}/zed-functions.sh" [ -n "${ZEVENT_POOL}" ] || exit 9 [ -n "${ZEVENT_SUBCLASS}" ] || exit 9 zed_check_cmd "${ZPOOL}" || exit 9 umask 077 note_subject="ZFS ${ZEVENT_SUBCLASS} event for ${ZEVENT_POOL} on $(hostname)" note_pathname="$(mktemp)" { echo "ZFS has finished a trim:" echo echo " eid: ${ZEVENT_EID}" echo " class: ${ZEVENT_SUBCLASS}" echo " host: $(hostname)" echo " time: ${ZEVENT_TIME_STRING}" "${ZPOOL}" status -t "${ZEVENT_POOL}" } > "${note_pathname}" zed_notify "${note_subject}" "${note_pathname}"; rv=$? rm -f "${note_pathname}" exit "${rv}" diff --git a/cmd/zed/zed.d/zed-functions.sh b/cmd/zed/zed.d/zed-functions.sh index eb59036cf4d8..bbff9c008bd1 100644 --- a/cmd/zed/zed.d/zed-functions.sh +++ b/cmd/zed/zed.d/zed-functions.sh @@ -1,614 +1,614 @@ #!/bin/sh -# shellcheck disable=SC2039 +# shellcheck disable=SC2154,SC3043 # zed-functions.sh # # ZED helper functions for use in ZEDLETs # Variable Defaults # : "${ZED_LOCKDIR:="/var/lock"}" : "${ZED_NOTIFY_INTERVAL_SECS:=3600}" : "${ZED_NOTIFY_VERBOSE:=0}" : "${ZED_RUNDIR:="/var/run"}" : "${ZED_SYSLOG_PRIORITY:="daemon.notice"}" : "${ZED_SYSLOG_TAG:="zed"}" ZED_FLOCK_FD=8 # zed_check_cmd (cmd, ...) # # For each argument given, search PATH for the executable command [cmd]. # Log a message if [cmd] is not found. # # Arguments # cmd: name of executable command for which to search # # Return # 0 if all commands are found in PATH and are executable # n for a count of the command executables that are not found # zed_check_cmd() { local cmd local rv=0 for cmd; do if ! command -v "${cmd}" >/dev/null 2>&1; then zed_log_err "\"${cmd}\" not installed" rv=$((rv + 1)) fi done return "${rv}" } # zed_log_msg (msg, ...) # # Write all argument strings to the system log. # # Globals # ZED_SYSLOG_PRIORITY # ZED_SYSLOG_TAG # # Return # nothing # zed_log_msg() { logger -p "${ZED_SYSLOG_PRIORITY}" -t "${ZED_SYSLOG_TAG}" -- "$@" } # zed_log_err (msg, ...) # # Write an error message to the system log. This message will contain the # script name, EID, and all argument strings. # # Globals # ZED_SYSLOG_PRIORITY # ZED_SYSLOG_TAG # ZEVENT_EID # # Return # nothing # zed_log_err() { logger -p "${ZED_SYSLOG_PRIORITY}" -t "${ZED_SYSLOG_TAG}" -- "error:" \ "${0##*/}:""${ZEVENT_EID:+" eid=${ZEVENT_EID}:"}" "$@" } # zed_lock (lockfile, [fd]) # # Obtain an exclusive (write) lock on [lockfile]. If the lock cannot be # immediately acquired, wait until it becomes available. # # Every zed_lock() must be paired with a corresponding zed_unlock(). # # By default, flock-style locks associate the lockfile with file descriptor 8. # The bash manpage warns that file descriptors >9 should be used with care as # they may conflict with file descriptors used internally by the shell. File # descriptor 9 is reserved for zed_rate_limit(). If concurrent locks are held # within the same process, they must use different file descriptors (preferably # decrementing from 8); otherwise, obtaining a new lock with a given file # descriptor will release the previous lock associated with that descriptor. # # Arguments # lockfile: pathname of the lock file; the lock will be stored in # ZED_LOCKDIR unless the pathname contains a "/". # fd: integer for the file descriptor used by flock (OPTIONAL unless holding # concurrent locks) # # Globals # ZED_FLOCK_FD # ZED_LOCKDIR # # Return # nothing # zed_lock() { local lockfile="$1" local fd="${2:-${ZED_FLOCK_FD}}" local umask_bak local err [ -n "${lockfile}" ] || return if ! expr "${lockfile}" : '.*/' >/dev/null 2>&1; then lockfile="${ZED_LOCKDIR}/${lockfile}" fi umask_bak="$(umask)" umask 077 # Obtain a lock on the file bound to the given file descriptor. # eval "exec ${fd}>> '${lockfile}'" if ! err="$(flock --exclusive "${fd}" 2>&1)"; then zed_log_err "failed to lock \"${lockfile}\": ${err}" fi umask "${umask_bak}" } # zed_unlock (lockfile, [fd]) # # Release the lock on [lockfile]. # # Arguments # lockfile: pathname of the lock file # fd: integer for the file descriptor used by flock (must match the file # descriptor passed to the zed_lock function call) # # Globals # ZED_FLOCK_FD # ZED_LOCKDIR # # Return # nothing # zed_unlock() { local lockfile="$1" local fd="${2:-${ZED_FLOCK_FD}}" local err [ -n "${lockfile}" ] || return if ! expr "${lockfile}" : '.*/' >/dev/null 2>&1; then lockfile="${ZED_LOCKDIR}/${lockfile}" fi # Release the lock and close the file descriptor. if ! err="$(flock --unlock "${fd}" 2>&1)"; then zed_log_err "failed to unlock \"${lockfile}\": ${err}" fi eval "exec ${fd}>&-" } # zed_notify (subject, pathname) # # Send a notification via all available methods. # # Arguments # subject: notification subject # pathname: pathname containing the notification message (OPTIONAL) # # Return # 0: notification succeeded via at least one method # 1: notification failed # 2: no notification methods configured # zed_notify() { local subject="$1" local pathname="$2" local num_success=0 local num_failure=0 zed_notify_email "${subject}" "${pathname}"; rv=$? [ "${rv}" -eq 0 ] && num_success=$((num_success + 1)) [ "${rv}" -eq 1 ] && num_failure=$((num_failure + 1)) zed_notify_pushbullet "${subject}" "${pathname}"; rv=$? [ "${rv}" -eq 0 ] && num_success=$((num_success + 1)) [ "${rv}" -eq 1 ] && num_failure=$((num_failure + 1)) zed_notify_slack_webhook "${subject}" "${pathname}"; rv=$? [ "${rv}" -eq 0 ] && num_success=$((num_success + 1)) [ "${rv}" -eq 1 ] && num_failure=$((num_failure + 1)) zed_notify_pushover "${subject}" "${pathname}"; rv=$? [ "${rv}" -eq 0 ] && num_success=$((num_success + 1)) [ "${rv}" -eq 1 ] && num_failure=$((num_failure + 1)) [ "${num_success}" -gt 0 ] && return 0 [ "${num_failure}" -gt 0 ] && return 1 return 2 } # zed_notify_email (subject, pathname) # # Send a notification via email to the address specified by ZED_EMAIL_ADDR. # # Requires the mail executable to be installed in the standard PATH, or # ZED_EMAIL_PROG to be defined with the pathname of an executable capable of # reading a message body from stdin. # # Command-line options to the mail executable can be specified in # ZED_EMAIL_OPTS. This undergoes the following keyword substitutions: # - @ADDRESS@ is replaced with the space-delimited recipient email address(es) # - @SUBJECT@ is replaced with the notification subject # # Arguments # subject: notification subject # pathname: pathname containing the notification message (OPTIONAL) # # Globals # ZED_EMAIL_PROG # ZED_EMAIL_OPTS # ZED_EMAIL_ADDR # # Return # 0: notification sent # 1: notification failed # 2: not configured # zed_notify_email() { local subject="$1" local pathname="${2:-"/dev/null"}" : "${ZED_EMAIL_PROG:="mail"}" : "${ZED_EMAIL_OPTS:="-s '@SUBJECT@' @ADDRESS@"}" # For backward compatibility with ZED_EMAIL. if [ -n "${ZED_EMAIL}" ] && [ -z "${ZED_EMAIL_ADDR}" ]; then ZED_EMAIL_ADDR="${ZED_EMAIL}" fi [ -n "${ZED_EMAIL_ADDR}" ] || return 2 zed_check_cmd "${ZED_EMAIL_PROG}" || return 1 [ -n "${subject}" ] || return 1 if [ ! -r "${pathname}" ]; then zed_log_err \ "${ZED_EMAIL_PROG##*/} cannot read \"${pathname}\"" return 1 fi ZED_EMAIL_OPTS="$(echo "${ZED_EMAIL_OPTS}" \ | sed -e "s/@ADDRESS@/${ZED_EMAIL_ADDR}/g" \ -e "s/@SUBJECT@/${subject}/g")" - # shellcheck disable=SC2086 + # shellcheck disable=SC2086,SC2248 eval ${ZED_EMAIL_PROG} ${ZED_EMAIL_OPTS} < "${pathname}" >/dev/null 2>&1 rv=$? if [ "${rv}" -ne 0 ]; then zed_log_err "${ZED_EMAIL_PROG##*/} exit=${rv}" return 1 fi return 0 } # zed_notify_pushbullet (subject, pathname) # # Send a notification via Pushbullet . # The access token (ZED_PUSHBULLET_ACCESS_TOKEN) identifies this client to the # Pushbullet server. The optional channel tag (ZED_PUSHBULLET_CHANNEL_TAG) is # for pushing to notification feeds that can be subscribed to; if a channel is # not defined, push notifications will instead be sent to all devices # associated with the account specified by the access token. # # Requires awk, curl, and sed executables to be installed in the standard PATH. # # References # https://docs.pushbullet.com/ # https://www.pushbullet.com/security # # Arguments # subject: notification subject # pathname: pathname containing the notification message (OPTIONAL) # # Globals # ZED_PUSHBULLET_ACCESS_TOKEN # ZED_PUSHBULLET_CHANNEL_TAG # # Return # 0: notification sent # 1: notification failed # 2: not configured # zed_notify_pushbullet() { local subject="$1" local pathname="${2:-"/dev/null"}" local msg_body local msg_tag local msg_json local msg_out local msg_err local url="https://api.pushbullet.com/v2/pushes" [ -n "${ZED_PUSHBULLET_ACCESS_TOKEN}" ] || return 2 [ -n "${subject}" ] || return 1 if [ ! -r "${pathname}" ]; then zed_log_err "pushbullet cannot read \"${pathname}\"" return 1 fi zed_check_cmd "awk" "curl" "sed" || return 1 # Escape the following characters in the message body for JSON: # newline, backslash, double quote, horizontal tab, vertical tab, # and carriage return. # msg_body="$(awk '{ ORS="\\n" } { gsub(/\\/, "\\\\"); gsub(/"/, "\\\""); gsub(/\t/, "\\t"); gsub(/\f/, "\\f"); gsub(/\r/, "\\r"); print }' \ "${pathname}")" # Push to a channel if one is configured. # [ -n "${ZED_PUSHBULLET_CHANNEL_TAG}" ] && msg_tag="$(printf \ '"channel_tag": "%s", ' "${ZED_PUSHBULLET_CHANNEL_TAG}")" # Construct the JSON message for pushing a note. # msg_json="$(printf '{%s"type": "note", "title": "%s", "body": "%s"}' \ "${msg_tag}" "${subject}" "${msg_body}")" # Send the POST request and check for errors. # msg_out="$(curl -u "${ZED_PUSHBULLET_ACCESS_TOKEN}:" -X POST "${url}" \ --header "Content-Type: application/json" --data-binary "${msg_json}" \ 2>/dev/null)"; rv=$? if [ "${rv}" -ne 0 ]; then zed_log_err "curl exit=${rv}" return 1 fi msg_err="$(echo "${msg_out}" \ | sed -n -e 's/.*"error" *:.*"message" *: *"\([^"]*\)".*/\1/p')" if [ -n "${msg_err}" ]; then zed_log_err "pushbullet \"${msg_err}"\" return 1 fi return 0 } # zed_notify_slack_webhook (subject, pathname) # # Notification via Slack Webhook . # The Webhook URL (ZED_SLACK_WEBHOOK_URL) identifies this client to the # Slack channel. # # Requires awk, curl, and sed executables to be installed in the standard PATH. # # References # https://api.slack.com/incoming-webhooks # # Arguments # subject: notification subject # pathname: pathname containing the notification message (OPTIONAL) # # Globals # ZED_SLACK_WEBHOOK_URL # # Return # 0: notification sent # 1: notification failed # 2: not configured # zed_notify_slack_webhook() { [ -n "${ZED_SLACK_WEBHOOK_URL}" ] || return 2 local subject="$1" local pathname="${2:-"/dev/null"}" local msg_body local msg_tag local msg_json local msg_out local msg_err local url="${ZED_SLACK_WEBHOOK_URL}" [ -n "${subject}" ] || return 1 if [ ! -r "${pathname}" ]; then zed_log_err "slack webhook cannot read \"${pathname}\"" return 1 fi zed_check_cmd "awk" "curl" "sed" || return 1 # Escape the following characters in the message body for JSON: # newline, backslash, double quote, horizontal tab, vertical tab, # and carriage return. # msg_body="$(awk '{ ORS="\\n" } { gsub(/\\/, "\\\\"); gsub(/"/, "\\\""); gsub(/\t/, "\\t"); gsub(/\f/, "\\f"); gsub(/\r/, "\\r"); print }' \ "${pathname}")" # Construct the JSON message for posting. # msg_json="$(printf '{"text": "*%s*\n%s"}' "${subject}" "${msg_body}" )" # Send the POST request and check for errors. # msg_out="$(curl -X POST "${url}" \ --header "Content-Type: application/json" --data-binary "${msg_json}" \ 2>/dev/null)"; rv=$? if [ "${rv}" -ne 0 ]; then zed_log_err "curl exit=${rv}" return 1 fi msg_err="$(echo "${msg_out}" \ | sed -n -e 's/.*"error" *:.*"message" *: *"\([^"]*\)".*/\1/p')" if [ -n "${msg_err}" ]; then zed_log_err "slack webhook \"${msg_err}"\" return 1 fi return 0 } # zed_notify_pushover (subject, pathname) # # Send a notification via Pushover . # The access token (ZED_PUSHOVER_TOKEN) identifies this client to the # Pushover server. The user token (ZED_PUSHOVER_USER) defines the user or # group to which the notification will be sent. # # Requires curl and sed executables to be installed in the standard PATH. # # References # https://pushover.net/api # # Arguments # subject: notification subject # pathname: pathname containing the notification message (OPTIONAL) # # Globals # ZED_PUSHOVER_TOKEN # ZED_PUSHOVER_USER # # Return # 0: notification sent # 1: notification failed # 2: not configured # zed_notify_pushover() { local subject="$1" local pathname="${2:-"/dev/null"}" local msg_body local msg_out local msg_err local url="https://api.pushover.net/1/messages.json" [ -n "${ZED_PUSHOVER_TOKEN}" ] && [ -n "${ZED_PUSHOVER_USER}" ] || return 2 if [ ! -r "${pathname}" ]; then zed_log_err "pushover cannot read \"${pathname}\"" return 1 fi zed_check_cmd "curl" "sed" || return 1 # Read the message body in. # msg_body="$(cat "${pathname}")" if [ -z "${msg_body}" ] then msg_body=$subject subject="" fi # Send the POST request and check for errors. # msg_out="$( \ curl \ --form-string "token=${ZED_PUSHOVER_TOKEN}" \ --form-string "user=${ZED_PUSHOVER_USER}" \ --form-string "message=${msg_body}" \ --form-string "title=${subject}" \ "${url}" \ 2>/dev/null \ )"; rv=$? if [ "${rv}" -ne 0 ]; then zed_log_err "curl exit=${rv}" return 1 fi msg_err="$(echo "${msg_out}" \ | sed -n -e 's/.*"errors" *:.*\[\(.*\)\].*/\1/p')" if [ -n "${msg_err}" ]; then zed_log_err "pushover \"${msg_err}"\" return 1 fi return 0 } # zed_rate_limit (tag, [interval]) # # Check whether an event of a given type [tag] has already occurred within the # last [interval] seconds. # # This function obtains a lock on the statefile using file descriptor 9. # # Arguments # tag: arbitrary string for grouping related events to rate-limit # interval: time interval in seconds (OPTIONAL) # # Globals # ZED_NOTIFY_INTERVAL_SECS # ZED_RUNDIR # # Return # 0 if the event should be processed # 1 if the event should be dropped # # State File Format # time;tag # zed_rate_limit() { local tag="$1" local interval="${2:-${ZED_NOTIFY_INTERVAL_SECS}}" local lockfile="zed.zedlet.state.lock" local lockfile_fd=9 local statefile="${ZED_RUNDIR}/zed.zedlet.state" local time_now local time_prev local umask_bak local rv=0 [ -n "${tag}" ] || return 0 zed_lock "${lockfile}" "${lockfile_fd}" time_now="$(date +%s)" time_prev="$(grep -E "^[0-9]+;${tag}\$" "${statefile}" 2>/dev/null \ | tail -1 | cut -d\; -f1)" if [ -n "${time_prev}" ] \ && [ "$((time_now - time_prev))" -lt "${interval}" ]; then rv=1 else umask_bak="$(umask)" umask 077 grep -E -v "^[0-9]+;${tag}\$" "${statefile}" 2>/dev/null \ > "${statefile}.$$" echo "${time_now};${tag}" >> "${statefile}.$$" mv -f "${statefile}.$$" "${statefile}" umask "${umask_bak}" fi zed_unlock "${lockfile}" "${lockfile_fd}" return "${rv}" } # zed_guid_to_pool (guid) # # Convert a pool GUID into its pool name (like "tank") # Arguments # guid: pool GUID (decimal or hex) # # Return # Pool name # zed_guid_to_pool() { if [ -z "$1" ] ; then return fi guid="$(printf "%u" "$1")" $ZPOOL get -H -ovalue,name guid | awk '$1 == '"$guid"' {print $2; exit}' } # zed_exit_if_ignoring_this_event # # Exit the script if we should ignore this event, as determined by # $ZED_SYSLOG_SUBCLASS_INCLUDE and $ZED_SYSLOG_SUBCLASS_EXCLUDE in zed.rc. # This function assumes you've imported the normal zed variables. zed_exit_if_ignoring_this_event() { if [ -n "${ZED_SYSLOG_SUBCLASS_INCLUDE}" ]; then eval "case ${ZEVENT_SUBCLASS} in ${ZED_SYSLOG_SUBCLASS_INCLUDE});; *) exit 0;; esac" elif [ -n "${ZED_SYSLOG_SUBCLASS_EXCLUDE}" ]; then eval "case ${ZEVENT_SUBCLASS} in ${ZED_SYSLOG_SUBCLASS_EXCLUDE}) exit 0;; *);; esac" fi } diff --git a/cmd/zed/zed.d/zed.rc b/cmd/zed/zed.d/zed.rc index 9ac77f929c73..a8fdc86c3431 100644 --- a/cmd/zed/zed.d/zed.rc +++ b/cmd/zed/zed.d/zed.rc @@ -1,144 +1,145 @@ ## # zed.rc # # This file should be owned by root and permissioned 0600. ## +# shellcheck disable=SC2034 ## # Absolute path to the debug output file. # #ZED_DEBUG_LOG="/tmp/zed.debug.log" ## # Email address of the zpool administrator for receipt of notifications; # multiple addresses can be specified if they are delimited by whitespace. # Email will only be sent if ZED_EMAIL_ADDR is defined. # Enabled by default; comment to disable. # ZED_EMAIL_ADDR="root" ## # Name or path of executable responsible for sending notifications via email; # the mail program must be capable of reading a message body from stdin. # Email will only be sent if ZED_EMAIL_ADDR is defined. # #ZED_EMAIL_PROG="mail" ## # Command-line options for ZED_EMAIL_PROG. # The string @ADDRESS@ will be replaced with the recipient email address(es). # The string @SUBJECT@ will be replaced with the notification subject; # this should be protected with quotes to prevent word-splitting. # Email will only be sent if ZED_EMAIL_ADDR is defined. # #ZED_EMAIL_OPTS="-s '@SUBJECT@' @ADDRESS@" ## # Default directory for zed lock files. # #ZED_LOCKDIR="/var/lock" ## # Minimum number of seconds between notifications for a similar event. # #ZED_NOTIFY_INTERVAL_SECS=3600 ## # Notification verbosity. # If set to 0, suppress notification if the pool is healthy. # If set to 1, send notification regardless of pool health. # #ZED_NOTIFY_VERBOSE=0 ## # Send notifications for 'ereport.fs.zfs.data' events. # Disabled by default, any non-empty value will enable the feature. # #ZED_NOTIFY_DATA= ## # Pushbullet access token. # This grants full access to your account -- protect it accordingly! # # # Disabled by default; uncomment to enable. # #ZED_PUSHBULLET_ACCESS_TOKEN="" ## # Pushbullet channel tag for push notification feeds that can be subscribed to. # # If not defined, push notifications will instead be sent to all devices # associated with the account specified by the access token. # Disabled by default; uncomment to enable. # #ZED_PUSHBULLET_CHANNEL_TAG="" ## # Slack Webhook URL. # This allows posting to the given channel and includes an access token. # # Disabled by default; uncomment to enable. # #ZED_SLACK_WEBHOOK_URL="" ## # Pushover token. # This defines the application from which the notification will be sent. # # Disabled by default; uncomment to enable. # ZED_PUSHOVER_USER, below, must also be configured. # #ZED_PUSHOVER_TOKEN="" ## # Pushover user key. # This defines which user or group will receive Pushover notifications. # # Disabled by default; uncomment to enable. # ZED_PUSHOVER_TOKEN, above, must also be configured. #ZED_PUSHOVER_USER="" ## # Default directory for zed state files. # #ZED_RUNDIR="/var/run" ## # Turn on/off enclosure LEDs when drives get DEGRADED/FAULTED. This works for # device mapper and multipath devices as well. This works with JBOD enclosures # and NVMe PCI drives (assuming they're supported by Linux in sysfs). # ZED_USE_ENCLOSURE_LEDS=1 ## # Run a scrub after every resilver # Disabled by default, 1 to enable and 0 to disable. #ZED_SCRUB_AFTER_RESILVER=0 ## # The syslog priority (e.g., specified as a "facility.level" pair). # #ZED_SYSLOG_PRIORITY="daemon.notice" ## # The syslog tag for marking zed events. # #ZED_SYSLOG_TAG="zed" ## # Which set of event subclasses to log # By default, events from all subclasses are logged. # If ZED_SYSLOG_SUBCLASS_INCLUDE is set, only subclasses # matching the pattern are logged. Use the pipe symbol (|) # or shell wildcards (*, ?) to match multiple subclasses. # Otherwise, if ZED_SYSLOG_SUBCLASS_EXCLUDE is set, the # matching subclasses are excluded from logging. #ZED_SYSLOG_SUBCLASS_INCLUDE="checksum|scrub_*|vdev.*" ZED_SYSLOG_SUBCLASS_EXCLUDE="history_event" ## # Use GUIDs instead of names when logging pool and vdevs # Disabled by default, 1 to enable and 0 to disable. #ZED_SYSLOG_DISPLAY_GUIDS=1 diff --git a/cmd/zpool/Makefile.am b/cmd/zpool/Makefile.am index fa494c030e1c..b89b5db85800 100644 --- a/cmd/zpool/Makefile.am +++ b/cmd/zpool/Makefile.am @@ -1,189 +1,191 @@ include $(top_srcdir)/config/Rules.am include $(top_srcdir)/config/Shellcheck.am AM_CFLAGS += $(LIBBLKID_CFLAGS) $(LIBUUID_CFLAGS) DEFAULT_INCLUDES += -I$(srcdir) +SHELLCHECK_OPTS = --enable=all + sbin_PROGRAMS = zpool zpool_SOURCES = \ zpool_iter.c \ zpool_main.c \ zpool_util.c \ zpool_util.h \ zpool_vdev.c if BUILD_FREEBSD zpool_SOURCES += os/freebsd/zpool_vdev_os.c endif if BUILD_LINUX zpool_SOURCES += os/linux/zpool_vdev_os.c endif zpool_LDADD = \ $(abs_top_builddir)/lib/libzfs/libzfs.la \ $(abs_top_builddir)/lib/libzfs_core/libzfs_core.la \ $(abs_top_builddir)/lib/libnvpair/libnvpair.la \ $(abs_top_builddir)/lib/libuutil/libuutil.la \ $(abs_top_builddir)/lib/libzutil/libzutil.la zpool_LDADD += $(LTLIBINTL) if BUILD_FREEBSD zpool_LDADD += -lgeom endif zpool_LDADD += -lm $(LIBBLKID_LIBS) $(LIBUUID_LIBS) include $(top_srcdir)/config/CppCheck.am zpoolconfdir = $(sysconfdir)/zfs/zpool.d zpoolexecdir = $(zfsexecdir)/zpool.d EXTRA_DIST = zpool.d/README compatibility.d dist_zpoolexec_SCRIPTS = \ zpool.d/dm-deps \ zpool.d/enc \ zpool.d/encdev \ zpool.d/fault_led \ zpool.d/iostat \ zpool.d/iostat-1s \ zpool.d/iostat-10s \ zpool.d/label \ zpool.d/locate_led \ zpool.d/lsblk \ zpool.d/media \ zpool.d/model \ zpool.d/serial \ zpool.d/ses \ zpool.d/size \ zpool.d/slot \ zpool.d/smart \ zpool.d/smartx \ zpool.d/temp \ zpool.d/health \ zpool.d/r_proc \ zpool.d/w_proc \ zpool.d/r_ucor \ zpool.d/w_ucor \ zpool.d/nonmed \ zpool.d/defect \ zpool.d/hours_on \ zpool.d/realloc \ zpool.d/rep_ucor \ zpool.d/cmd_to \ zpool.d/pend_sec \ zpool.d/off_ucor \ zpool.d/ata_err \ zpool.d/nvme_err \ zpool.d/pwr_cyc \ zpool.d/upath \ zpool.d/vendor \ zpool.d/smart_test \ zpool.d/test_type \ zpool.d/test_status \ zpool.d/test_progress \ zpool.d/test_ended zpoolconfdefaults = \ dm-deps \ enc \ encdev \ fault_led \ iostat \ iostat-1s \ iostat-10s \ label \ locate_led \ lsblk \ media \ model \ serial \ ses \ size \ slot \ smart \ smartx \ temp \ health \ r_proc \ w_proc \ r_ucor \ w_ucor \ nonmed \ defect \ hours_on \ realloc \ rep_ucor \ cmd_to \ pend_sec \ off_ucor \ ata_err \ nvme_err \ pwr_cyc \ upath \ vendor \ smart_test \ test_type \ test_status \ test_progress \ test_ended zpoolcompatdir = $(pkgdatadir)/compatibility.d dist_zpoolcompat_DATA = \ compatibility.d/compat-2018 \ compatibility.d/compat-2019 \ compatibility.d/compat-2020 \ compatibility.d/compat-2021 \ compatibility.d/freebsd-11.0 \ compatibility.d/freebsd-11.2 \ compatibility.d/freebsd-11.3 \ compatibility.d/freenas-9.10.2 \ compatibility.d/grub2 \ compatibility.d/openzfsonosx-1.7.0 \ compatibility.d/openzfsonosx-1.8.1 \ compatibility.d/openzfsonosx-1.9.3 \ compatibility.d/openzfs-2.0-freebsd \ compatibility.d/openzfs-2.0-linux \ compatibility.d/openzfs-2.1-freebsd \ compatibility.d/openzfs-2.1-linux \ compatibility.d/zol-0.6.1 \ compatibility.d/zol-0.6.4 \ compatibility.d/zol-0.6.5 \ compatibility.d/zol-0.7 \ compatibility.d/zol-0.8 # canonical <- alias symbolic link pairs # eg: "2018" is a link to "compat-2018" zpoolcompatlinks = \ "compat-2018 2018" \ "compat-2019 2019" \ "compat-2020 2020" \ "compat-2021 2021" \ "freebsd-11.0 freebsd-11.1" \ "freebsd-11.0 freenas-11.0" \ "freebsd-11.2 freenas-11.2" \ "freebsd-11.3 freebsd-11.4" \ "freebsd-11.3 freebsd-12.0" \ "freebsd-11.3 freebsd-12.1" \ "freebsd-11.3 freebsd-12.2" \ "freebsd-11.3 freenas-11.3" \ "freenas-11.0 freenas-11.1" \ "openzfsonosx-1.9.3 openzfsonosx-1.9.4" \ "openzfs-2.0-freebsd truenas-12.0" \ "zol-0.7 ubuntu-18.04" \ "zol-0.8 ubuntu-20.04" install-data-hook: $(MKDIR_P) "$(DESTDIR)$(zpoolconfdir)" for f in $(zpoolconfdefaults); do \ test -f "$(DESTDIR)$(zpoolconfdir)/$${f}" -o \ -L "$(DESTDIR)$(zpoolconfdir)/$${f}" || \ ln -s "$(zpoolexecdir)/$${f}" "$(DESTDIR)$(zpoolconfdir)"; \ done for l in $(zpoolcompatlinks); do \ (cd "$(DESTDIR)$(zpoolcompatdir)"; ln -sf $${l} ); \ done diff --git a/cmd/zpool/zpool.d/dm-deps b/cmd/zpool/zpool.d/dm-deps index 42af6a8d63cd..44224787f096 100755 --- a/cmd/zpool/zpool.d/dm-deps +++ b/cmd/zpool/zpool.d/dm-deps @@ -1,27 +1,28 @@ #!/bin/sh # # Show device mapper dependent / underlying devices. This is useful for # looking up the /dev/sd* devices associated with a dm or multipath device. # if [ "$1" = "-h" ] ; then echo "Show device mapper dependent (underlying) devices." exit fi +# shellcheck disable=SC2154 dev="$VDEV_PATH" # If the VDEV path is a symlink, resolve it to a real device if [ -L "$dev" ] ; then dev=$(readlink "$dev") fi dev="${dev##*/}" val="" if [ -d "/sys/class/block/$dev/slaves" ] ; then # ls -C: output in columns, no newlines, two spaces (change to one) # shellcheck disable=SC2012 val=$(ls -C "/sys/class/block/$dev/slaves" | tr -s '[:space:]' ' ') fi echo "dm-deps=$val" diff --git a/cmd/zpool/zpool.d/iostat b/cmd/zpool/zpool.d/iostat index 19be475e9b27..95c459a3f0bf 100755 --- a/cmd/zpool/zpool.d/iostat +++ b/cmd/zpool/zpool.d/iostat @@ -1,77 +1,78 @@ #!/bin/sh # # Display most relevant iostat bandwidth/latency numbers. The output is # dependent on the name of the script/symlink used to call it. # helpstr=" iostat: Show iostat values since boot (summary page). iostat-1s: Do a single 1-second iostat sample and show values. iostat-10s: Do a single 10-second iostat sample and show values." script="${0##*/}" if [ "$1" = "-h" ] ; then echo "$helpstr" | grep "$script:" | tr -s '\t' | cut -f 2- exit fi if [ "$script" = "iostat-1s" ] ; then # Do a single one-second sample interval=1 # Don't show summary stats brief="yes" elif [ "$script" = "iostat-10s" ] ; then # Do a single ten-second sample interval=10 # Don't show summary stats brief="yes" fi +# shellcheck disable=SC2154 if [ -f "$VDEV_UPATH" ] ; then # We're a file-based vdev, iostat doesn't work on us. Do nothing. exit fi if [ "$(uname)" = "FreeBSD" ]; then out=$(iostat -dKx \ ${interval:+"-w $interval"} \ ${interval:+"-c 1"} \ "$VDEV_UPATH" | tail -n 2) else out=$(iostat -kx \ ${brief:+"-y"} \ ${interval:+"$interval"} \ ${interval:+"1"} \ "$VDEV_UPATH" | grep -v '^$' | tail -n 2) fi # Sample output (we want the last two lines): # # Linux 2.6.32-642.13.1.el6.x86_64 (centos68) 03/09/2017 _x86_64_ (6 CPU) # # avg-cpu: %user %nice %system %iowait %steal %idle # 0.00 0.00 0.00 0.00 0.00 100.00 # # Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util # sdb 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 # # Get the column names cols=$(echo "$out" | head -n 1) # Get the values and tab separate them to make them cut-able. vals=$(echo "$out" | tail -n 1 | tr -s '[:space:]' '\t') i=0 for col in $cols ; do i=$((i+1)) # Skip the first column since it's just the device name - if [ $i -eq 1 ]; then + if [ "$i" -eq 1 ]; then continue fi # Get i'th value val=$(echo "$vals" | cut -f "$i") echo "$col=$val" done diff --git a/cmd/zpool/zpool.d/lsblk b/cmd/zpool/zpool.d/lsblk index 919783a1c1bf..1ed1464431aa 100755 --- a/cmd/zpool/zpool.d/lsblk +++ b/cmd/zpool/zpool.d/lsblk @@ -1,83 +1,84 @@ #!/bin/sh # # Print some common lsblk values # # Any (lowercased) name symlinked to the lsblk script will be passed to lsblk # as one of its --output names. Here's a partial list of --output names # from the lsblk binary: # # Available columns (for --output): # NAME device name # KNAME internal kernel device name # MAJ:MIN major:minor device number # FSTYPE filesystem type # MOUNTPOINT where the device is mounted # LABEL filesystem LABEL # UUID filesystem UUID # RA read-ahead of the device # RO read-only device # RM removable device # MODEL device identifier # SIZE size of the device # STATE state of the device # OWNER user name # GROUP group name # MODE device node permissions # ALIGNMENT alignment offset # MIN-IO minimum I/O size # OPT-IO optimal I/O size # PHY-SEC physical sector size # LOG-SEC logical sector size # ROTA rotational device # SCHED I/O scheduler name # RQ-SIZE request queue size # TYPE device type # DISC-ALN discard alignment offset # DISC-GRAN discard granularity # DISC-MAX discard max bytes # DISC-ZERO discard zeroes data # # If the script is run as just 'lsblk' then print out disk size, vendor, # and model number. helpstr=" label: Show filesystem label. model: Show disk model number. size: Show the disk capacity. vendor: Show the disk vendor. lsblk: Show the disk size, vendor, and model number." script="${0##*/}" if [ "$1" = "-h" ] ; then echo "$helpstr" | grep "$script:" | tr -s '\t' | cut -f 2- exit fi if [ "$script" = "lsblk" ] ; then list="size vendor model" else list=$(echo "$script" | tr '[:upper:]' '[:lower:]') fi # Older versions of lsblk don't support all these values (like SERIAL). for i in $list ; do # Special case: Looking up the size of a file-based vdev can't # be done with lsblk. + # shellcheck disable=SC2154 if [ "$i" = "size" ] && [ -f "$VDEV_UPATH" ] ; then size=$(du -h --apparent-size "$VDEV_UPATH" | cut -f 1) echo "size=$size" continue fi val="" if val=$(eval "lsblk -dl -n -o $i $VDEV_UPATH 2>/dev/null") ; then # Remove leading/trailing whitespace from value val=$(echo "$val" | sed -e 's/^[[:space:]]*//' \ -e 's/[[:space:]]*$//') fi echo "$i=$val" done diff --git a/cmd/zpool/zpool.d/media b/cmd/zpool/zpool.d/media index 660f78b743fc..095ac86dc4d8 100755 --- a/cmd/zpool/zpool.d/media +++ b/cmd/zpool/zpool.d/media @@ -1,31 +1,33 @@ #!/bin/sh # # Print out the type of device # if [ "$1" = "-h" ] ; then echo "Show whether a vdev is a file, hdd, ssd, or iscsi." exit fi +# shellcheck disable=SC2154 if [ -b "$VDEV_UPATH" ]; then device="${VDEV_UPATH##*/}" read -r val 2>/dev/null < "/sys/block/$device/queue/rotational" case "$val" in 0) MEDIA="ssd" ;; 1) MEDIA="hdd" ;; + *) MEDIA="invalid" ;; esac vpd_pg83="/sys/block/$device/device/vpd_pg83" if [ -f "$vpd_pg83" ]; then if grep -q --binary "iqn." "$vpd_pg83"; then MEDIA="iscsi" fi fi else if [ -f "$VDEV_UPATH" ]; then MEDIA="file" fi fi echo "media=$MEDIA" diff --git a/cmd/zpool/zpool.d/ses b/cmd/zpool/zpool.d/ses index b51fe31894ab..638145c95d47 100755 --- a/cmd/zpool/zpool.d/ses +++ b/cmd/zpool/zpool.d/ses @@ -1,58 +1,61 @@ #!/bin/sh # # Print SCSI Enclosure Services (SES) info. The output is dependent on the name # of the script/symlink used to call it. # helpstr=" enc: Show disk enclosure w:x:y:z value. slot: Show disk slot number as reported by the enclosure. encdev: Show /dev/sg* device associated with the enclosure disk slot. fault_led: Show value of the disk enclosure slot fault LED. locate_led: Show value of the disk enclosure slot locate LED. ses: Show disk's enc, enc device, slot, and fault/locate LED values." script="${0##*/}" if [ "$1" = "-h" ] ; then echo "$helpstr" | grep "$script:" | tr -s '\t' | cut -f 2- exit fi if [ "$script" = "ses" ] ; then scripts='enc encdev slot fault_led locate_led' else scripts="$script" fi for i in $scripts ; do + # shellcheck disable=SC2154 if [ -z "$VDEV_ENC_SYSFS_PATH" ] ; then echo "$i=" continue fi val="" case $i in enc) val=$(ls "$VDEV_ENC_SYSFS_PATH/../../" 2>/dev/null) ;; slot) val=$(cat "$VDEV_ENC_SYSFS_PATH/slot" 2>/dev/null) ;; encdev) val=$(ls "$VDEV_ENC_SYSFS_PATH/../device/scsi_generic" 2>/dev/null) ;; fault_led) # JBODs fault LED is called 'fault', NVMe fault LED is called # 'attention'. if [ -f "$VDEV_ENC_SYSFS_PATH/fault" ] ; then val=$(cat "$VDEV_ENC_SYSFS_PATH/fault" 2>/dev/null) elif [ -f "$VDEV_ENC_SYSFS_PATH/attention" ] ; then val=$(cat "$VDEV_ENC_SYSFS_PATH/attention" 2>/dev/null) fi ;; locate_led) val=$(cat "$VDEV_ENC_SYSFS_PATH/locate" 2>/dev/null) ;; + *) + val=invalid + ;; esac echo "$i=$val" done - diff --git a/cmd/zpool/zpool.d/smart b/cmd/zpool/zpool.d/smart index b95256d75648..032491988c18 100755 --- a/cmd/zpool/zpool.d/smart +++ b/cmd/zpool/zpool.d/smart @@ -1,240 +1,241 @@ #!/bin/sh # # Show SMART stats # helpstr=" smart: Show SMART temperature and error stats (specific to drive type) smartx: Show SMART extended drive stats (specific to drive type). temp: Show SMART drive temperature in celsius (all drives). health: Show reported SMART status (all drives). r_proc: Show SMART read GBytes processed over drive lifetime (SAS). w_proc: Show SMART write GBytes processed over drive lifetime (SAS). r_ucor: Show SMART read uncorrectable errors (SAS). w_ucor: Show SMART write uncorrectable errors (SAS). nonmed: Show SMART non-medium errors (SAS). defect: Show SMART grown defect list (SAS). hours_on: Show number of hours drive powered on (all drives). realloc: Show SMART reallocated sectors count (ATA). rep_ucor: Show SMART reported uncorrectable count (ATA). cmd_to: Show SMART command timeout count (ATA). pend_sec: Show SMART current pending sector count (ATA). off_ucor: Show SMART offline uncorrectable errors (ATA). ata_err: Show SMART ATA errors (ATA). pwr_cyc: Show SMART power cycle count (ATA). serial: Show disk serial number. nvme_err: Show SMART NVMe errors (NVMe). smart_test: Show SMART self-test results summary. test_type: Show SMART self-test type (short, long... ). test_status: Show SMART self-test status. test_progress: Show SMART self-test percentage done. test_ended: Show when the last SMART self-test ended (if supported). " # Hack for developer testing # # If you set $samples to a directory containing smartctl output text files, # we will use them instead of running smartctl on the vdevs. This can be # useful if you want to test a bunch of different smartctl outputs. Also, if # $samples is set, and additional 'file' column is added to the zpool output # showing the filename. samples= # get_filename_from_dir DIR # # Look in directory DIR and return a filename from it. The filename returned # is chosen quasi-sequentially (based off our PID). This allows us to return # a different filename every time this script is invoked (which we do for each # vdev), without having to maintain state. get_filename_from_dir() { dir=$1 pid="$$" num_files=$(find "$dir" -maxdepth 1 -type f | wc -l) mod=$((pid % num_files)) i=0 find "$dir" -type f -printf '%f\n' | while read -r file ; do if [ "$mod" = "$i" ] ; then echo "$file" break fi i=$((i+1)) done } script="${0##*/}" if [ "$1" = "-h" ] ; then echo "$helpstr" | grep "$script:" | tr -s '\t' | cut -f 2- exit fi +# shellcheck disable=SC2154 if [ -b "$VDEV_UPATH" ] && PATH="/usr/sbin:$PATH" command -v smartctl > /dev/null || [ -n "$samples" ] ; then if [ -n "$samples" ] ; then # cat a smartctl output text file instead of running smartctl # on a vdev (only used for developer testing). file=$(get_filename_from_dir "$samples") echo "file=$file" raw_out=$(cat "$samples/$file") else raw_out=$(sudo smartctl -a "$VDEV_UPATH") fi # What kind of drive are we? Look for the right line in smartctl: # # SAS: # Transport protocol: SAS # # SATA: # ATA Version is: 8 # # NVMe: # SMART/Health Information (NVMe Log 0xnn, NSID 0xnn) # out=$(echo "$raw_out" | awk ' # SAS specific /read:/{print "rrd="$4"\nr_cor="$5"\nr_proc="$7"\nr_ucor="$8} /write:/{print "rwr="$4"\nw_cor="$5"\nw_proc="$7"\nw_ucor="$8} /Non-medium error count/{print "nonmed="$4} /Elements in grown defect list/{print "defect="$6} # SAS common /SAS/{type="sas"} /Drive Temperature:/{print "temp="$4} # Status can be a long string, substitute spaces for '_' /SMART Health Status:/{printf "health="; for(i=4;i<=NF-1;i++){printf "%s_", $i}; printf "%s\n", $i} /number of hours powered up/{print "hours_on="$7; hours_on=int($7)} /Serial number:/{print "serial="$3} # SATA specific /Reallocated_Sector_Ct/{print "realloc="$10} /Reported_Uncorrect/{print "rep_ucor="$10} /Command_Timeout/{print "cmd_to="$10} /Current_Pending_Sector/{print "pend_sec="$10} /Offline_Uncorrectable/{print "off_ucor="$10} /ATA Error Count:/{print "ata_err="$4} /Power_Cycle_Count/{print "pwr_cyc="$10} # SATA common /SATA/{type="sata"} /Temperature_Celsius/{print "temp="$10} /Airflow_Temperature_Cel/{print "temp="$10} /Current Temperature:/{print "temp="$3} /SMART overall-health self-assessment test result:/{print "health="$6} /Power_On_Hours/{print "hours_on="$10; hours_on=int($10)} /Serial Number:/{print "serial="$3} # NVMe common /NVMe/{type="nvme"} /Temperature:/{print "temp="$2} /SMART overall-health self-assessment test result:/{print "health="$6} /Power On Hours:/{gsub("[^0-9]","",$4); print "hours_on="$4} /Serial Number:/{print "serial="$3} /Power Cycles:/{print "pwr_cyc="$3} # NVMe specific /Media and Data Integrity Errors:/{print "nvme_err="$6} # SMART self-test info /Self-test execution status:/{progress=tolower($4)} # SAS /SMART Self-test log/{test_seen=1} # SAS /SMART Extended Self-test Log/{test_seen=1} # SATA /# 1/{ test_type=tolower($3"_"$4); # Status could be one word ("Completed") or multiple ("Completed: read # failure"). Look for the ":" to see if we need to grab more words. if ($5 ~ ":") status=tolower($5""$6"_"$7) else status=tolower($5) if (status=="self") status="running"; if (type == "sas") { hours=int($(NF-4)) } else { hours=int($(NF-1)) # SATA reports percent remaining, rather than percent done # Convert it to percent done. progress=(100-int($(NF-2)))"%" } # When we int()-ify "hours", it converts stuff like "NOW" and "-" into # 0. In those cases, set it to hours_on, so they will cancel out in # the "hours_ago" calculation later on. if (hours == 0) hours=hours_on if (test_seen) { print "test="hours_on print "test_type="test_type print "test_status="status print "test_progress="progress } # Not all drives report hours_on if (hours_on && hours) { total_hours_ago=(hours_on-hours) days_ago=int(total_hours_ago/24) hours_ago=(total_hours_ago % 24) if (days_ago != 0) ago_str=days_ago"d" if (hours_ago !=0) ago_str=ago_str""hours_ago"h" print "test_ended="ago_str } } END {print "type="type; ORS="\n"; print ""} '); fi type=$(echo "$out" | grep '^type=' | cut -d '=' -f 2) # If type is not set by now, either we don't have a block device # or smartctl failed. Either way, default to ATA and set $out to # nothing. if [ -z "$type" ]; then type="sata" out= fi case $script in smart) # Print temperature plus common predictors of drive failure if [ "$type" = "sas" ] ; then scripts="temp|health|r_ucor|w_ucor" elif [ "$type" = "sata" ] ; then scripts="temp|health|ata_err|realloc|rep_ucor|cmd_to|pend_sec|off_ucor" elif [ "$type" = "nvme" ] ; then scripts="temp|health|nvme_err" fi ;; smartx) # Print some other interesting stats if [ "$type" = "sas" ] ; then scripts="hours_on|defect|nonmed|r_proc|w_proc" elif [ "$type" = "sata" ] ; then scripts="hours_on|pwr_cyc" elif [ "$type" = "nvme" ] ; then scripts="hours_on|pwr_cyc" fi ;; smart_test) scripts="test_type|test_status|test_progress|test_ended" ;; *) scripts="$script" esac with_vals=$(echo "$out" | grep -E "$scripts") if [ -n "$with_vals" ]; then echo "$with_vals" without_vals=$(echo "$scripts" | tr '|' '\n' | grep -v -E "$(echo "$with_vals" | awk -F "=" '{print $1}')" | awk '{print $0"="}') else without_vals=$(echo "$scripts" | tr '|' '\n' | awk '{print $0"="}') fi if [ -n "$without_vals" ]; then echo "$without_vals" fi diff --git a/cmd/zpool/zpool.d/upath b/cmd/zpool/zpool.d/upath index 16a4327d4850..e37ee1b8cc2a 100755 --- a/cmd/zpool/zpool.d/upath +++ b/cmd/zpool/zpool.d/upath @@ -1,7 +1,8 @@ #!/bin/sh if [ "$1" = "-h" ] ; then echo "Show the underlying path for a device." exit fi +# shellcheck disable=SC2154 echo upath="$VDEV_UPATH" diff --git a/cmd/zvol_wait/Makefile.am b/cmd/zvol_wait/Makefile.am index 2e5bf3323389..ee66d51de96f 100644 --- a/cmd/zvol_wait/Makefile.am +++ b/cmd/zvol_wait/Makefile.am @@ -1,3 +1,5 @@ include $(top_srcdir)/config/Shellcheck.am dist_bin_SCRIPTS = zvol_wait + +SHELLCHECK_OPTS = --enable=all