summaryrefslogtreecommitdiffstats
path: root/lib/libzfs/libzfs_util.c
diff options
context:
space:
mode:
authorTony Hutter <[email protected]>2017-04-21 09:27:04 -0700
committerBrian Behlendorf <[email protected]>2017-04-21 09:27:04 -0700
commitd6418de057ecb71fb4cdc1b0a89d5265d13d121a (patch)
treebb4eec33de86e05bcc12d74ac7f8ff335ee45919 /lib/libzfs/libzfs_util.c
parent038091fd4f8d24f308708987192065e55574bbe9 (diff)
Prebaked scripts for zpool status/iostat -c
This patch updates the "zpool status/iostat -c" commands to only run "pre-baked" scripts from the /etc/zfs/zpool.d directory (or wherever you install to). The scripts can only be run from -c as an unprivileged user (unless the ZPOOL_SCRIPTS_AS_ROOT environment var is set by root). This was done to encourage scripts to be written is such a way that normal users can use them, and to be cautious. If your script needs to run a privileged command, consider adding the appropriate line in /etc/sudoers. See zpool(8) for an example of how to do this. The patch also allows the scripts to output custom column names. If the script outputs a line like: name=value then "name" is used for the column name, and "value" is its value. Multiple columns can be specified by outputting multiple lines. Column names and values can have spaces. If the value is empty, a dash (-) is printed instead. After all the "name=value" lines are read (if any), zpool will take the next the next line of output (if any) and print it without a column header. After that, no more lines will be processed. This can be useful for printing errors. Lastly, this patch also disables the -c option with the latency and request size histograms, since it produced awkward output and made the code harder to maintain. Reviewed-by: Brian Behlendorf <[email protected]> Reviewed-by: Giuseppe Di Natale <[email protected]> Signed-off-by: Tony Hutter <[email protected]> Closes #5852
Diffstat (limited to 'lib/libzfs/libzfs_util.c')
-rw-r--r--lib/libzfs/libzfs_util.c150
1 files changed, 146 insertions, 4 deletions
diff --git a/lib/libzfs/libzfs_util.c b/lib/libzfs/libzfs_util.c
index e9119e11d..f17642936 100644
--- a/lib/libzfs/libzfs_util.c
+++ b/lib/libzfs/libzfs_util.c
@@ -726,30 +726,106 @@ libzfs_module_loaded(const char *module)
return (access(path, F_OK) == 0);
}
-int
-libzfs_run_process(const char *path, char *argv[], int flags)
+
+/*
+ * Read lines from an open file descriptor and store them in an array of
+ * strings until EOF. lines[] will be allocated and populated with all the
+ * lines read. All newlines are replaced with NULL terminators for
+ * convenience. lines[] must be freed after use with libzfs_free_str_array().
+ *
+ * Returns the number of lines read.
+ */
+static int
+libzfs_read_stdout_from_fd(int fd, char **lines[])
+{
+
+ FILE *fp;
+ int lines_cnt = 0;
+ size_t len = 0;
+ char *line = NULL;
+ char **tmp_lines = NULL, **tmp;
+ char *nl = NULL;
+ int rc;
+
+ fp = fdopen(fd, "r");
+ if (fp == NULL)
+ return (0);
+ while (1) {
+ rc = getline(&line, &len, fp);
+ if (rc == -1)
+ break;
+
+ tmp = realloc(tmp_lines, sizeof (*tmp_lines) * (lines_cnt + 1));
+ if (tmp == NULL) {
+ /* Return the lines we were able to process */
+ break;
+ }
+ tmp_lines = tmp;
+
+ /* Terminate newlines */
+ if ((nl = strchr(line, '\n')) != NULL)
+ *nl = '\0';
+ tmp_lines[lines_cnt] = line;
+ lines_cnt++;
+ line = NULL;
+ }
+ fclose(fp);
+ *lines = tmp_lines;
+ return (lines_cnt);
+}
+
+static int
+libzfs_run_process_impl(const char *path, char *argv[], char *env[], int flags,
+ char **lines[], int *lines_cnt)
{
pid_t pid;
int error, devnull_fd;
+ int link[2];
+
+ /*
+ * Setup a pipe between our child and parent process if we're
+ * reading stdout.
+ */
+ if ((lines != NULL) && pipe(link) == -1)
+ return (-ESTRPIPE);
pid = vfork();
if (pid == 0) {
+ /* Child process */
devnull_fd = open("/dev/null", O_WRONLY);
if (devnull_fd < 0)
_exit(-1);
- if (!(flags & STDOUT_VERBOSE))
+ if (!(flags & STDOUT_VERBOSE) && (lines == NULL))
(void) dup2(devnull_fd, STDOUT_FILENO);
+ else if (lines != NULL) {
+ /* Save the output to lines[] */
+ dup2(link[1], STDOUT_FILENO);
+ close(link[0]);
+ close(link[1]);
+ }
if (!(flags & STDERR_VERBOSE))
(void) dup2(devnull_fd, STDERR_FILENO);
close(devnull_fd);
- (void) execvp(path, argv);
+ if (flags & NO_DEFAULT_PATH) {
+ if (env == NULL)
+ execv(path, argv);
+ else
+ execve(path, argv, env);
+ } else {
+ if (env == NULL)
+ execvp(path, argv);
+ else
+ execvpe(path, argv, env);
+ }
+
_exit(-1);
} else if (pid > 0) {
+ /* Parent process */
int status;
while ((error = waitpid(pid, &status, 0)) == -1 &&
@@ -757,12 +833,78 @@ libzfs_run_process(const char *path, char *argv[], int flags)
if (error < 0 || !WIFEXITED(status))
return (-1);
+ if (lines != NULL) {
+ close(link[1]);
+ *lines_cnt = libzfs_read_stdout_from_fd(link[0], lines);
+ }
return (WEXITSTATUS(status));
}
return (-1);
}
+int
+libzfs_run_process(const char *path, char *argv[], int flags)
+{
+ return (libzfs_run_process_impl(path, argv, NULL, flags, NULL, NULL));
+}
+
+/*
+ * Run a command and store its stdout lines in an array of strings (lines[]).
+ * lines[] is allocated and populated for you, and the number of lines is set in
+ * lines_cnt. lines[] must be freed after use with libzfs_free_str_array().
+ * All newlines (\n) in lines[] are terminated for convenience.
+ */
+int
+libzfs_run_process_get_stdout(const char *path, char *argv[], char *env[],
+ char **lines[], int *lines_cnt)
+{
+ return (libzfs_run_process_impl(path, argv, env, 0, lines, lines_cnt));
+}
+
+/*
+ * Same as libzfs_run_process_get_stdout(), but run without $PATH set. This
+ * means that *path needs to be the full path to the executable.
+ */
+int
+libzfs_run_process_get_stdout_nopath(const char *path, char *argv[],
+ char *env[], char **lines[], int *lines_cnt)
+{
+ return (libzfs_run_process_impl(path, argv, env, NO_DEFAULT_PATH,
+ lines, lines_cnt));
+}
+
+/*
+ * Free an array of strings. Free both the strings contained in the array and
+ * the array itself.
+ */
+void
+libzfs_free_str_array(char **strs, int count)
+{
+ while (--count >= 0)
+ free(strs[count]);
+
+ free(strs);
+}
+
+/*
+ * Returns 1 if environment variable is set to "YES", "yes", "ON", "on", or
+ * a non-zero number.
+ *
+ * Returns 0 otherwise.
+ */
+int
+libzfs_envvar_is_set(char *envvar)
+{
+ char *env = getenv(envvar);
+ if (env && (strtoul(env, NULL, 0) > 0 ||
+ (!strncasecmp(env, "YES", 3) && strnlen(env, 4) == 3) ||
+ (!strncasecmp(env, "ON", 2) && strnlen(env, 3) == 2)))
+ return (1);
+
+ return (0);
+}
+
/*
* Verify the required ZFS_DEV device is available and optionally attempt
* to load the ZFS modules. Under normal circumstances the modules