summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGiuseppe Di Natale <[email protected]>2017-06-05 13:52:15 -0400
committerBrian Behlendorf <[email protected]>2017-06-05 10:52:15 -0700
commit099700d9dff46309cdd16f4c4331daddb70d8570 (patch)
tree13383ee2af7cc69b0f790b3c3f70fbc61ae074fb
parent92aceb2a7ee8c9367fdc901fed933f6f258173e0 (diff)
zpool iostat/status -c improvements
Users can now provide their own scripts to be run with 'zpool iostat/status -c'. User scripts should be placed in ~/.zpool.d to be included in zpool's default search path. Provide a script which can be used with 'zpool iostat|status -c' that will return the type of device (hdd, sdd, file). Provide a script to get various values from smartctl when using 'zpool iostat/status -c'. Allow users to define the ZPOOL_SCRIPTS_PATH environment variable which can be used to override the default 'zpool iostat/status -c' search path. Allow the ZPOOL_SCRIPTS_ENABLED environment variable to enable or disable 'zpool status/iostat -c' functionality. Use the new smart script to provide the serial command. Install /etc/sudoers.d/zfs file which contains the sudoer rule for smartctl as a sample. Allow 'zpool iostat/status -c' tests to run in tree. Reviewed-by: Tony Hutter <[email protected]> Reviewed-by: Brian Behlendorf <[email protected]> Signed-off-by: Giuseppe Di Natale <[email protected]> Closes #6121 Closes #6153
-rw-r--r--cmd/zpool/Makefile.am38
l---------cmd/zpool/zpool.d/ata_err1
l---------cmd/zpool/zpool.d/cmd_to1
l---------cmd/zpool/zpool.d/defect1
l---------cmd/zpool/zpool.d/health1
l---------cmd/zpool/zpool.d/hours_on1
-rwxr-xr-xcmd/zpool/zpool.d/lsblk7
-rwxr-xr-xcmd/zpool/zpool.d/media27
l---------cmd/zpool/zpool.d/nonmed1
l---------cmd/zpool/zpool.d/off_ucor1
l---------cmd/zpool/zpool.d/pend_sec1
l---------cmd/zpool/zpool.d/pwr_cyc1
l---------cmd/zpool/zpool.d/r_proc1
l---------cmd/zpool/zpool.d/r_ucor1
l---------cmd/zpool/zpool.d/realloc1
l---------cmd/zpool/zpool.d/rep_ucor1
l---------cmd/zpool/zpool.d/serial2
-rwxr-xr-xcmd/zpool/zpool.d/ses8
-rwxr-xr-xcmd/zpool/zpool.d/smart123
l---------cmd/zpool/zpool.d/smartx1
l---------cmd/zpool/zpool.d/temp1
l---------cmd/zpool/zpool.d/w_proc1
l---------cmd/zpool/zpool.d/w_ucor1
-rw-r--r--cmd/zpool/zpool_iter.c58
-rw-r--r--cmd/zpool/zpool_main.c66
-rw-r--r--cmd/zpool/zpool_util.h5
-rw-r--r--configure.ac1
-rw-r--r--etc/Makefile.am4
-rw-r--r--etc/sudoers.d/Makefile.am5
-rw-r--r--etc/sudoers.d/zfs8
-rw-r--r--man/man8/zpool.842
-rw-r--r--rpm/generic/zfs.spec.in2
-rwxr-xr-xscripts/zfs-helpers.sh6
-rw-r--r--tests/runfiles/linux.run7
-rw-r--r--tests/zfs-tests/tests/functional/cli_root/zpool_status/Makefile.am5
-rwxr-xr-xtests/zfs-tests/tests/functional/cli_root/zpool_status/zpool_status_-c_disable.ksh54
-rwxr-xr-xtests/zfs-tests/tests/functional/cli_root/zpool_status/zpool_status_-c_homedir.ksh76
-rwxr-xr-xtests/zfs-tests/tests/functional/cli_root/zpool_status/zpool_status_-c_searchpath.ksh88
-rw-r--r--tests/zfs-tests/tests/functional/cli_user/zpool_iostat/Makefile.am5
-rwxr-xr-xtests/zfs-tests/tests/functional/cli_user/zpool_iostat/zpool_iostat_-c_disable.ksh54
-rwxr-xr-xtests/zfs-tests/tests/functional/cli_user/zpool_iostat/zpool_iostat_-c_homedir.ksh76
-rwxr-xr-xtests/zfs-tests/tests/functional/cli_user/zpool_iostat/zpool_iostat_-c_searchpath.ksh88
-rw-r--r--zfs-script-config.sh.in1
43 files changed, 812 insertions, 61 deletions
diff --git a/cmd/zpool/Makefile.am b/cmd/zpool/Makefile.am
index f905fb73d..6eff1d143 100644
--- a/cmd/zpool/Makefile.am
+++ b/cmd/zpool/Makefile.am
@@ -36,12 +36,31 @@ dist_zpoolexec_SCRIPTS = \
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/slaves \
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/pwr_cyc \
zpool.d/upath \
zpool.d/vendor
@@ -55,12 +74,31 @@ zpoolconfdefaults = \
label \
locate_led \
lsblk \
+ media \
model \
serial \
ses \
size \
slaves \
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 \
+ pwr_cyc \
upath \
vendor
diff --git a/cmd/zpool/zpool.d/ata_err b/cmd/zpool/zpool.d/ata_err
new file mode 120000
index 000000000..94f22861f
--- /dev/null
+++ b/cmd/zpool/zpool.d/ata_err
@@ -0,0 +1 @@
+smart \ No newline at end of file
diff --git a/cmd/zpool/zpool.d/cmd_to b/cmd/zpool/zpool.d/cmd_to
new file mode 120000
index 000000000..94f22861f
--- /dev/null
+++ b/cmd/zpool/zpool.d/cmd_to
@@ -0,0 +1 @@
+smart \ No newline at end of file
diff --git a/cmd/zpool/zpool.d/defect b/cmd/zpool/zpool.d/defect
new file mode 120000
index 000000000..94f22861f
--- /dev/null
+++ b/cmd/zpool/zpool.d/defect
@@ -0,0 +1 @@
+smart \ No newline at end of file
diff --git a/cmd/zpool/zpool.d/health b/cmd/zpool/zpool.d/health
new file mode 120000
index 000000000..94f22861f
--- /dev/null
+++ b/cmd/zpool/zpool.d/health
@@ -0,0 +1 @@
+smart \ No newline at end of file
diff --git a/cmd/zpool/zpool.d/hours_on b/cmd/zpool/zpool.d/hours_on
new file mode 120000
index 000000000..94f22861f
--- /dev/null
+++ b/cmd/zpool/zpool.d/hours_on
@@ -0,0 +1 @@
+smart \ No newline at end of file
diff --git a/cmd/zpool/zpool.d/lsblk b/cmd/zpool/zpool.d/lsblk
index fc0394a3d..1cdef4049 100755
--- a/cmd/zpool/zpool.d/lsblk
+++ b/cmd/zpool/zpool.d/lsblk
@@ -38,16 +38,15 @@
# DISC-ZERO discard zeroes data
#
# If the script is run as just 'lsblk' then print out disk size, vendor,
-# model number and serial number.
+# and model number.
helpstr="
label: Show filesystem label.
model: Show disk model number.
-serial: Show disk serial number.
size: Show the disk capacity.
vendor: Show the disk vendor.
-lsblk: Show the disk size, vendor, model number, and serial number."
+lsblk: Show the disk size, vendor, and model number."
script=$(basename "$0")
@@ -57,7 +56,7 @@ if [ "$1" = "-h" ] ; then
fi
if [ "$script" = "lsblk" ] ; then
- list="size vendor model serial"
+ list="size vendor model"
else
list=$(echo "$script" | tr '[:upper:]' '[:lower:]')
fi
diff --git a/cmd/zpool/zpool.d/media b/cmd/zpool/zpool.d/media
new file mode 100755
index 000000000..05bc15918
--- /dev/null
+++ b/cmd/zpool/zpool.d/media
@@ -0,0 +1,27 @@
+#!/bin/sh
+#
+# Print out the type of device
+#
+
+if [ "$1" = "-h" ] ; then
+ echo "Show whether a vdev is a file, hdd, or ssd."
+ exit
+fi
+
+if [ -b "$VDEV_UPATH" ]; then
+ device=$(basename "$VDEV_UPATH")
+ val=$(cat "/sys/block/$device/queue/rotational" 2>/dev/null)
+ if [ "$val" = "0" ]; then
+ MEDIA="ssd"
+ fi
+
+ if [ "$val" = "1" ]; then
+ MEDIA="hdd"
+ fi
+else
+ if [ -f "$VDEV_UPATH" ]; then
+ MEDIA="file"
+ fi
+fi
+
+echo "media=$MEDIA"
diff --git a/cmd/zpool/zpool.d/nonmed b/cmd/zpool/zpool.d/nonmed
new file mode 120000
index 000000000..94f22861f
--- /dev/null
+++ b/cmd/zpool/zpool.d/nonmed
@@ -0,0 +1 @@
+smart \ No newline at end of file
diff --git a/cmd/zpool/zpool.d/off_ucor b/cmd/zpool/zpool.d/off_ucor
new file mode 120000
index 000000000..94f22861f
--- /dev/null
+++ b/cmd/zpool/zpool.d/off_ucor
@@ -0,0 +1 @@
+smart \ No newline at end of file
diff --git a/cmd/zpool/zpool.d/pend_sec b/cmd/zpool/zpool.d/pend_sec
new file mode 120000
index 000000000..94f22861f
--- /dev/null
+++ b/cmd/zpool/zpool.d/pend_sec
@@ -0,0 +1 @@
+smart \ No newline at end of file
diff --git a/cmd/zpool/zpool.d/pwr_cyc b/cmd/zpool/zpool.d/pwr_cyc
new file mode 120000
index 000000000..94f22861f
--- /dev/null
+++ b/cmd/zpool/zpool.d/pwr_cyc
@@ -0,0 +1 @@
+smart \ No newline at end of file
diff --git a/cmd/zpool/zpool.d/r_proc b/cmd/zpool/zpool.d/r_proc
new file mode 120000
index 000000000..94f22861f
--- /dev/null
+++ b/cmd/zpool/zpool.d/r_proc
@@ -0,0 +1 @@
+smart \ No newline at end of file
diff --git a/cmd/zpool/zpool.d/r_ucor b/cmd/zpool/zpool.d/r_ucor
new file mode 120000
index 000000000..94f22861f
--- /dev/null
+++ b/cmd/zpool/zpool.d/r_ucor
@@ -0,0 +1 @@
+smart \ No newline at end of file
diff --git a/cmd/zpool/zpool.d/realloc b/cmd/zpool/zpool.d/realloc
new file mode 120000
index 000000000..94f22861f
--- /dev/null
+++ b/cmd/zpool/zpool.d/realloc
@@ -0,0 +1 @@
+smart \ No newline at end of file
diff --git a/cmd/zpool/zpool.d/rep_ucor b/cmd/zpool/zpool.d/rep_ucor
new file mode 120000
index 000000000..94f22861f
--- /dev/null
+++ b/cmd/zpool/zpool.d/rep_ucor
@@ -0,0 +1 @@
+smart \ No newline at end of file
diff --git a/cmd/zpool/zpool.d/serial b/cmd/zpool/zpool.d/serial
index 7d1e766ad..94f22861f 120000
--- a/cmd/zpool/zpool.d/serial
+++ b/cmd/zpool/zpool.d/serial
@@ -1 +1 @@
-lsblk \ No newline at end of file
+smart \ No newline at end of file
diff --git a/cmd/zpool/zpool.d/ses b/cmd/zpool/zpool.d/ses
index 10d5dcfd3..f6b7520df 100755
--- a/cmd/zpool/zpool.d/ses
+++ b/cmd/zpool/zpool.d/ses
@@ -6,10 +6,10 @@
helpstr="
enc: Show disk enclosure w:x:y:z value.
slot: Show disk slot number as reported by the enclosure.
-encdev: Show the /dev/sg* device for the enclosure associated with the disk slot.
-fault_led: Show the value of the disk enclosure slot fault LED.
-locate_led: Show the value of the disk enclosure slot locate LED.
-ses: Show disk's enclosure, enclosure dev, slot number, and fault/locate LED values."
+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=$(basename "$0")
if [ "$1" = "-h" ] ; then
diff --git a/cmd/zpool/zpool.d/smart b/cmd/zpool/zpool.d/smart
new file mode 100755
index 000000000..3721f30ed
--- /dev/null
+++ b/cmd/zpool/zpool.d/smart
@@ -0,0 +1,123 @@
+#!/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.
+"
+
+script=$(basename "$0")
+
+if [ "$1" = "-h" ] ; then
+ echo "$helpstr" | grep "$script:" | tr -s '\t' | cut -f 2-
+ exit
+fi
+
+smartctl_path=$(which smartctl)
+
+if [ -b "$VDEV_UPATH" ] && [ -x "$smartctl_path" ]; then
+ raw_out=$(eval "sudo $smartctl_path -a $VDEV_UPATH")
+
+ # Are we a SAS or ATA drive? Look for the right line in smartctl:
+ #
+ # SAS:
+ # Transport protocol: SAS
+ #
+ # SATA:
+ # ATA Version is: 8
+ #
+ type=$(echo "$raw_out" | grep -m 1 -Eo '^ATA|SAS$')
+ 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
+/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}
+/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
+/Temperature_Celsius/{print "temp="$10}
+/SMART overall-health self-assessment test result:/{print "health="$6}
+/Power_On_Hours/{print "hours_on="$10}
+/Serial Number:/{print "serial="$3}
+
+END {ORS="\n"; print ""}
+');
+fi
+
+# 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="ATA"
+ 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" = "ATA" ] ; then
+ scripts="temp|health|ata_err|realloc|rep_ucor|cmd_to|pend_sec|off_ucor"
+ fi
+ ;;
+smartx)
+ # Print some other interesting stats
+ if [ "$type" = "SAS" ] ; then
+ scripts="hours_on|defect|nonmed|r_proc|w_proc"
+ elif [ "$type" = "ATA" ] ; then
+ scripts="hours_on|pwr_cyc"
+ fi
+ ;;
+*)
+ scripts="$script"
+esac
+
+with_vals=$(echo "$out" | grep -E "$scripts")
+if [ ! -z "$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 [ ! -z "$without_vals" ]; then
+ echo "$without_vals"
+fi
diff --git a/cmd/zpool/zpool.d/smartx b/cmd/zpool/zpool.d/smartx
new file mode 120000
index 000000000..94f22861f
--- /dev/null
+++ b/cmd/zpool/zpool.d/smartx
@@ -0,0 +1 @@
+smart \ No newline at end of file
diff --git a/cmd/zpool/zpool.d/temp b/cmd/zpool/zpool.d/temp
new file mode 120000
index 000000000..94f22861f
--- /dev/null
+++ b/cmd/zpool/zpool.d/temp
@@ -0,0 +1 @@
+smart \ No newline at end of file
diff --git a/cmd/zpool/zpool.d/w_proc b/cmd/zpool/zpool.d/w_proc
new file mode 120000
index 000000000..94f22861f
--- /dev/null
+++ b/cmd/zpool/zpool.d/w_proc
@@ -0,0 +1 @@
+smart \ No newline at end of file
diff --git a/cmd/zpool/zpool.d/w_ucor b/cmd/zpool/zpool.d/w_ucor
new file mode 120000
index 000000000..94f22861f
--- /dev/null
+++ b/cmd/zpool/zpool.d/w_ucor
@@ -0,0 +1 @@
+smart \ No newline at end of file
diff --git a/cmd/zpool/zpool_iter.c b/cmd/zpool/zpool_iter.c
index 94777f076..abb1b1798 100644
--- a/cmd/zpool/zpool_iter.c
+++ b/cmd/zpool/zpool_iter.c
@@ -521,28 +521,66 @@ out:
free(env[i]);
}
+/*
+ * Generate the search path for zpool iostat/status -c scripts.
+ * The string returned must be freed.
+ */
+char *
+zpool_get_cmd_search_path(void)
+{
+ const char *env;
+ char *sp = NULL;
+
+ env = getenv("ZPOOL_SCRIPTS_PATH");
+ if (env != NULL)
+ return (strdup(env));
+
+ env = getenv("HOME");
+ if (env != NULL) {
+ if (asprintf(&sp, "%s/.zpool.d:%s",
+ env, ZPOOL_SCRIPTS_DIR) != -1) {
+ return (sp);
+ }
+ }
+
+ if (asprintf(&sp, "%s", ZPOOL_SCRIPTS_DIR) != -1)
+ return (sp);
+
+ return (NULL);
+}
+
/* Thread function run for each vdev */
static void
vdev_run_cmd_thread(void *cb_cmd_data)
{
vdev_cmd_data_t *data = cb_cmd_data;
- const char *sep = ",";
- char *cmd = NULL, *cmddup, *rest;
- char fullpath[MAXPATHLEN];
+ char *cmd = NULL, *cmddup, *cmdrest;
cmddup = strdup(data->cmd);
if (cmddup == NULL)
return;
- rest = cmddup;
- while ((cmd = strtok_r(rest, sep, &rest))) {
- if (snprintf(fullpath, sizeof (fullpath), "%s/%s",
- ZPOOL_SCRIPTS_DIR, cmd) == -1)
+ cmdrest = cmddup;
+ while ((cmd = strtok_r(cmdrest, ",", &cmdrest))) {
+ char *dir = NULL, *sp, *sprest;
+ char fullpath[MAXPATHLEN];
+
+ sp = zpool_get_cmd_search_path();
+ if (sp == NULL)
continue;
- /* Does the script exist in our zpool scripts dir? */
- if (access(fullpath, X_OK) == 0)
- vdev_run_cmd(data, fullpath);
+ sprest = sp;
+ while ((dir = strtok_r(sprest, ":", &sprest))) {
+ if (snprintf(fullpath, sizeof (fullpath),
+ "%s/%s", dir, cmd) == -1)
+ continue;
+
+ if (access(fullpath, X_OK) == 0) {
+ vdev_run_cmd(data, fullpath);
+ break;
+ }
+ }
+ free(sp);
}
free(cmddup);
}
diff --git a/cmd/zpool/zpool_main.c b/cmd/zpool/zpool_main.c
index f99145da9..5fccd754b 100644
--- a/cmd/zpool/zpool_main.c
+++ b/cmd/zpool/zpool_main.c
@@ -4190,25 +4190,22 @@ print_zpool_script_help(char *name, char *path)
libzfs_free_str_array(lines, lines_cnt);
}
-
/*
- * Go though the list of all the zpool status/iostat -c scripts, run their
+ * Go though the zpool status/iostat -c scripts in the user's path, run their
* help option (-h), and print out the results.
*/
static void
-print_zpool_script_list(void)
+print_zpool_dir_scripts(char *dirpath)
{
DIR *dir;
struct dirent *ent;
char fullpath[MAXPATHLEN];
struct stat dir_stat;
- if ((dir = opendir(ZPOOL_SCRIPTS_DIR)) != NULL) {
- printf("\n");
+ if ((dir = opendir(dirpath)) != NULL) {
/* print all the files and directories within directory */
while ((ent = readdir(dir)) != NULL) {
- sprintf(fullpath, "%s/%s", ZPOOL_SCRIPTS_DIR,
- ent->d_name);
+ sprintf(fullpath, "%s/%s", dirpath, ent->d_name);
/* Print the scripts */
if (stat(fullpath, &dir_stat) == 0)
@@ -4217,15 +4214,34 @@ print_zpool_script_list(void)
print_zpool_script_help(ent->d_name,
fullpath);
}
- printf("\n");
closedir(dir);
- } else {
- fprintf(stderr, gettext("Can't open %s scripts dir\n"),
- ZPOOL_SCRIPTS_DIR);
}
}
/*
+ * Print out help text for all zpool status/iostat -c scripts.
+ */
+static void
+print_zpool_script_list(char *subcommand)
+{
+ char *dir, *sp;
+
+ printf(gettext("Available 'zpool %s -c' commands:\n"), subcommand);
+
+ sp = zpool_get_cmd_search_path();
+ if (sp == NULL)
+ return;
+
+ dir = strtok(sp, ":");
+ while (dir != NULL) {
+ print_zpool_dir_scripts(dir);
+ dir = strtok(NULL, ":");
+ }
+
+ free(sp);
+}
+
+/*
* zpool iostat [[-c [script1,script2,...]] [-lq]|[-rw]] [-ghHLpPvy] [-n name]
* [-T d|u] [[ pool ...]|[pool vdev ...]|[vdev ...]]
* [interval [count]]
@@ -4285,6 +4301,15 @@ zpool_do_iostat(int argc, char **argv)
gettext("Can't set -c flag twice\n"));
exit(1);
}
+
+ if (getenv("ZPOOL_SCRIPTS_ENABLED") != NULL &&
+ !libzfs_envvar_is_set("ZPOOL_SCRIPTS_ENABLED")) {
+ fprintf(stderr, gettext(
+ "Can't run -c, disabled by "
+ "ZPOOL_SCRIPTS_ENABLED.\n"));
+ exit(1);
+ }
+
if ((getuid() <= 0 || geteuid() <= 0) &&
!libzfs_envvar_is_set("ZPOOL_SCRIPTS_AS_ROOT")) {
fprintf(stderr, gettext(
@@ -4336,10 +4361,7 @@ zpool_do_iostat(int argc, char **argv)
break;
case '?':
if (optopt == 'c') {
- fprintf(stderr, gettext(
- "Current scripts in %s:\n"),
- ZPOOL_SCRIPTS_DIR);
- print_zpool_script_list();
+ print_zpool_script_list("iostat");
exit(0);
} else {
fprintf(stderr,
@@ -6427,6 +6449,15 @@ zpool_do_status(int argc, char **argv)
gettext("Can't set -c flag twice\n"));
exit(1);
}
+
+ if (getenv("ZPOOL_SCRIPTS_ENABLED") != NULL &&
+ !libzfs_envvar_is_set("ZPOOL_SCRIPTS_ENABLED")) {
+ fprintf(stderr, gettext(
+ "Can't run -c, disabled by "
+ "ZPOOL_SCRIPTS_ENABLED.\n"));
+ exit(1);
+ }
+
if ((getuid() <= 0 || geteuid() <= 0) &&
!libzfs_envvar_is_set("ZPOOL_SCRIPTS_AS_ROOT")) {
fprintf(stderr, gettext(
@@ -6459,10 +6490,7 @@ zpool_do_status(int argc, char **argv)
break;
case '?':
if (optopt == 'c') {
- fprintf(stderr, gettext(
- "Current scripts in %s:\n"),
- ZPOOL_SCRIPTS_DIR);
- print_zpool_script_list();
+ print_zpool_script_list("status");
exit(0);
} else {
fprintf(stderr,
diff --git a/cmd/zpool/zpool_util.h b/cmd/zpool/zpool_util.h
index ab7662cfa..aef2cff27 100644
--- a/cmd/zpool/zpool_util.h
+++ b/cmd/zpool/zpool_util.h
@@ -45,6 +45,11 @@ uint64_t array64_max(uint64_t array[], unsigned int len);
int isnumber(char *str);
/*
+ * Misc utility functions
+ */
+char *zpool_get_cmd_search_path(void);
+
+/*
* Virtual device functions
*/
diff --git a/configure.ac b/configure.ac
index 06b6662fd..c52d72e3d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -65,6 +65,7 @@ AC_CONFIG_FILES([
etc/zfs/Makefile
etc/systemd/Makefile
etc/systemd/system/Makefile
+ etc/sudoers.d/Makefile
etc/modules-load.d/Makefile
man/Makefile
man/man1/Makefile
diff --git a/etc/Makefile.am b/etc/Makefile.am
index a62678b4e..28b955106 100644
--- a/etc/Makefile.am
+++ b/etc/Makefile.am
@@ -1,2 +1,2 @@
-SUBDIRS = zfs $(ZFS_INIT_SYSTEMD) $(ZFS_INIT_SYSV) $(ZFS_MODULE_LOAD)
-DIST_SUBDIRS = init.d zfs systemd modules-load.d
+SUBDIRS = zfs sudoers.d $(ZFS_INIT_SYSTEMD) $(ZFS_INIT_SYSV) $(ZFS_MODULE_LOAD)
+DIST_SUBDIRS = init.d zfs systemd modules-load.d sudoers.d
diff --git a/etc/sudoers.d/Makefile.am b/etc/sudoers.d/Makefile.am
new file mode 100644
index 000000000..ca9186a7e
--- /dev/null
+++ b/etc/sudoers.d/Makefile.am
@@ -0,0 +1,5 @@
+sudoersddir = $(sysconfdir)/sudoers.d
+sudoersd_DATA = zfs
+
+EXTRA_DIST = \
+ $(top_srcdir)/etc/sudoers.d/zfs
diff --git a/etc/sudoers.d/zfs b/etc/sudoers.d/zfs
new file mode 100644
index 000000000..f66ebad21
--- /dev/null
+++ b/etc/sudoers.d/zfs
@@ -0,0 +1,8 @@
+##
+## Allow any user to run `zpool iostat/status -c smart` in order
+## to read basic SMART health statistics for a pool.
+##
+## CAUTION: Any syntax error introduced here will break sudo.
+##
+
+# ALL ALL = (root) NOPASSWD: /usr/sbin/smartctl -a /dev/[hsv]d[a-z0-9]*
diff --git a/man/man8/zpool.8 b/man/man8/zpool.8
index ff3db440d..190b9dfc0 100644
--- a/man/man8/zpool.8
+++ b/man/man8/zpool.8
@@ -1550,14 +1550,13 @@ Run a script (or scripts) on each vdev and include the output in zpool iostat
.sp
The \fB-c\fR option allows you to run script(s) for each vdev and display the
output in zpool iostat. For security reasons, a user can only execute scripts
-found in the /<etc>/zfs/zpool.d directory as an unprivileged user. However, a
-privileged user can run \fB-c\fR if they have the ZPOOL_SCRIPTS_AS_ROOT
-environment variable set. If a script requires the use of a privileged
-command (like smartctl) then it's recommended you allow the user access to it in
-/etc/sudoers. For example, to allow user "zfsuser" access to "smartctl -a", add
-the following to /etc/sudoers:
-
-zfsuser ALL=NOPASSWD: /usr/sbin/smartctl -a /dev/sd[a-z]*, NOEXEC: /usr/sbin/smartctl -a /dev/sd[a-z]*`
+as an unprivileged user. By default, a user may run a script from ~/.zpool.d
+or /etc/zfs/zpool.d. The default search path can be overriden by setting
+the \fBZPOOL_SCRIPTS_PATH\fR environment variable. A privileged user can run
+\fB-c\fR if they have the \fBZPOOL_SCRIPTS_AS_ROOT\fR environment variable set.
+If a script requires the use of a privileged command (like \fBsmartctl(8)\fR)
+then it's recommended you allow the user access to it in /etc/sudoers or add
+the user to the /etc/sudoers.d/zfs file.
If \fB-c\fR is passed without a script name, it prints a list of all scripts.
\fB-c\fR also sets verbose mode (\fB-v\fR).
@@ -2148,15 +2147,14 @@ If a scrub or resilver is in progress, this command reports the percentage done
Run a script (or scripts) on each vdev and include the output in zpool status
.sp
The \fB-c\fR option allows you to run script(s) for each vdev and display the
-output in zpool iostat. For security reasons, a user can only execute scripts
-found in the /<etc>/zfs/zpool.d directory as an unprivileged user. However, a
-privileged user can run \fB-c\fR if they have the ZPOOL_SCRIPTS_AS_ROOT
-environment variable set. If a script requires the use of a privileged
-command (like smartctl) then it's recommended you allow the user access to it in
-/etc/sudoers. For example, to allow user "zfsuser" access to "smartctl -a", add
-the following to /etc/sudoers:
-
-zfsuser ALL=NOPASSWD: /usr/sbin/smartctl -a /dev/sd[a-z]*, NOEXEC: /usr/sbin/smartctl -a /dev/sd[a-z]*`
+output in zpool status. For security reasons, a user can only execute scripts
+as an unprivileged user. By default, a user may run a script from ~/.zpool.d
+or /etc/zfs/zpool.d. The default search path can be overriden by setting
+the \fBZPOOL_SCRIPTS_PATH\fR environment variable. A privileged user can run
+\fB-c\fR if they have the \fBZPOOL_SCRIPTS_AS_ROOT\fR environment variable set.
+If a script requires the use of a privileged command (like \fBsmartctl(8)\fR)
+then it's recommended you allow the user access to it in /etc/sudoers or add
+the user to the /etc/sudoers.d/zfs file.
If \fB-c\fR is passed without a script name, it prints a list of all scripts.
@@ -2727,6 +2725,16 @@ them on \fBzpool create\fR or \fBzpool add\fR by setting ZFS_VDEV_DEVID_OPT_OUT.
.B "ZPOOL_SCRIPTS_AS_ROOT"
Allow a privilaged user to run the \fBzpool status/iostat\fR with the \fB-c\fR
option. Normally, only unprivilaged users are allowed to run \fB-c\fR.
+.TP
+.B "ZPOOL_SCRIPTS_PATH"
+The search path for scripts when running \fBzpool status/iostat\fR with the \fB-c\fR
+option. This is a colon-separated list of directories and overrides the default
+~/.zpool.d and /etc/zfs/zpool.d search paths.
+.TP
+.B "ZPOOL_SCRIPTS_ENABLED"
+Allow a user to run \fBzpool status/iostat\fR with the \fB-c\fR option. If
+ZPOOL_SCRIPTS_ENABLED is not set, it is assumed that the user is allowed to
+run \fBzpool status/iostat -c\fR.
.SH SEE ALSO
.sp
diff --git a/rpm/generic/zfs.spec.in b/rpm/generic/zfs.spec.in
index 2ad4b693c..c62d76346 100644
--- a/rpm/generic/zfs.spec.in
+++ b/rpm/generic/zfs.spec.in
@@ -181,6 +181,7 @@ Requires: bc
Requires: ksh
Requires: fio
Requires: acl
+Requires: sudo
Requires: sysstat
%description test
@@ -290,6 +291,7 @@ exit 0
%config(noreplace) %{_initconfdir}/zfs
%endif
%config(noreplace) %{_sysconfdir}/%{name}
+%attr(440, root, root) %config(noreplace) %{_sysconfdir}/sudoers.d/*
%files -n libzpool2
%{_libdir}/libzpool.so.*
diff --git a/scripts/zfs-helpers.sh b/scripts/zfs-helpers.sh
index 0354cc0ed..d2da575f4 100755
--- a/scripts/zfs-helpers.sh
+++ b/scripts/zfs-helpers.sh
@@ -14,6 +14,7 @@
# --with-mounthelperdir=DIR install mount.zfs in dir [/sbin]
# --with-udevdir=DIR install udev helpers [default=check]
# --with-udevruledir=DIR install udev rules [default=UDEVDIR/rules.d]
+# --sysconfdir=DIR install zfs configuration files [PREFIX/etc]
#
basedir="$(dirname $0)"
@@ -96,6 +97,7 @@ if [ "$VERBOSE" ]; then
echo "udevdir: $udevdir"
echo "udevruledir: $udevruledir"
echo "mounthelperdir: $mounthelperdir"
+ echo "sysconfdir: $sysconfdir"
echo "DRYRUN: $DRYRUN"
echo
fi
@@ -114,6 +116,7 @@ install() {
msg "ln -s $src $dst"
if [ ! "$DRYRUN" ]; then
+ mkdir -p $(dirname $dst) &>/dev/null
ln -s $src $dst
fi
fi
@@ -125,6 +128,7 @@ remove() {
if [ -h $dst ]; then
msg "rm $dst"
rm $dst
+ rmdir $(dirname $dst) &>/dev/null
fi
}
@@ -136,6 +140,7 @@ if [ ${INSTALL} ]; then
install $UDEVRULEDIR/60-zvol.rules $udevruledir/60-zvol.rules
install $UDEVRULEDIR/69-vdev.rules $udevruledir/69-vdev.rules
install $UDEVRULEDIR/90-zfs.rules $udevruledir/90-zfs.rules
+ install $CMDDIR/zpool/zpool.d $sysconfdir/zfs/zpool.d
else
remove $mounthelperdir/mount.zfs
remove $mounthelperdir/fsck.zfs
@@ -144,6 +149,7 @@ else
remove $udevruledir/60-zvol.rules
remove $udevruledir/69-vdev.rules
remove $udevruledir/90-zfs.rules
+ remove $sysconfdir/zfs/zpool.d
fi
exit 0
diff --git a/tests/runfiles/linux.run b/tests/runfiles/linux.run
index 674684f1f..115b9b025 100644
--- a/tests/runfiles/linux.run
+++ b/tests/runfiles/linux.run
@@ -279,7 +279,9 @@ pre =
post =
[tests/functional/cli_root/zpool_status]
-tests = ['zpool_status_001_pos', 'zpool_status_002_pos','zpool_status_003_pos']
+tests = ['zpool_status_001_pos', 'zpool_status_002_pos','zpool_status_003_pos',
+ 'zpool_status_-c_disable', 'zpool_status_-c_homedir',
+ 'zpool_status_-c_searchpath']
user =
[tests/functional/cli_root/zpool_sync]
@@ -318,7 +320,8 @@ user =
[tests/functional/cli_user/zpool_iostat]
tests = ['zpool_iostat_001_neg', 'zpool_iostat_002_pos',
'zpool_iostat_003_neg', 'zpool_iostat_004_pos',
- 'zpool_iostat_005_pos']
+ 'zpool_iostat_005_pos', 'zpool_iostat_-c_disable',
+ 'zpool_iostat_-c_homedir', 'zpool_iostat_-c_searchpath']
user =
[tests/functional/cli_user/zpool_list]
diff --git a/tests/zfs-tests/tests/functional/cli_root/zpool_status/Makefile.am b/tests/zfs-tests/tests/functional/cli_root/zpool_status/Makefile.am
index 747ec1dfa..aab4de0e7 100644
--- a/tests/zfs-tests/tests/functional/cli_root/zpool_status/Makefile.am
+++ b/tests/zfs-tests/tests/functional/cli_root/zpool_status/Makefile.am
@@ -4,4 +4,7 @@ dist_pkgdata_SCRIPTS = \
cleanup.ksh \
zpool_status_001_pos.ksh \
zpool_status_002_pos.ksh \
- zpool_status_003_pos.ksh
+ zpool_status_003_pos.ksh \
+ zpool_status_-c_disable.ksh \
+ zpool_status_-c_homedir.ksh \
+ zpool_status_-c_searchpath.ksh
diff --git a/tests/zfs-tests/tests/functional/cli_root/zpool_status/zpool_status_-c_disable.ksh b/tests/zfs-tests/tests/functional/cli_root/zpool_status/zpool_status_-c_disable.ksh
new file mode 100755
index 000000000..c8105fb4a
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/cli_root/zpool_status/zpool_status_-c_disable.ksh
@@ -0,0 +1,54 @@
+#!/bin/ksh -p
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright (c) 2017 by Lawrence Livermore National Security, LLC.
+#
+
+# DESCRIPTION:
+# Verify zpool status command mode (-c) respects ZPOOL_SCRIPTS_ENABLED.
+#
+# STRATEGY:
+# 1. Set ZPOOL_SCRIPTS_ENABLED to 0, disabling zpool status -c
+# 2. zpool status -c must not run successfully
+# 3. Set ZPOOL_SCRIPTS_ENABLED to 1, enabling zpool status -c
+# 4. zpool status -c must run successfully
+# 5. Unset ZPOOL_SCRIPTS_ENABLED, enabling zpool status -c
+# 6. zpool status -c must run successfully
+
+. $STF_SUITE/include/libtest.shlib
+. $STF_SUITE/include/zpool_script.shlib
+
+verify_runnable "both"
+
+log_assert "zpool status -c properly handles ZPOOL_SCRIPTS_ENABLED"
+
+export ZPOOL_SCRIPTS_ENABLED=0
+log_mustnot zpool status -c media
+
+export ZPOOL_SCRIPTS_ENABLED=1
+log_must zpool status -c media
+
+unset ZPOOL_SCRIPTS_ENABLED
+log_must zpool status -c media
+
+log_pass "zpool status -c properly handles ZPOOL_SCRIPTS_ENABLED passed"
diff --git a/tests/zfs-tests/tests/functional/cli_root/zpool_status/zpool_status_-c_homedir.ksh b/tests/zfs-tests/tests/functional/cli_root/zpool_status/zpool_status_-c_homedir.ksh
new file mode 100755
index 000000000..4cc3deb6d
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/cli_root/zpool_status/zpool_status_-c_homedir.ksh
@@ -0,0 +1,76 @@
+#!/bin/ksh -p
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright (c) 2017 by Lawrence Livermore National Security, LLC.
+#
+
+# DESCRIPTION:
+# Verify zpool status command mode (-c) works with scripts in user's
+# home directory.
+#
+# STRATEGY:
+# 1. Change HOME to /var/tmp
+# 2. Make a simple script that echos a key value pair
+# in /var/tmp/.zpool.d
+# 3. Make sure it can be run with -c
+# 4. Remove the script we created
+
+. $STF_SUITE/include/libtest.shlib
+. $STF_SUITE/include/zpool_script.shlib
+
+verify_runnable "both"
+
+# In tree testing sets this variable, we need to unset it
+# to restore zpool's search path.
+unset ZPOOL_SCRIPTS_PATH
+
+# change HOME
+export HOME="$TEST_BASE_DIR"
+typeset USER_SCRIPT_FULL="$HOME/.zpool.d/userscript"
+
+function cleanup
+{
+ log_must rm -rf "$HOME/.zpool.d"
+}
+
+log_assert "zpool status -c can run scripts from ~/.zpool.d"
+
+if [ -e "$USER_SCRIPT_FULL" ]; then
+ log_fail "$USER_SCRIPT_FULL already exists."
+fi
+
+log_onexit cleanup
+
+# create simple script
+log_must mkdir -p "$HOME/.zpool.d"
+cat > "$USER_SCRIPT_FULL" << EOF
+#!/bin/sh
+echo "USRCOL=USRVAL"
+EOF
+log_must chmod +x "$USER_SCRIPT_FULL"
+
+# test that we can run the script
+typeset USER_SCRIPT=$(basename "$USER_SCRIPT_FULL")
+test_zpool_script "$USER_SCRIPT" "$TESTPOOL" "zpool status -P -c"
+
+log_pass "zpool status -c can run scripts from ~/.zpool.d passed"
diff --git a/tests/zfs-tests/tests/functional/cli_root/zpool_status/zpool_status_-c_searchpath.ksh b/tests/zfs-tests/tests/functional/cli_root/zpool_status/zpool_status_-c_searchpath.ksh
new file mode 100755
index 000000000..a075b9a0c
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/cli_root/zpool_status/zpool_status_-c_searchpath.ksh
@@ -0,0 +1,88 @@
+#!/bin/ksh -p
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright (c) 2017 by Lawrence Livermore National Security, LLC.
+#
+
+# DESCRIPTION:
+# Verify zpool status command mode (-c) works with ZPOOL_SCRIPTS_PATH
+# defined.
+#
+# STRATEGY:
+# 1. Set ZPOOL_SCRIPTS_PATH to contain a couple of non-default dirs
+# 2. Make a simple script that echos a key value pair in each dir
+# 3. Make sure scripts can be run with -c
+# 4. Remove the scripts we created
+
+. $STF_SUITE/include/libtest.shlib
+. $STF_SUITE/include/zpool_script.shlib
+
+verify_runnable "both"
+
+typeset SCRIPT_1="$TEST_BASE_DIR/scripts1/test1"
+typeset SCRIPT_2="$TEST_BASE_DIR/scripts2/test2"
+
+function cleanup
+{
+ log_must rm -rf $(dirname "$SCRIPT_1")
+ log_must rm -rf $(dirname "$SCRIPT_2")
+}
+
+log_assert "zpool status -c can run scripts from custom search path"
+
+if [ -e "$SCRIPT_1" ]; then
+ log_fail "$SCRIPT_1 already exists."
+fi
+
+if [ -e "$SCRIPT_2" ]; then
+ log_fail "$SCRIPT_2 already exists."
+fi
+
+log_onexit cleanup
+
+# change zpool status search path
+export ZPOOL_SCRIPTS_PATH="$(dirname $SCRIPT_1):$(dirname $SCRIPT_2)"
+
+# create simple script in each dir
+log_must mkdir -p $(dirname "$SCRIPT_1")
+cat > "$SCRIPT_1" << EOF
+#!/bin/sh
+echo "USRCOL1=USRVAL1"
+EOF
+log_must chmod +x "$SCRIPT_1"
+
+log_must mkdir -p $(dirname "$SCRIPT_2")
+cat > "$SCRIPT_2" << EOF
+#!/bin/sh
+echo "USRCOL2=USRVAL2"
+EOF
+log_must chmod +x "$SCRIPT_2"
+
+# test that we can run the scripts
+typeset CMD_1=$(basename "$SCRIPT_1")
+typeset CMD_2=$(basename "$SCRIPT_2")
+test_zpool_script "$CMD_1" "$TESTPOOL" "zpool status -P -c"
+test_zpool_script "$CMD_2" "$TESTPOOL" "zpool status -P -c"
+test_zpool_script "$CMD_2,$CMD_1" "$TESTPOOL" "zpool status -P -c"
+
+log_pass "zpool status -c can run scripts from custom search path passed"
diff --git a/tests/zfs-tests/tests/functional/cli_user/zpool_iostat/Makefile.am b/tests/zfs-tests/tests/functional/cli_user/zpool_iostat/Makefile.am
index cadf9439a..5ee30eafc 100644
--- a/tests/zfs-tests/tests/functional/cli_user/zpool_iostat/Makefile.am
+++ b/tests/zfs-tests/tests/functional/cli_user/zpool_iostat/Makefile.am
@@ -6,4 +6,7 @@ dist_pkgdata_SCRIPTS = \
zpool_iostat_002_pos.ksh \
zpool_iostat_003_neg.ksh \
zpool_iostat_004_pos.ksh \
- zpool_iostat_005_pos.ksh
+ zpool_iostat_005_pos.ksh \
+ zpool_iostat_-c_disable.ksh \
+ zpool_iostat_-c_searchpath.ksh \
+ zpool_iostat_-c_homedir.ksh
diff --git a/tests/zfs-tests/tests/functional/cli_user/zpool_iostat/zpool_iostat_-c_disable.ksh b/tests/zfs-tests/tests/functional/cli_user/zpool_iostat/zpool_iostat_-c_disable.ksh
new file mode 100755
index 000000000..0d64eb0ec
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/cli_user/zpool_iostat/zpool_iostat_-c_disable.ksh
@@ -0,0 +1,54 @@
+#!/bin/ksh -p
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright (c) 2017 by Lawrence Livermore National Security, LLC.
+#
+
+# DESCRIPTION:
+# Verify zpool iostat command mode (-c) respects ZPOOL_SCRIPTS_ENABLED.
+#
+# STRATEGY:
+# 1. Set ZPOOL_SCRIPTS_ENABLED to 0, disabling zpool iostat -c
+# 2. zpool iostat -c must not run successfully
+# 3. Set ZPOOL_SCRIPTS_ENABLED to 1, enabling zpool iostat -c
+# 4. zpool iostat -c must run successfully
+# 5. Unset ZPOOL_SCRIPTS_ENABLED, enabling zpool iostat -c
+# 6. zpool iostat -c must run successfully
+
+. $STF_SUITE/include/libtest.shlib
+. $STF_SUITE/include/zpool_script.shlib
+
+verify_runnable "both"
+
+log_assert "zpool iostat -c properly handles ZPOOL_SCRIPTS_ENABLED"
+
+export ZPOOL_SCRIPTS_ENABLED=0
+log_mustnot zpool iostat -c media
+
+export ZPOOL_SCRIPTS_ENABLED=1
+log_must zpool iostat -c media
+
+unset ZPOOL_SCRIPTS_ENABLED
+log_must zpool iostat -c media
+
+log_pass "zpool iostat -c properly handles ZPOOL_SCRIPTS_ENABLED passed"
diff --git a/tests/zfs-tests/tests/functional/cli_user/zpool_iostat/zpool_iostat_-c_homedir.ksh b/tests/zfs-tests/tests/functional/cli_user/zpool_iostat/zpool_iostat_-c_homedir.ksh
new file mode 100755
index 000000000..5cb50fde6
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/cli_user/zpool_iostat/zpool_iostat_-c_homedir.ksh
@@ -0,0 +1,76 @@
+#!/bin/ksh -p
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright (c) 2017 by Lawrence Livermore National Security, LLC.
+#
+
+# DESCRIPTION:
+# Verify zpool iostat command mode (-c) works with scripts in user's
+# home directory.
+#
+# STRATEGY:
+# 1. Change HOME to /var/tmp
+# 2. Make a simple script that echos a key value pair
+# in /var/tmp/.zpool.d
+# 3. Make sure it can be run with -c
+# 4. Remove the script we created
+
+. $STF_SUITE/include/libtest.shlib
+. $STF_SUITE/include/zpool_script.shlib
+
+verify_runnable "both"
+
+# In tree testing sets this variable, we need to unset it
+# to restore zpool's search path.
+unset ZPOOL_SCRIPTS_PATH
+
+# change HOME
+export HOME="$TEST_BASE_DIR"
+typeset USER_SCRIPT_FULL="$HOME/.zpool.d/userscript"
+
+function cleanup
+{
+ log_must rm -rf "$HOME/.zpool.d"
+}
+
+log_assert "zpool iostat -c can run scripts from ~/.zpool.d"
+
+if [ -e "$USER_SCRIPT_FULL" ]; then
+ log_fail "$USER_SCRIPT_FULL already exists."
+fi
+
+log_onexit cleanup
+
+# create simple script
+log_must mkdir -p "$HOME/.zpool.d"
+cat > "$USER_SCRIPT_FULL" << EOF
+#!/bin/sh
+echo "USRCOL=USRVAL"
+EOF
+log_must chmod +x "$USER_SCRIPT_FULL"
+
+# test that we can run the script
+typeset USER_SCRIPT=$(basename "$USER_SCRIPT_FULL")
+test_zpool_script "$USER_SCRIPT" "$TESTPOOL" "zpool iostat -P -c"
+
+log_pass "zpool iostat -c can run scripts from ~/.zpool.d passed"
diff --git a/tests/zfs-tests/tests/functional/cli_user/zpool_iostat/zpool_iostat_-c_searchpath.ksh b/tests/zfs-tests/tests/functional/cli_user/zpool_iostat/zpool_iostat_-c_searchpath.ksh
new file mode 100755
index 000000000..1197ea2d1
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/cli_user/zpool_iostat/zpool_iostat_-c_searchpath.ksh
@@ -0,0 +1,88 @@
+#!/bin/ksh -p
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright (c) 2017 by Lawrence Livermore National Security, LLC.
+#
+
+# DESCRIPTION:
+# Verify zpool iostat command mode (-c) works with ZPOOL_SCRIPTS_PATH
+# defined.
+#
+# STRATEGY:
+# 1. Set ZPOOL_SCRIPTS_PATH to contain a couple of non-default dirs
+# 2. Make a simple script that echos a key value pair in each dir
+# 3. Make sure scripts can be run with -c
+# 4. Remove the scripts we created
+
+. $STF_SUITE/include/libtest.shlib
+. $STF_SUITE/include/zpool_script.shlib
+
+verify_runnable "both"
+
+typeset SCRIPT_1="$TEST_BASE_DIR/scripts1/test1"
+typeset SCRIPT_2="$TEST_BASE_DIR/scripts2/test2"
+
+function cleanup
+{
+ log_must rm -rf $(dirname "$SCRIPT_1")
+ log_must rm -rf $(dirname "$SCRIPT_2")
+}
+
+log_assert "zpool iostat -c can run scripts from custom search path"
+
+if [ -e "$SCRIPT_1" ]; then
+ log_fail "$SCRIPT_1 already exists."
+fi
+
+if [ -e "$SCRIPT_2" ]; then
+ log_fail "$SCRIPT_2 already exists."
+fi
+
+log_onexit cleanup
+
+# change zpool iostat search path
+export ZPOOL_SCRIPTS_PATH="$(dirname $SCRIPT_1):$(dirname $SCRIPT_2)"
+
+# create simple script in each dir
+log_must mkdir -p $(dirname "$SCRIPT_1")
+cat > "$SCRIPT_1" << EOF
+#!/bin/sh
+echo "USRCOL1=USRVAL1"
+EOF
+log_must chmod +x "$SCRIPT_1"
+
+log_must mkdir -p $(dirname "$SCRIPT_2")
+cat > "$SCRIPT_2" << EOF
+#!/bin/sh
+echo "USRCOL2=USRVAL2"
+EOF
+log_must chmod +x "$SCRIPT_2"
+
+# test that we can run the scripts
+typeset CMD_1=$(basename "$SCRIPT_1")
+typeset CMD_2=$(basename "$SCRIPT_2")
+test_zpool_script "$CMD_1" "$TESTPOOL" "zpool iostat -P -c"
+test_zpool_script "$CMD_2" "$TESTPOOL" "zpool iostat -P -c"
+test_zpool_script "$CMD_2,$CMD_1" "$TESTPOOL" "zpool iostat -P -c"
+
+log_pass "zpool iostat -c can run scripts from custom search path passed"
diff --git a/zfs-script-config.sh.in b/zfs-script-config.sh.in
index 593b4d00e..f1006b3f7 100644
--- a/zfs-script-config.sh.in
+++ b/zfs-script-config.sh.in
@@ -24,6 +24,7 @@ export UDEVRULEDIR=${BUILDDIR}/udev/rules.d
export ZEDLET_ETC_DIR=${SRCDIR}/cmd/zed/zed.d
export ZEDLET_LIBEXEC_DIR=${SRCDIR}/cmd/zed/zed.d
export ZPOOL_SCRIPT_DIR=${SRCDIR}/cmd/zpool/zpool.d
+export ZPOOL_SCRIPTS_PATH=${SRCDIR}/cmd/zpool/zpool.d
export ZDB=${CMDDIR}/zdb/zdb
export ZFS=${CMDDIR}/zfs/zfs