]> granicus.if.org Git - zfs/blobdiff - lib/libzfs/libzfs_util.c
Race condition between spa async threads and export
[zfs] / lib / libzfs / libzfs_util.c
index 4da0fb44b2c1aba95bcca3a42b9d165d0ebda26d..9dcbb9b608cded365bce6180a44c12e5ba089176 100644 (file)
  *
  * CDDL HEADER END
  */
+
 /*
- * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
- * Use is subject to license terms.
+ * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2018, Joyent, Inc. All rights reserved.
+ * Copyright (c) 2011, 2018 by Delphix. All rights reserved.
+ * Copyright 2016 Igor Kozhukhov <ikozhukhov@gmail.com>
+ * Copyright (c) 2017 Datto Inc.
  */
 
 /*
 #include <stdlib.h>
 #include <strings.h>
 #include <unistd.h>
-#include <ctype.h>
 #include <math.h>
+#include <sys/stat.h>
 #include <sys/mnttab.h>
 #include <sys/mntent.h>
 #include <sys/types.h>
+#include <sys/wait.h>
 
 #include <libzfs.h>
+#include <libzfs_core.h>
 
 #include "libzfs_impl.h"
 #include "zfs_prop.h"
+#include "zfeature_common.h"
+#include <zfs_fletcher.h>
+#include <libzutil.h>
+#include <sys/zfs_sysfs.h>
 
 int
 libzfs_errno(libzfs_handle_t *hdl)
@@ -52,6 +62,31 @@ libzfs_errno(libzfs_handle_t *hdl)
        return (hdl->libzfs_error);
 }
 
+const char *
+libzfs_error_init(int error)
+{
+       switch (error) {
+       case ENXIO:
+               return (dgettext(TEXT_DOMAIN, "The ZFS modules are not "
+                   "loaded.\nTry running '/sbin/modprobe zfs' as root "
+                   "to load them.\n"));
+       case ENOENT:
+               return (dgettext(TEXT_DOMAIN, "/dev/zfs and /proc/self/mounts "
+                   "are required.\nTry running 'udevadm trigger' and 'mount "
+                   "-t proc proc /proc' as root.\n"));
+       case ENOEXEC:
+               return (dgettext(TEXT_DOMAIN, "The ZFS modules cannot be "
+                   "auto-loaded.\nTry running '/sbin/modprobe zfs' as "
+                   "root to manually load them.\n"));
+       case EACCES:
+               return (dgettext(TEXT_DOMAIN, "Permission denied the "
+                   "ZFS utilities must be run as root.\n"));
+       default:
+               return (dgettext(TEXT_DOMAIN, "Failed to initialize the "
+                   "libzfs library.\n"));
+       }
+}
+
 const char *
 libzfs_error_action(libzfs_handle_t *hdl)
 {
@@ -70,7 +105,7 @@ libzfs_error_description(libzfs_handle_t *hdl)
        case EZFS_BADPROP:
                return (dgettext(TEXT_DOMAIN, "invalid property value"));
        case EZFS_PROPREADONLY:
-               return (dgettext(TEXT_DOMAIN, "read only property"));
+               return (dgettext(TEXT_DOMAIN, "read-only property"));
        case EZFS_PROPTYPE:
                return (dgettext(TEXT_DOMAIN, "property doesn't apply to "
                    "datasets of this type"));
@@ -90,12 +125,10 @@ libzfs_error_description(libzfs_handle_t *hdl)
        case EZFS_BADSTREAM:
                return (dgettext(TEXT_DOMAIN, "invalid backup stream"));
        case EZFS_DSREADONLY:
-               return (dgettext(TEXT_DOMAIN, "dataset is read only"));
+               return (dgettext(TEXT_DOMAIN, "dataset is read-only"));
        case EZFS_VOLTOOBIG:
                return (dgettext(TEXT_DOMAIN, "volume size exceeds limit for "
                    "this system"));
-       case EZFS_VOLHASDATA:
-               return (dgettext(TEXT_DOMAIN, "volume has data"));
        case EZFS_INVALIDNAME:
                return (dgettext(TEXT_DOMAIN, "invalid name"));
        case EZFS_BADRESTORE:
@@ -114,7 +147,8 @@ libzfs_error_description(libzfs_handle_t *hdl)
        case EZFS_RESILVERING:
                return (dgettext(TEXT_DOMAIN, "currently resilvering"));
        case EZFS_BADVERSION:
-               return (dgettext(TEXT_DOMAIN, "unsupported version"));
+               return (dgettext(TEXT_DOMAIN, "unsupported version or "
+                   "feature"));
        case EZFS_POOLUNAVAIL:
                return (dgettext(TEXT_DOMAIN, "pool is unavailable"));
        case EZFS_DEVOVERFLOW:
@@ -138,16 +172,12 @@ libzfs_error_description(libzfs_handle_t *hdl)
                return (dgettext(TEXT_DOMAIN, "smb remove share failed"));
        case EZFS_SHARESMBFAILED:
                return (dgettext(TEXT_DOMAIN, "smb add share failed"));
-       case EZFS_ISCSISVCUNAVAIL:
-               return (dgettext(TEXT_DOMAIN,
-                   "iscsitgt service need to be enabled by "
-                   "a privileged user"));
-       case EZFS_DEVLINKS:
-               return (dgettext(TEXT_DOMAIN, "failed to create /dev links"));
        case EZFS_PERM:
                return (dgettext(TEXT_DOMAIN, "permission denied"));
        case EZFS_NOSPC:
                return (dgettext(TEXT_DOMAIN, "out of space"));
+       case EZFS_FAULT:
+               return (dgettext(TEXT_DOMAIN, "bad address"));
        case EZFS_IO:
                return (dgettext(TEXT_DOMAIN, "I/O error"));
        case EZFS_INTR:
@@ -161,12 +191,6 @@ libzfs_error_description(libzfs_handle_t *hdl)
                return (dgettext(TEXT_DOMAIN, "recursive dataset dependency"));
        case EZFS_NOHISTORY:
                return (dgettext(TEXT_DOMAIN, "no history available"));
-       case EZFS_UNSHAREISCSIFAILED:
-               return (dgettext(TEXT_DOMAIN,
-                   "iscsitgtd failed request to unshare"));
-       case EZFS_SHAREISCSIFAILED:
-               return (dgettext(TEXT_DOMAIN,
-                   "iscsitgtd failed request to share"));
        case EZFS_POOLPROPS:
                return (dgettext(TEXT_DOMAIN, "failed to retrieve "
                    "pool properties"));
@@ -194,9 +218,6 @@ libzfs_error_description(libzfs_handle_t *hdl)
        case EZFS_NODELEGATION:
                return (dgettext(TEXT_DOMAIN, "delegated administration is "
                    "disabled on pool"));
-       case EZFS_PERMRDONLY:
-               return (dgettext(TEXT_DOMAIN, "snapshot permissions cannot be"
-                   " modified"));
        case EZFS_BADCACHE:
                return (dgettext(TEXT_DOMAIN, "invalid or missing cache file"));
        case EZFS_ISL2CACHE:
@@ -207,6 +228,9 @@ libzfs_error_description(libzfs_handle_t *hdl)
        case EZFS_NOTSUP:
                return (dgettext(TEXT_DOMAIN, "operation not supported "
                    "on this dataset"));
+       case EZFS_IOC_NOTSUPPORTED:
+               return (dgettext(TEXT_DOMAIN, "operation not supported by "
+                   "zfs kernel module"));
        case EZFS_ACTIVE_SPARE:
                return (dgettext(TEXT_DOMAIN, "pool has active shared spare "
                    "device"));
@@ -218,6 +242,69 @@ libzfs_error_description(libzfs_handle_t *hdl)
        case EZFS_REFTAG_HOLD:
                return (dgettext(TEXT_DOMAIN, "tag already exists on this "
                    "dataset"));
+       case EZFS_TAGTOOLONG:
+               return (dgettext(TEXT_DOMAIN, "tag too long"));
+       case EZFS_PIPEFAILED:
+               return (dgettext(TEXT_DOMAIN, "pipe create failed"));
+       case EZFS_THREADCREATEFAILED:
+               return (dgettext(TEXT_DOMAIN, "thread create failed"));
+       case EZFS_POSTSPLIT_ONLINE:
+               return (dgettext(TEXT_DOMAIN, "disk was split from this pool "
+                   "into a new one"));
+       case EZFS_SCRUB_PAUSED:
+               return (dgettext(TEXT_DOMAIN, "scrub is paused; "
+                   "use 'zpool scrub' to resume"));
+       case EZFS_SCRUBBING:
+               return (dgettext(TEXT_DOMAIN, "currently scrubbing; "
+                   "use 'zpool scrub -s' to cancel current scrub"));
+       case EZFS_NO_SCRUB:
+               return (dgettext(TEXT_DOMAIN, "there is no active scrub"));
+       case EZFS_DIFF:
+               return (dgettext(TEXT_DOMAIN, "unable to generate diffs"));
+       case EZFS_DIFFDATA:
+               return (dgettext(TEXT_DOMAIN, "invalid diff data"));
+       case EZFS_POOLREADONLY:
+               return (dgettext(TEXT_DOMAIN, "pool is read-only"));
+       case EZFS_NO_PENDING:
+               return (dgettext(TEXT_DOMAIN, "operation is not "
+                   "in progress"));
+       case EZFS_CHECKPOINT_EXISTS:
+               return (dgettext(TEXT_DOMAIN, "checkpoint exists"));
+       case EZFS_DISCARDING_CHECKPOINT:
+               return (dgettext(TEXT_DOMAIN, "currently discarding "
+                   "checkpoint"));
+       case EZFS_NO_CHECKPOINT:
+               return (dgettext(TEXT_DOMAIN, "checkpoint does not exist"));
+       case EZFS_DEVRM_IN_PROGRESS:
+               return (dgettext(TEXT_DOMAIN, "device removal in progress"));
+       case EZFS_VDEV_TOO_BIG:
+               return (dgettext(TEXT_DOMAIN, "device exceeds supported size"));
+       case EZFS_ACTIVE_POOL:
+               return (dgettext(TEXT_DOMAIN, "pool is imported on a "
+                   "different host"));
+       case EZFS_CRYPTOFAILED:
+               return (dgettext(TEXT_DOMAIN, "encryption failure"));
+       case EZFS_TOOMANY:
+               return (dgettext(TEXT_DOMAIN, "argument list too long"));
+       case EZFS_INITIALIZING:
+               return (dgettext(TEXT_DOMAIN, "currently initializing"));
+       case EZFS_NO_INITIALIZE:
+               return (dgettext(TEXT_DOMAIN, "there is no active "
+                   "initialization"));
+       case EZFS_WRONG_PARENT:
+               return (dgettext(TEXT_DOMAIN, "invalid parent dataset"));
+       case EZFS_TRIMMING:
+               return (dgettext(TEXT_DOMAIN, "currently trimming"));
+       case EZFS_NO_TRIM:
+               return (dgettext(TEXT_DOMAIN, "there is no active trim"));
+       case EZFS_TRIM_NOTSUP:
+               return (dgettext(TEXT_DOMAIN, "trim operations are not "
+                   "supported by this device"));
+       case EZFS_NO_RESILVER_DEFER:
+               return (dgettext(TEXT_DOMAIN, "this action requires the "
+                   "resilver_defer feature"));
+       case EZFS_EXPORT_IN_PROGRESS:
+               return (dgettext(TEXT_DOMAIN, "pool export in progress"));
        case EZFS_UNKNOWN:
                return (dgettext(TEXT_DOMAIN, "unknown error"));
        default:
@@ -306,6 +393,10 @@ zfs_common_error(libzfs_handle_t *hdl, int error, const char *fmt,
                zfs_verror(hdl, EZFS_IO, fmt, ap);
                return (-1);
 
+       case EFAULT:
+               zfs_verror(hdl, EZFS_FAULT, fmt, ap);
+               return (-1);
+
        case EINTR:
                zfs_verror(hdl, EZFS_INTR, fmt, ap);
                return (-1);
@@ -336,6 +427,7 @@ zfs_standard_error_fmt(libzfs_handle_t *hdl, int error, const char *fmt, ...)
        switch (error) {
        case ENXIO:
        case ENODEV:
+       case EPIPE:
                zfs_verror(hdl, EZFS_IO, fmt, ap);
                break;
 
@@ -348,7 +440,7 @@ zfs_standard_error_fmt(libzfs_handle_t *hdl, int error, const char *fmt, ...)
        case ENOSPC:
        case EDQUOT:
                zfs_verror(hdl, EZFS_NOSPC, fmt, ap);
-               return (-1);
+               break;
 
        case EEXIST:
                zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
@@ -362,9 +454,7 @@ zfs_standard_error_fmt(libzfs_handle_t *hdl, int error, const char *fmt, ...)
                zfs_verror(hdl, EZFS_BUSY, fmt, ap);
                break;
        case EROFS:
-               zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
-                   "snapshot permissions cannot be modified"));
-               zfs_verror(hdl, EZFS_PERMRDONLY, fmt, ap);
+               zfs_verror(hdl, EZFS_POOLREADONLY, fmt, ap);
                break;
        case ENAMETOOLONG:
                zfs_verror(hdl, EZFS_NAMETOOLONG, fmt, ap);
@@ -377,8 +467,31 @@ zfs_standard_error_fmt(libzfs_handle_t *hdl, int error, const char *fmt, ...)
                    "pool I/O is currently suspended"));
                zfs_verror(hdl, EZFS_POOLUNAVAIL, fmt, ap);
                break;
+       case EREMOTEIO:
+               zfs_verror(hdl, EZFS_ACTIVE_POOL, fmt, ap);
+               break;
+       case ZFS_ERR_UNKNOWN_SEND_STREAM_FEATURE:
+       case ZFS_ERR_IOC_CMD_UNAVAIL:
+               zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "the loaded zfs "
+                   "module does not support this operation. A reboot may "
+                   "be required to enable this operation."));
+               zfs_verror(hdl, EZFS_IOC_NOTSUPPORTED, fmt, ap);
+               break;
+       case ZFS_ERR_IOC_ARG_UNAVAIL:
+               zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "the loaded zfs "
+                   "module does not support an option for this operation. "
+                   "A reboot may be required to enable this option."));
+               zfs_verror(hdl, EZFS_IOC_NOTSUPPORTED, fmt, ap);
+               break;
+       case ZFS_ERR_IOC_ARG_REQUIRED:
+       case ZFS_ERR_IOC_ARG_BADTYPE:
+               zfs_verror(hdl, EZFS_IOC_NOTSUPPORTED, fmt, ap);
+               break;
+       case ZFS_ERR_WRONG_PARENT:
+               zfs_verror(hdl, EZFS_WRONG_PARENT, fmt, ap);
+               break;
        default:
-               zfs_error_aux(hdl, strerror(errno));
+               zfs_error_aux(hdl, strerror(error));
                zfs_verror(hdl, EZFS_UNKNOWN, fmt, ap);
                break;
        }
@@ -428,6 +541,11 @@ zpool_standard_error_fmt(libzfs_handle_t *hdl, int error, const char *fmt, ...)
                zfs_verror(hdl, EZFS_BUSY, fmt, ap);
                break;
 
+       /* There is no pending operation to cancel */
+       case ENOTACTIVE:
+               zfs_verror(hdl, EZFS_NO_PENDING, fmt, ap);
+               break;
+
        case ENXIO:
                zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
                    "one or more devices is currently unavailable"));
@@ -450,12 +568,58 @@ zpool_standard_error_fmt(libzfs_handle_t *hdl, int error, const char *fmt, ...)
        case EDQUOT:
                zfs_verror(hdl, EZFS_NOSPC, fmt, ap);
                return (-1);
+
        case EAGAIN:
                zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
                    "pool I/O is currently suspended"));
                zfs_verror(hdl, EZFS_POOLUNAVAIL, fmt, ap);
                break;
 
+       case EROFS:
+               zfs_verror(hdl, EZFS_POOLREADONLY, fmt, ap);
+               break;
+       case EDOM:
+               zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+                   "block size out of range or does not match"));
+               zfs_verror(hdl, EZFS_BADPROP, fmt, ap);
+               break;
+       case EREMOTEIO:
+               zfs_verror(hdl, EZFS_ACTIVE_POOL, fmt, ap);
+               break;
+       case ZFS_ERR_CHECKPOINT_EXISTS:
+               zfs_verror(hdl, EZFS_CHECKPOINT_EXISTS, fmt, ap);
+               break;
+       case ZFS_ERR_DISCARDING_CHECKPOINT:
+               zfs_verror(hdl, EZFS_DISCARDING_CHECKPOINT, fmt, ap);
+               break;
+       case ZFS_ERR_NO_CHECKPOINT:
+               zfs_verror(hdl, EZFS_NO_CHECKPOINT, fmt, ap);
+               break;
+       case ZFS_ERR_DEVRM_IN_PROGRESS:
+               zfs_verror(hdl, EZFS_DEVRM_IN_PROGRESS, fmt, ap);
+               break;
+       case ZFS_ERR_VDEV_TOO_BIG:
+               zfs_verror(hdl, EZFS_VDEV_TOO_BIG, fmt, ap);
+               break;
+       case ZFS_ERR_EXPORT_IN_PROGRESS:
+               zfs_verror(hdl, EZFS_EXPORT_IN_PROGRESS, fmt, ap);
+               break;
+       case ZFS_ERR_IOC_CMD_UNAVAIL:
+               zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "the loaded zfs "
+                   "module does not support this operation. A reboot may "
+                   "be required to enable this operation."));
+               zfs_verror(hdl, EZFS_IOC_NOTSUPPORTED, fmt, ap);
+               break;
+       case ZFS_ERR_IOC_ARG_UNAVAIL:
+               zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "the loaded zfs "
+                   "module does not support an option for this operation. "
+                   "A reboot may be required to enable this option."));
+               zfs_verror(hdl, EZFS_IOC_NOTSUPPORTED, fmt, ap);
+               break;
+       case ZFS_ERR_IOC_ARG_REQUIRED:
+       case ZFS_ERR_IOC_ARG_BADTYPE:
+               zfs_verror(hdl, EZFS_IOC_NOTSUPPORTED, fmt, ap);
+               break;
        default:
                zfs_error_aux(hdl, strerror(error));
                zfs_verror(hdl, EZFS_UNKNOWN, fmt, ap);
@@ -488,6 +652,29 @@ zfs_alloc(libzfs_handle_t *hdl, size_t size)
        return (data);
 }
 
+/*
+ * A safe form of asprintf() which will die if the allocation fails.
+ */
+/*PRINTFLIKE2*/
+char *
+zfs_asprintf(libzfs_handle_t *hdl, const char *fmt, ...)
+{
+       va_list ap;
+       char *ret;
+       int err;
+
+       va_start(ap, fmt);
+
+       err = vasprintf(&ret, fmt, ap);
+
+       va_end(ap);
+
+       if (err < 0)
+               (void) no_memory(hdl);
+
+       return (ret);
+}
+
 /*
  * A safe form of realloc(), which also zeroes newly allocated space.
  */
@@ -519,62 +706,287 @@ zfs_strdup(libzfs_handle_t *hdl, const char *str)
        return (ret);
 }
 
+void
+libzfs_print_on_error(libzfs_handle_t *hdl, boolean_t printerr)
+{
+       hdl->libzfs_printerr = printerr;
+}
+
+static int
+libzfs_module_loaded(const char *module)
+{
+       const char path_prefix[] = "/sys/module/";
+       char path[256];
+
+       memcpy(path, path_prefix, sizeof (path_prefix) - 1);
+       strcpy(path + sizeof (path_prefix) - 1, module);
+
+       return (access(path, F_OK) == 0);
+}
+
+
 /*
- * Convert a number to an appropriately human-readable output.
+ * 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.
  */
-void
-zfs_nicenum(uint64_t num, char *buf, size_t buflen)
+static int
+libzfs_read_stdout_from_fd(int fd, char **lines[])
 {
-       uint64_t n = num;
-       int index = 0;
-       char u;
 
-       while (n >= 1024) {
-               n /= 1024;
-               index++;
+       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);
+}
 
-       u = " KMGTPE"[index];
+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];
 
-       if (index == 0) {
-               (void) snprintf(buf, buflen, "%llu", n);
-       } else if ((num & ((1ULL << 10 * index) - 1)) == 0) {
-               /*
-                * If this is an even multiple of the base, always display
-                * without any decimal precision.
-                */
-               (void) snprintf(buf, buflen, "%llu%c", n, u);
-       } else {
-               /*
-                * We want to choose a precision that reflects the best choice
-                * for fitting in 5 characters.  This can get rather tricky when
-                * we have numbers that are very close to an order of magnitude.
-                * For example, when displaying 10239 (which is really 9.999K),
-                * we want only a single place of precision for 10.0K.  We could
-                * develop some complex heuristics for this, but it's much
-                * easier just to try each combination in turn.
-                */
-               int i;
-               for (i = 2; i >= 0; i--) {
-                       if (snprintf(buf, buflen, "%.*f%c", i,
-                           (double)num / (1ULL << 10 * index), u) <= 5)
-                               break;
+       /*
+        * 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) && (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);
+
+               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 &&
+                   errno == EINTR) { }
+               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_print_on_error(libzfs_handle_t *hdl, boolean_t printerr)
+libzfs_free_str_array(char **strs, int count)
 {
-       hdl->libzfs_printerr = printerr;
+       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
+ * should already have been loaded by some external mechanism.
+ *
+ * Environment variables:
+ * - ZFS_MODULE_LOADING="YES|yes|ON|on" - Attempt to load modules.
+ * - ZFS_MODULE_TIMEOUT="<seconds>"     - Seconds to wait for ZFS_DEV
+ */
+static int
+libzfs_load_module(const char *module)
+{
+       char *argv[4] = {"/sbin/modprobe", "-q", (char *)module, (char *)0};
+       char *load_str, *timeout_str;
+       long timeout = 10; /* seconds */
+       long busy_timeout = 10; /* milliseconds */
+       int load = 0, fd;
+       hrtime_t start;
+
+       /* Optionally request module loading */
+       if (!libzfs_module_loaded(module)) {
+               load_str = getenv("ZFS_MODULE_LOADING");
+               if (load_str) {
+                       if (!strncasecmp(load_str, "YES", strlen("YES")) ||
+                           !strncasecmp(load_str, "ON", strlen("ON")))
+                               load = 1;
+                       else
+                               load = 0;
+               }
+
+               if (load) {
+                       if (libzfs_run_process("/sbin/modprobe", argv, 0))
+                               return (ENOEXEC);
+               }
+
+               if (!libzfs_module_loaded(module))
+                       return (ENXIO);
+       }
+
+       /*
+        * Device creation by udev is asynchronous and waiting may be
+        * required.  Busy wait for 10ms and then fall back to polling every
+        * 10ms for the allowed timeout (default 10s, max 10m).  This is
+        * done to optimize for the common case where the device is
+        * immediately available and to avoid penalizing the possible
+        * case where udev is slow or unable to create the device.
+        */
+       timeout_str = getenv("ZFS_MODULE_TIMEOUT");
+       if (timeout_str) {
+               timeout = strtol(timeout_str, NULL, 0);
+               timeout = MAX(MIN(timeout, (10 * 60)), 0); /* 0 <= N <= 600 */
+       }
+
+       start = gethrtime();
+       do {
+               fd = open(ZFS_DEV, O_RDWR);
+               if (fd >= 0) {
+                       (void) close(fd);
+                       return (0);
+               } else if (errno != ENOENT) {
+                       return (errno);
+               } else if (NSEC2MSEC(gethrtime() - start) < busy_timeout) {
+                       sched_yield();
+               } else {
+                       usleep(10 * MILLISEC);
+               }
+       } while (NSEC2MSEC(gethrtime() - start) < (timeout * MILLISEC));
+
+       return (ENOENT);
 }
 
 libzfs_handle_t *
 libzfs_init(void)
 {
        libzfs_handle_t *hdl;
+       int error;
+
+       error = libzfs_load_module(ZFS_DRIVER);
+       if (error) {
+               errno = error;
+               return (NULL);
+       }
 
-       if ((hdl = calloc(sizeof (libzfs_handle_t), 1)) == NULL) {
+       if ((hdl = calloc(1, sizeof (libzfs_handle_t))) == NULL) {
                return (NULL);
        }
 
@@ -583,17 +995,52 @@ libzfs_init(void)
                return (NULL);
        }
 
+#ifdef HAVE_SETMNTENT
+       if ((hdl->libzfs_mnttab = setmntent(MNTTAB, "r")) == NULL) {
+#else
        if ((hdl->libzfs_mnttab = fopen(MNTTAB, "r")) == NULL) {
+#endif
                (void) close(hdl->libzfs_fd);
                free(hdl);
                return (NULL);
        }
 
-       hdl->libzfs_sharetab = fopen("/etc/dfs/sharetab", "r");
+       hdl->libzfs_sharetab = fopen(ZFS_SHARETAB, "r");
+
+       if (libzfs_core_init() != 0) {
+               (void) close(hdl->libzfs_fd);
+               (void) fclose(hdl->libzfs_mnttab);
+               if (hdl->libzfs_sharetab)
+                       (void) fclose(hdl->libzfs_sharetab);
+               free(hdl);
+               return (NULL);
+       }
 
        zfs_prop_init();
        zpool_prop_init();
+       zpool_feature_init();
        libzfs_mnttab_init(hdl);
+       fletcher_4_init();
+
+       if (getenv("ZFS_PROP_DEBUG") != NULL) {
+               hdl->libzfs_prop_debug = B_TRUE;
+       }
+
+       /*
+        * For testing, remove some settable properties and features
+        */
+       if (libzfs_envvar_is_set("ZFS_SYSFS_PROP_SUPPORT_TEST")) {
+               zprop_desc_t *proptbl;
+
+               proptbl = zpool_prop_get_table();
+               proptbl[ZPOOL_PROP_COMMENT].pd_zfs_mod_supported = B_FALSE;
+
+               proptbl = zfs_prop_get_table();
+               proptbl[ZFS_PROP_DNODESIZE].pd_zfs_mod_supported = B_FALSE;
+
+               zfeature_info_t *ftbl = spa_feature_table;
+               ftbl[SPA_FEATURE_LARGE_BLOCKS].fi_zfs_mod_supported = B_FALSE;
+       }
 
        return (hdl);
 }
@@ -603,15 +1050,19 @@ libzfs_fini(libzfs_handle_t *hdl)
 {
        (void) close(hdl->libzfs_fd);
        if (hdl->libzfs_mnttab)
+#ifdef HAVE_SETMNTENT
+               (void) endmntent(hdl->libzfs_mnttab);
+#else
                (void) fclose(hdl->libzfs_mnttab);
+#endif
        if (hdl->libzfs_sharetab)
                (void) fclose(hdl->libzfs_sharetab);
        zfs_uninit_libshare(hdl);
-       if (hdl->libzfs_log_str)
-               (void) free(hdl->libzfs_log_str);
        zpool_free_handles(hdl);
        namespace_clear(hdl);
        libzfs_mnttab_fini(hdl);
+       libzfs_core_fini();
+       fletcher_4_fini();
        free(hdl);
 }
 
@@ -637,7 +1088,7 @@ zfs_get_pool_handle(const zfs_handle_t *zhp)
  * Given a name, determine whether or not it's a valid path
  * (starts with '/' or "./").  If so, walk the mnttab trying
  * to match the device number.  If not, treat the path as an
- * fs/vol/snap name.
+ * fs/vol/snap/bkmark name.
  */
 zfs_handle_t *
 zfs_path_to_zhandle(libzfs_handle_t *hdl, char *path, zfs_type_t argtype)
@@ -658,7 +1109,10 @@ zfs_path_to_zhandle(libzfs_handle_t *hdl, char *path, zfs_type_t argtype)
                return (NULL);
        }
 
-       rewind(hdl->libzfs_mnttab);
+       /* Reopen MNTTAB to prevent reading stale data from open file */
+       if (freopen(MNTTAB, "r", hdl->libzfs_mnttab) == NULL)
+               return (NULL);
+
        while ((ret = getextmntent(hdl->libzfs_mnttab, &entry, 0)) == 0) {
                if (makedevice(entry.mnt_major, entry.mnt_minor) ==
                    statbuf.st_dev) {
@@ -686,10 +1140,11 @@ int
 zcmd_alloc_dst_nvlist(libzfs_handle_t *hdl, zfs_cmd_t *zc, size_t len)
 {
        if (len == 0)
-               len = 2048;
+               len = 16 * 1024;
        zc->zc_nvlist_dst_size = len;
-       if ((zc->zc_nvlist_dst = (uint64_t)(uintptr_t)
-           zfs_alloc(hdl, zc->zc_nvlist_dst_size)) == NULL)
+       zc->zc_nvlist_dst =
+           (uint64_t)(uintptr_t)zfs_alloc(hdl, zc->zc_nvlist_dst_size);
+       if (zc->zc_nvlist_dst == 0)
                return (-1);
 
        return (0);
@@ -704,9 +1159,9 @@ int
 zcmd_expand_dst_nvlist(libzfs_handle_t *hdl, zfs_cmd_t *zc)
 {
        free((void *)(uintptr_t)zc->zc_nvlist_dst);
-       if ((zc->zc_nvlist_dst = (uint64_t)(uintptr_t)
-           zfs_alloc(hdl, zc->zc_nvlist_dst_size))
-           == NULL)
+       zc->zc_nvlist_dst =
+           (uint64_t)(uintptr_t)zfs_alloc(hdl, zc->zc_nvlist_dst_size);
+       if (zc->zc_nvlist_dst == 0)
                return (-1);
 
        return (0);
@@ -721,6 +1176,9 @@ zcmd_free_nvlists(zfs_cmd_t *zc)
        free((void *)(uintptr_t)zc->zc_nvlist_conf);
        free((void *)(uintptr_t)zc->zc_nvlist_src);
        free((void *)(uintptr_t)zc->zc_nvlist_dst);
+       zc->zc_nvlist_conf = 0;
+       zc->zc_nvlist_src = 0;
+       zc->zc_nvlist_dst = 0;
 }
 
 static int
@@ -773,17 +1231,7 @@ zcmd_read_dst_nvlist(libzfs_handle_t *hdl, zfs_cmd_t *zc, nvlist_t **nvlp)
 int
 zfs_ioctl(libzfs_handle_t *hdl, int request, zfs_cmd_t *zc)
 {
-       int error;
-
-       zc->zc_history = (uint64_t)(uintptr_t)hdl->libzfs_log_str;
-       error = ioctl(hdl->libzfs_fd, request, zc);
-       if (hdl->libzfs_log_str) {
-               free(hdl->libzfs_log_str);
-               hdl->libzfs_log_str = NULL;
-       }
-       zc->zc_history = 0;
-
-       return (error);
+       return (ioctl(hdl->libzfs_fd, request, zc));
 }
 
 /*
@@ -812,6 +1260,8 @@ zprop_print_headers(zprop_get_cbdata_t *cbp, zfs_type_t type)
            "PROPERTY"));
        cbp->cb_colwidths[GET_COL_VALUE] = strlen(dgettext(TEXT_DOMAIN,
            "VALUE"));
+       cbp->cb_colwidths[GET_COL_RECVD] = strlen(dgettext(TEXT_DOMAIN,
+           "RECEIVED"));
        cbp->cb_colwidths[GET_COL_SOURCE] = strlen(dgettext(TEXT_DOMAIN,
            "SOURCE"));
 
@@ -825,7 +1275,7 @@ zprop_print_headers(zprop_get_cbdata_t *cbp, zfs_type_t type)
         * inheriting from the longest name.  This is acceptable because in the
         * majority of cases 'SOURCE' is the last column displayed, and we don't
         * use the width anyway.  Note that the 'VALUE' column can be oversized,
-        * if the name of the property is much longer the any values we find.
+        * if the name of the property is much longer than any values we find.
         */
        for (pl = cbp->cb_proplist; pl != NULL; pl = pl->pl_next) {
                /*
@@ -856,6 +1306,11 @@ zprop_print_headers(zprop_get_cbdata_t *cbp, zfs_type_t type)
                    pl->pl_width > cbp->cb_colwidths[GET_COL_VALUE])
                        cbp->cb_colwidths[GET_COL_VALUE] = pl->pl_width;
 
+               /* 'RECEIVED' column. */
+               if (pl != cbp->cb_proplist &&
+                   pl->pl_recvd_width > cbp->cb_colwidths[GET_COL_RECVD])
+                       cbp->cb_colwidths[GET_COL_RECVD] = pl->pl_recvd_width;
+
                /*
                 * 'NAME' and 'SOURCE' columns
                 */
@@ -871,7 +1326,7 @@ zprop_print_headers(zprop_get_cbdata_t *cbp, zfs_type_t type)
        /*
         * Now go through and print the headers.
         */
-       for (i = 0; i < 4; i++) {
+       for (i = 0; i < ZFS_GET_NCOLS; i++) {
                switch (cbp->cb_columns[i]) {
                case GET_COL_NAME:
                        title = dgettext(TEXT_DOMAIN, "NAME");
@@ -882,6 +1337,9 @@ zprop_print_headers(zprop_get_cbdata_t *cbp, zfs_type_t type)
                case GET_COL_VALUE:
                        title = dgettext(TEXT_DOMAIN, "VALUE");
                        break;
+               case GET_COL_RECVD:
+                       title = dgettext(TEXT_DOMAIN, "RECEIVED");
+                       break;
                case GET_COL_SOURCE:
                        title = dgettext(TEXT_DOMAIN, "SOURCE");
                        break;
@@ -890,7 +1348,8 @@ zprop_print_headers(zprop_get_cbdata_t *cbp, zfs_type_t type)
                }
 
                if (title != NULL) {
-                       if (i == 3 || cbp->cb_columns[i + 1] == 0)
+                       if (i == (ZFS_GET_NCOLS - 1) ||
+                           cbp->cb_columns[i + 1] == GET_COL_NONE)
                                (void) printf("%s", title);
                        else
                                (void) printf("%-*s  ",
@@ -908,10 +1367,10 @@ zprop_print_headers(zprop_get_cbdata_t *cbp, zfs_type_t type)
 void
 zprop_print_one_property(const char *name, zprop_get_cbdata_t *cbp,
     const char *propname, const char *value, zprop_source_t sourcetype,
-    const char *source)
+    const char *source, const char *recvd_value)
 {
        int i;
-       const char *str;
+       const char *str = NULL;
        char buf[128];
 
        /*
@@ -923,7 +1382,7 @@ zprop_print_one_property(const char *name, zprop_get_cbdata_t *cbp,
        if (cbp->cb_first)
                zprop_print_headers(cbp, cbp->cb_type);
 
-       for (i = 0; i < 4; i++) {
+       for (i = 0; i < ZFS_GET_NCOLS; i++) {
                switch (cbp->cb_columns[i]) {
                case GET_COL_NAME:
                        str = name;
@@ -960,14 +1419,26 @@ zprop_print_one_property(const char *name, zprop_get_cbdata_t *cbp,
                                    "inherited from %s", source);
                                str = buf;
                                break;
+                       case ZPROP_SRC_RECEIVED:
+                               str = "received";
+                               break;
+
+                       default:
+                               str = NULL;
+                               assert(!"unhandled zprop_source_t");
                        }
                        break;
 
+               case GET_COL_RECVD:
+                       str = (recvd_value == NULL ? "-" : recvd_value);
+                       break;
+
                default:
                        continue;
                }
 
-               if (cbp->cb_columns[i + 1] == 0)
+               if (i == (ZFS_GET_NCOLS - 1) ||
+                   cbp->cb_columns[i + 1] == GET_COL_NONE)
                        (void) printf("%s", str);
                else if (cbp->cb_scripted)
                        (void) printf("%s\t", str);
@@ -975,7 +1446,6 @@ zprop_print_one_property(const char *name, zprop_get_cbdata_t *cbp,
                        (void) printf("%-*s  ",
                            cbp->cb_colwidths[cbp->cb_columns[i]],
                            str);
-
        }
 
        (void) printf("\n");
@@ -998,21 +1468,26 @@ str2shift(libzfs_handle_t *hdl, const char *buf)
                        break;
        }
        if (i == strlen(ends)) {
-               zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
-                   "invalid numeric suffix '%s'"), buf);
+               if (hdl)
+                       zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+                           "invalid numeric suffix '%s'"), buf);
                return (-1);
        }
 
        /*
-        * We want to allow trailing 'b' characters for 'GB' or 'Mb'.  But don't
-        * allow 'BB' - that's just weird.
+        * Allow 'G' = 'GB' = 'GiB', case-insensitively.
+        * However, 'BB' and 'BiB' are disallowed.
         */
-       if (buf[1] == '\0' || (toupper(buf[1]) == 'B' && buf[2] == '\0' &&
-           toupper(buf[0]) != 'B'))
-               return (10*i);
-
-       zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
-           "invalid numeric suffix '%s'"), buf);
+       if (buf[1] == '\0' ||
+           (toupper(buf[0]) != 'B' &&
+           ((toupper(buf[1]) == 'B' && buf[2] == '\0') ||
+           (toupper(buf[1]) == 'I' && toupper(buf[2]) == 'B' &&
+           buf[3] == '\0'))))
+               return (10 * i);
+
+       if (hdl)
+               zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+                   "invalid numeric suffix '%s'"), buf);
        return (-1);
 }
 
@@ -1037,7 +1512,7 @@ zfs_nicestrtonum(libzfs_handle_t *hdl, const char *value, uint64_t *num)
                return (-1);
        }
 
-       /* Rely on stroull() to process the numeric portion.  */
+       /* Rely on strtoull() to process the numeric portion.  */
        errno = 0;
        *num = strtoull(value, &end, 10);
 
@@ -1111,6 +1586,8 @@ zprop_parse_value(libzfs_handle_t *hdl, nvpair_t *elem, int prop,
        const char *propname;
        char *value;
        boolean_t isnone = B_FALSE;
+       boolean_t isauto = B_FALSE;
+       int err = 0;
 
        if (type == ZFS_TYPE_POOL) {
                proptype = zpool_prop_get_type(prop);
@@ -1133,7 +1610,12 @@ zprop_parse_value(libzfs_handle_t *hdl, nvpair_t *elem, int prop,
                            "'%s' must be a string"), nvpair_name(elem));
                        goto error;
                }
-               (void) nvpair_value_string(elem, svalp);
+               err = nvpair_value_string(elem, svalp);
+               if (err != 0) {
+                       zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+                           "'%s' is invalid"), nvpair_name(elem));
+                       goto error;
+               }
                if (strlen(*svalp) >= ZFS_MAXPROPLEN) {
                        zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
                            "'%s' is too long"), nvpair_name(elem));
@@ -1146,8 +1628,9 @@ zprop_parse_value(libzfs_handle_t *hdl, nvpair_t *elem, int prop,
                        (void) nvpair_value_string(elem, &value);
                        if (strcmp(value, "none") == 0) {
                                isnone = B_TRUE;
-                       } else if (zfs_nicestrtonum(hdl, value, ivalp)
-                           != 0) {
+                       } else if (strcmp(value, "auto") == 0) {
+                               isauto = B_TRUE;
+                       } else if (zfs_nicestrtonum(hdl, value, ivalp) != 0) {
                                goto error;
                        }
                } else if (datatype == DATA_TYPE_UINT64) {
@@ -1167,6 +1650,41 @@ zprop_parse_value(libzfs_handle_t *hdl, nvpair_t *elem, int prop,
                            "use 'none' to disable quota/refquota"));
                        goto error;
                }
+
+               /*
+                * Special handling for "*_limit=none". In this case it's not
+                * 0 but UINT64_MAX.
+                */
+               if ((type & ZFS_TYPE_DATASET) && isnone &&
+                   (prop == ZFS_PROP_FILESYSTEM_LIMIT ||
+                   prop == ZFS_PROP_SNAPSHOT_LIMIT)) {
+                       *ivalp = UINT64_MAX;
+               }
+
+               /*
+                * Special handling for setting 'refreservation' to 'auto'.  Use
+                * UINT64_MAX to tell the caller to use zfs_fix_auto_resv().
+                * 'auto' is only allowed on volumes.
+                */
+               if (isauto) {
+                       switch (prop) {
+                       case ZFS_PROP_REFRESERVATION:
+                               if ((type & ZFS_TYPE_VOLUME) == 0) {
+                                       zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+                                           "'%s=auto' only allowed on "
+                                           "volumes"), nvpair_name(elem));
+                                       goto error;
+                               }
+                               *ivalp = UINT64_MAX;
+                               break;
+                       default:
+                               zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+                                   "'auto' is invalid value for '%s'"),
+                                   nvpair_name(elem));
+                               goto error;
+                       }
+               }
+
                break;
 
        case PROP_TYPE_INDEX:
@@ -1220,7 +1738,7 @@ addlist(libzfs_handle_t *hdl, char *propname, zprop_list_t **listp,
 
        prop = zprop_name_to_prop(propname, type);
 
-       if (prop != ZPROP_INVAL && !zprop_valid_for_type(prop, type))
+       if (prop != ZPROP_INVAL && !zprop_valid_for_type(prop, type, B_FALSE))
                prop = ZPROP_INVAL;
 
        /*
@@ -1228,8 +1746,11 @@ addlist(libzfs_handle_t *hdl, char *propname, zprop_list_t **listp,
         * this is a pool property or if this isn't a user-defined
         * dataset property,
         */
-       if (prop == ZPROP_INVAL && (type == ZFS_TYPE_POOL ||
-           (!zfs_prop_user(propname) && !zfs_prop_userquota(propname)))) {
+       if (prop == ZPROP_INVAL && ((type == ZFS_TYPE_POOL &&
+           !zpool_prop_feature(propname) &&
+           !zpool_prop_unsupported(propname)) ||
+           (type == ZFS_TYPE_DATASET && !zfs_prop_user(propname) &&
+           !zfs_prop_userquota(propname) && !zfs_prop_written(propname)))) {
                zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
                    "invalid property '%s'"), propname);
                return (zfs_error(hdl, EZFS_BADPROP,
@@ -1241,7 +1762,8 @@ addlist(libzfs_handle_t *hdl, char *propname, zprop_list_t **listp,
 
        entry->pl_prop = prop;
        if (prop == ZPROP_INVAL) {
-               if ((entry->pl_user_prop = zfs_strdup(hdl, propname)) == NULL) {
+               if ((entry->pl_user_prop = zfs_strdup(hdl, propname)) ==
+                   NULL) {
                        free(entry);
                        return (-1);
                }
@@ -1428,3 +1950,66 @@ zprop_iter(zprop_func func, void *cb, boolean_t show_all, boolean_t ordered,
 {
        return (zprop_iter_common(func, cb, show_all, ordered, type));
 }
+
+/*
+ * Fill given version buffer with zfs userland version
+ */
+void
+zfs_version_userland(char *version, int len)
+{
+       (void) strlcpy(version, ZFS_META_ALIAS, len);
+}
+
+/*
+ * Fill given version buffer with zfs kernel version read from ZFS_SYSFS_DIR
+ * Returns 0 on success, and -1 on error (with errno set)
+ */
+int
+zfs_version_kernel(char *version, int len)
+{
+       int _errno;
+       int fd;
+       int rlen;
+
+       if ((fd = open(ZFS_SYSFS_DIR "/version", O_RDONLY)) == -1)
+               return (-1);
+
+       if ((rlen = read(fd, version, len)) == -1) {
+               version[0] = '\0';
+               _errno = errno;
+               (void) close(fd);
+               errno = _errno;
+               return (-1);
+       }
+
+       version[rlen-1] = '\0';  /* discard '\n' */
+
+       if (close(fd) == -1)
+               return (-1);
+
+       return (0);
+}
+
+/*
+ * Prints both zfs userland and kernel versions
+ * Returns 0 on success, and -1 on error (with errno set)
+ */
+int
+zfs_version_print(void)
+{
+       char zver_userland[128];
+       char zver_kernel[128];
+
+       if (zfs_version_kernel(zver_kernel, sizeof (zver_kernel)) == -1) {
+               fprintf(stderr, "zfs_version_kernel() failed: %s\n",
+                   strerror(errno));
+               return (-1);
+       }
+
+       zfs_version_userland(zver_userland, sizeof (zver_userland));
+
+       (void) printf("%s\n", zver_userland);
+       (void) printf("zfs-kmod-%s\n", zver_kernel);
+
+       return (0);
+}