aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorMatt Macy <mmacy@FreeBSD.org>2020-08-24 22:48:19 +0000
committerMatt Macy <mmacy@FreeBSD.org>2020-08-24 22:48:19 +0000
commit3b0ce0e28db46d0403929aba45c682285e1ac217 (patch)
tree91721e6e5518bd0d8113dee535898f2225443411 /lib
downloadsrc-3b0ce0e28db46d0403929aba45c682285e1ac217.tar.gz
src-3b0ce0e28db46d0403929aba45c682285e1ac217.zip
Vendor import of openzfs master @ 184df27eef0abdc7ab2105b21257f753834b936bvendor/openzfs/2.0-rc0-g184df27
Sponsored by: iX Systems, Inc.
Notes
Notes: svn path=/vendor-sys/openzfs/dist/; revision=364736 svn path=/vendor-sys/openzfs/2.0-rc0-g184df27/; revision=364741; tag=vendor/openzfs/2.0-rc0-g184df27
Diffstat (limited to 'lib')
-rw-r--r--lib/Makefile.am14
-rw-r--r--lib/libavl/Makefile.am14
-rw-r--r--lib/libefi/Makefile.am12
-rw-r--r--lib/libefi/rdwr_efi.c1655
-rw-r--r--lib/libicp/Makefile.am73
-rw-r--r--lib/libnvpair/Makefile.am44
-rw-r--r--lib/libnvpair/libnvpair.c1277
-rw-r--r--lib/libnvpair/libnvpair_json.c406
-rw-r--r--lib/libnvpair/nvpair_alloc_system.c65
-rw-r--r--lib/libshare/Makefile.am25
-rw-r--r--lib/libshare/libshare.c366
-rw-r--r--lib/libshare/libshare_impl.h61
-rw-r--r--lib/libshare/nfs.h27
-rw-r--r--lib/libshare/os/freebsd/nfs.c448
-rw-r--r--lib/libshare/os/freebsd/smb.c128
-rw-r--r--lib/libshare/os/linux/nfs.c712
-rw-r--r--lib/libshare/os/linux/smb.c453
-rw-r--r--lib/libshare/smb.h49
-rw-r--r--lib/libspl/Makefile.am56
-rw-r--r--lib/libspl/asm-generic/.gitignore1
-rw-r--r--lib/libspl/asm-generic/atomic.c450
-rw-r--r--lib/libspl/asm-i386/atomic.S840
-rw-r--r--lib/libspl/asm-x86_64/atomic.S691
-rw-r--r--lib/libspl/assert.c46
-rw-r--r--lib/libspl/include/Makefile.am22
-rw-r--r--lib/libspl/include/assert.h151
-rw-r--r--lib/libspl/include/atomic.h296
-rw-r--r--lib/libspl/include/ia32/Makefile.am1
-rw-r--r--lib/libspl/include/ia32/sys/Makefile.am3
-rw-r--r--lib/libspl/include/ia32/sys/asm_linkage.h302
-rw-r--r--lib/libspl/include/libdevinfo.h30
-rw-r--r--lib/libspl/include/libgen.h35
-rw-r--r--lib/libspl/include/libshare.h86
-rw-r--r--lib/libspl/include/limits.h40
-rw-r--r--lib/libspl/include/locale.h35
-rw-r--r--lib/libspl/include/os/Makefile.am7
-rw-r--r--lib/libspl/include/os/freebsd/Makefile.am1
-rw-r--r--lib/libspl/include/os/freebsd/sys/Makefile.am11
-rw-r--r--lib/libspl/include/os/freebsd/sys/byteorder.h192
-rw-r--r--lib/libspl/include/os/freebsd/sys/file.h42
-rw-r--r--lib/libspl/include/os/freebsd/sys/mnttab.h85
-rw-r--r--lib/libspl/include/os/freebsd/sys/mount.h108
-rw-r--r--lib/libspl/include/os/freebsd/sys/param.h66
-rw-r--r--lib/libspl/include/os/freebsd/sys/stat.h71
-rw-r--r--lib/libspl/include/os/freebsd/sys/sysmacros.h1
-rw-r--r--lib/libspl/include/os/freebsd/sys/vfs.h37
-rw-r--r--lib/libspl/include/os/freebsd/sys/zfs_context_os.h34
-rw-r--r--lib/libspl/include/os/linux/Makefile.am1
-rw-r--r--lib/libspl/include/os/linux/sys/Makefile.am10
-rw-r--r--lib/libspl/include/os/linux/sys/byteorder.h223
-rw-r--r--lib/libspl/include/os/linux/sys/errno.h46
-rw-r--r--lib/libspl/include/os/linux/sys/mnttab.h89
-rw-r--r--lib/libspl/include/os/linux/sys/mount.h98
-rw-r--r--lib/libspl/include/os/linux/sys/param.h67
-rw-r--r--lib/libspl/include/os/linux/sys/stat.h50
-rw-r--r--lib/libspl/include/os/linux/sys/sysmacros.h103
-rw-r--r--lib/libspl/include/os/linux/sys/zfs_context_os.h25
-rw-r--r--lib/libspl/include/rpc/Makefile.am3
-rw-r--r--lib/libspl/include/rpc/xdr.h68
-rw-r--r--lib/libspl/include/statcommon.h41
-rw-r--r--lib/libspl/include/stdio.h34
-rw-r--r--lib/libspl/include/stdlib.h34
-rw-r--r--lib/libspl/include/string.h40
-rw-r--r--lib/libspl/include/stropts.h25
-rw-r--r--lib/libspl/include/sys/Makefile.am47
-rw-r--r--lib/libspl/include/sys/acl.h287
-rw-r--r--lib/libspl/include/sys/acl_impl.h59
-rw-r--r--lib/libspl/include/sys/callb.h30
-rw-r--r--lib/libspl/include/sys/cmn_err.h30
-rw-r--r--lib/libspl/include/sys/cred.h32
-rw-r--r--lib/libspl/include/sys/debug.h40
-rw-r--r--lib/libspl/include/sys/dkio.h483
-rw-r--r--lib/libspl/include/sys/dklabel.h268
-rw-r--r--lib/libspl/include/sys/dktp/Makefile.am4
-rw-r--r--lib/libspl/include/sys/dktp/fdisk.h173
-rw-r--r--lib/libspl/include/sys/feature_tests.h32
-rw-r--r--lib/libspl/include/sys/int_limits.h30
-rw-r--r--lib/libspl/include/sys/int_types.h32
-rw-r--r--lib/libspl/include/sys/inttypes.h34
-rw-r--r--lib/libspl/include/sys/isa_defs.h264
-rw-r--r--lib/libspl/include/sys/kmem.h45
-rw-r--r--lib/libspl/include/sys/kstat.h822
-rw-r--r--lib/libspl/include/sys/list.h65
-rw-r--r--lib/libspl/include/sys/list_impl.h51
-rw-r--r--lib/libspl/include/sys/mhd.h159
-rw-r--r--lib/libspl/include/sys/mkdev.h30
-rw-r--r--lib/libspl/include/sys/policy.h26
-rw-r--r--lib/libspl/include/sys/poll.h41
-rw-r--r--lib/libspl/include/sys/priv.h30
-rw-r--r--lib/libspl/include/sys/processor.h34
-rw-r--r--lib/libspl/include/sys/sha2.h151
-rw-r--r--lib/libspl/include/sys/simd.h502
-rw-r--r--lib/libspl/include/sys/stack.h74
-rw-r--r--lib/libspl/include/sys/stdtypes.h54
-rw-r--r--lib/libspl/include/sys/strings.h33
-rw-r--r--lib/libspl/include/sys/stropts.h29
-rw-r--r--lib/libspl/include/sys/sunddi.h29
-rw-r--r--lib/libspl/include/sys/systeminfo.h38
-rw-r--r--lib/libspl/include/sys/time.h107
-rw-r--r--lib/libspl/include/sys/trace_spl.h24
-rw-r--r--lib/libspl/include/sys/trace_zfs.h24
-rw-r--r--lib/libspl/include/sys/types.h77
-rw-r--r--lib/libspl/include/sys/types32.h89
-rw-r--r--lib/libspl/include/sys/tzfile.h164
-rw-r--r--lib/libspl/include/sys/uio.h153
-rw-r--r--lib/libspl/include/sys/va_list.h32
-rw-r--r--lib/libspl/include/sys/varargs.h30
-rw-r--r--lib/libspl/include/sys/vnode.h30
-rw-r--r--lib/libspl/include/sys/vtoc.h350
-rw-r--r--lib/libspl/include/sys/zone.h30
-rw-r--r--lib/libspl/include/thread.h30
-rw-r--r--lib/libspl/include/tzfile.h32
-rw-r--r--lib/libspl/include/ucred.h32
-rw-r--r--lib/libspl/include/umem.h208
-rw-r--r--lib/libspl/include/unistd.h39
-rw-r--r--lib/libspl/include/util/Makefile.am3
-rw-r--r--lib/libspl/include/util/sscanf.h30
-rw-r--r--lib/libspl/include/zone.h53
-rw-r--r--lib/libspl/list.c243
-rw-r--r--lib/libspl/mkdirp.c212
-rw-r--r--lib/libspl/os/freebsd/getexecname.c71
-rw-r--r--lib/libspl/os/freebsd/gethostid.c36
-rw-r--r--lib/libspl/os/freebsd/getmntany.c67
-rw-r--r--lib/libspl/os/freebsd/mnttab.c216
-rw-r--r--lib/libspl/os/linux/getexecname.c59
-rw-r--r--lib/libspl/os/linux/gethostid.c86
-rw-r--r--lib/libspl/os/linux/getmntany.c165
-rw-r--r--lib/libspl/page.c34
-rw-r--r--lib/libspl/strlcat.c60
-rw-r--r--lib/libspl/strlcpy.c56
-rw-r--r--lib/libspl/timestamp.c63
-rw-r--r--lib/libspl/zone.c63
-rw-r--r--lib/libtpool/Makefile.am9
-rw-r--r--lib/libtpool/thread_pool.c599
-rw-r--r--lib/libtpool/thread_pool_impl.h93
-rw-r--r--lib/libunicode/Makefile.am15
-rw-r--r--lib/libuutil/Makefile.am34
-rw-r--r--lib/libuutil/uu_alloc.c135
-rw-r--r--lib/libuutil/uu_avl.c569
-rw-r--r--lib/libuutil/uu_dprintf.c130
-rw-r--r--lib/libuutil/uu_ident.c122
-rw-r--r--lib/libuutil/uu_list.c718
-rw-r--r--lib/libuutil/uu_misc.c281
-rw-r--r--lib/libuutil/uu_open.c70
-rw-r--r--lib/libuutil/uu_pname.c207
-rw-r--r--lib/libuutil/uu_string.c54
-rw-r--r--lib/libzfs/.gitignore1
-rw-r--r--lib/libzfs/Makefile.am93
-rw-r--r--lib/libzfs/THIRDPARTYLICENSE.openssl127
-rw-r--r--lib/libzfs/THIRDPARTYLICENSE.openssl.descrip1
-rw-r--r--lib/libzfs/libzfs.pc.in14
-rw-r--r--lib/libzfs/libzfs_changelist.c781
-rw-r--r--lib/libzfs/libzfs_config.c457
-rw-r--r--lib/libzfs/libzfs_crypto.c1621
-rw-r--r--lib/libzfs/libzfs_dataset.c5492
-rw-r--r--lib/libzfs/libzfs_diff.c797
-rw-r--r--lib/libzfs/libzfs_import.c472
-rw-r--r--lib/libzfs/libzfs_iter.c600
-rw-r--r--lib/libzfs/libzfs_mount.c1614
-rw-r--r--lib/libzfs/libzfs_pool.c4531
-rw-r--r--lib/libzfs/libzfs_sendrecv.c5170
-rw-r--r--lib/libzfs/libzfs_status.c508
-rw-r--r--lib/libzfs/libzfs_util.c2092
-rw-r--r--lib/libzfs/os/freebsd/libzfs_compat.c321
-rw-r--r--lib/libzfs/os/freebsd/libzfs_ioctl_compat.c432
-rw-r--r--lib/libzfs/os/freebsd/libzfs_zmount.c139
-rw-r--r--lib/libzfs/os/linux/libzfs_mount_os.c411
-rw-r--r--lib/libzfs/os/linux/libzfs_pool_os.c345
-rw-r--r--lib/libzfs/os/linux/libzfs_sendrecv_os.c52
-rw-r--r--lib/libzfs/os/linux/libzfs_util_os.c215
-rw-r--r--lib/libzfs_core/.gitignore1
-rw-r--r--lib/libzfs_core/Makefile.am29
-rw-r--r--lib/libzfs_core/libzfs_core.c1644
-rw-r--r--lib/libzfs_core/libzfs_core.pc.in13
-rw-r--r--lib/libzpool/Makefile.am230
-rw-r--r--lib/libzpool/kernel.c1410
-rw-r--r--lib/libzpool/taskq.c380
-rw-r--r--lib/libzpool/util.c257
-rw-r--r--lib/libzstd/Makefile.am21
-rw-r--r--lib/libzutil/Makefile.am50
-rw-r--r--lib/libzutil/os/freebsd/zutil_compat.c124
-rw-r--r--lib/libzutil/os/freebsd/zutil_device_path_os.c132
-rw-r--r--lib/libzutil/os/freebsd/zutil_import_os.c239
-rw-r--r--lib/libzutil/os/linux/zutil_compat.c30
-rw-r--r--lib/libzutil/os/linux/zutil_device_path_os.c506
-rw-r--r--lib/libzutil/os/linux/zutil_import_os.c871
-rw-r--r--lib/libzutil/zutil_device_path.c174
-rw-r--r--lib/libzutil/zutil_import.c1587
-rw-r--r--lib/libzutil/zutil_import.h76
-rw-r--r--lib/libzutil/zutil_nicenum.c172
-rw-r--r--lib/libzutil/zutil_pool.c145
191 files changed, 54965 insertions, 0 deletions
diff --git a/lib/Makefile.am b/lib/Makefile.am
new file mode 100644
index 000000000000..dda87e41c0aa
--- /dev/null
+++ b/lib/Makefile.am
@@ -0,0 +1,14 @@
+# NB: GNU Automake Manual, Chapter 8.3.5: Libtool Convenience Libraries
+# These nine libraries are intermediary build components.
+SUBDIRS = libavl libicp libshare libspl libtpool libzstd
+
+if BUILD_LINUX
+SUBDIRS += libefi
+endif
+
+# libzutil depends on libefi if present
+SUBDIRS += libzutil libunicode
+
+# These five libraries, which are installed as the final build product,
+# incorporate the eight convenience libraries given above.
+SUBDIRS += libuutil libnvpair libzfs_core libzfs libzpool
diff --git a/lib/libavl/Makefile.am b/lib/libavl/Makefile.am
new file mode 100644
index 000000000000..6087b1d2f4a0
--- /dev/null
+++ b/lib/libavl/Makefile.am
@@ -0,0 +1,14 @@
+include $(top_srcdir)/config/Rules.am
+
+VPATH = $(top_srcdir)/module/avl/
+
+# Includes kernel code, generate warnings for large stack frames
+AM_CFLAGS += $(FRAME_LARGER_THAN)
+
+noinst_LTLIBRARIES = libavl.la
+
+KERNEL_C = \
+ avl.c
+
+nodist_libavl_la_SOURCES = \
+ $(KERNEL_C)
diff --git a/lib/libefi/Makefile.am b/lib/libefi/Makefile.am
new file mode 100644
index 000000000000..fab6c8d477a6
--- /dev/null
+++ b/lib/libefi/Makefile.am
@@ -0,0 +1,12 @@
+include $(top_srcdir)/config/Rules.am
+
+AM_CFLAGS += $(LIBUUID_CFLAGS) $(ZLIB_CFLAGS)
+
+noinst_LTLIBRARIES = libefi.la
+
+USER_C = \
+ rdwr_efi.c
+
+libefi_la_SOURCES = $(USER_C)
+
+libefi_la_LIBADD = $(LIBUUID_LIBS) $(ZLIB_LIBS)
diff --git a/lib/libefi/rdwr_efi.c b/lib/libefi/rdwr_efi.c
new file mode 100644
index 000000000000..2cb093f9601d
--- /dev/null
+++ b/lib/libefi/rdwr_efi.c
@@ -0,0 +1,1655 @@
+/*
+ * 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) 2002, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright 2012 Nexenta Systems, Inc. All rights reserved.
+ * Copyright (c) 2018 by Delphix. All rights reserved.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+#include <uuid/uuid.h>
+#include <zlib.h>
+#include <libintl.h>
+#include <sys/types.h>
+#include <sys/dkio.h>
+#include <sys/vtoc.h>
+#include <sys/mhd.h>
+#include <sys/param.h>
+#include <sys/dktp/fdisk.h>
+#include <sys/efi_partition.h>
+#include <sys/byteorder.h>
+#include <sys/vdev_disk.h>
+#include <linux/fs.h>
+
+static struct uuid_to_ptag {
+ struct uuid uuid;
+} conversion_array[] = {
+ { EFI_UNUSED },
+ { EFI_BOOT },
+ { EFI_ROOT },
+ { EFI_SWAP },
+ { EFI_USR },
+ { EFI_BACKUP },
+ { EFI_UNUSED }, /* STAND is never used */
+ { EFI_VAR },
+ { EFI_HOME },
+ { EFI_ALTSCTR },
+ { EFI_UNUSED }, /* CACHE (cachefs) is never used */
+ { EFI_RESERVED },
+ { EFI_SYSTEM },
+ { EFI_LEGACY_MBR },
+ { EFI_SYMC_PUB },
+ { EFI_SYMC_CDS },
+ { EFI_MSFT_RESV },
+ { EFI_DELL_BASIC },
+ { EFI_DELL_RAID },
+ { EFI_DELL_SWAP },
+ { EFI_DELL_LVM },
+ { EFI_DELL_RESV },
+ { EFI_AAPL_HFS },
+ { EFI_AAPL_UFS },
+ { EFI_FREEBSD_BOOT },
+ { EFI_FREEBSD_SWAP },
+ { EFI_FREEBSD_UFS },
+ { EFI_FREEBSD_VINUM },
+ { EFI_FREEBSD_ZFS },
+ { EFI_BIOS_BOOT },
+ { EFI_INTC_RS },
+ { EFI_SNE_BOOT },
+ { EFI_LENOVO_BOOT },
+ { EFI_MSFT_LDMM },
+ { EFI_MSFT_LDMD },
+ { EFI_MSFT_RE },
+ { EFI_IBM_GPFS },
+ { EFI_MSFT_STORAGESPACES },
+ { EFI_HPQ_DATA },
+ { EFI_HPQ_SVC },
+ { EFI_RHT_DATA },
+ { EFI_RHT_HOME },
+ { EFI_RHT_SRV },
+ { EFI_RHT_DMCRYPT },
+ { EFI_RHT_LUKS },
+ { EFI_FREEBSD_DISKLABEL },
+ { EFI_AAPL_RAID },
+ { EFI_AAPL_RAIDOFFLINE },
+ { EFI_AAPL_BOOT },
+ { EFI_AAPL_LABEL },
+ { EFI_AAPL_TVRECOVERY },
+ { EFI_AAPL_CORESTORAGE },
+ { EFI_NETBSD_SWAP },
+ { EFI_NETBSD_FFS },
+ { EFI_NETBSD_LFS },
+ { EFI_NETBSD_RAID },
+ { EFI_NETBSD_CAT },
+ { EFI_NETBSD_CRYPT },
+ { EFI_GOOG_KERN },
+ { EFI_GOOG_ROOT },
+ { EFI_GOOG_RESV },
+ { EFI_HAIKU_BFS },
+ { EFI_MIDNIGHTBSD_BOOT },
+ { EFI_MIDNIGHTBSD_DATA },
+ { EFI_MIDNIGHTBSD_SWAP },
+ { EFI_MIDNIGHTBSD_UFS },
+ { EFI_MIDNIGHTBSD_VINUM },
+ { EFI_MIDNIGHTBSD_ZFS },
+ { EFI_CEPH_JOURNAL },
+ { EFI_CEPH_DMCRYPTJOURNAL },
+ { EFI_CEPH_OSD },
+ { EFI_CEPH_DMCRYPTOSD },
+ { EFI_CEPH_CREATE },
+ { EFI_CEPH_DMCRYPTCREATE },
+ { EFI_OPENBSD_DISKLABEL },
+ { EFI_BBRY_QNX },
+ { EFI_BELL_PLAN9 },
+ { EFI_VMW_KCORE },
+ { EFI_VMW_VMFS },
+ { EFI_VMW_RESV },
+ { EFI_RHT_ROOTX86 },
+ { EFI_RHT_ROOTAMD64 },
+ { EFI_RHT_ROOTARM },
+ { EFI_RHT_ROOTARM64 },
+ { EFI_ACRONIS_SECUREZONE },
+ { EFI_ONIE_BOOT },
+ { EFI_ONIE_CONFIG },
+ { EFI_IBM_PPRPBOOT },
+ { EFI_FREEDESKTOP_BOOT }
+};
+
+/*
+ * Default vtoc information for non-SVr4 partitions
+ */
+struct dk_map2 default_vtoc_map[NDKMAP] = {
+ { V_ROOT, 0 }, /* a - 0 */
+ { V_SWAP, V_UNMNT }, /* b - 1 */
+ { V_BACKUP, V_UNMNT }, /* c - 2 */
+ { V_UNASSIGNED, 0 }, /* d - 3 */
+ { V_UNASSIGNED, 0 }, /* e - 4 */
+ { V_UNASSIGNED, 0 }, /* f - 5 */
+ { V_USR, 0 }, /* g - 6 */
+ { V_UNASSIGNED, 0 }, /* h - 7 */
+
+#if defined(_SUNOS_VTOC_16)
+
+#if defined(i386) || defined(__amd64) || defined(__arm) || \
+ defined(__powerpc) || defined(__sparc) || defined(__s390__) || \
+ defined(__mips__) || defined(__rv64g__)
+ { V_BOOT, V_UNMNT }, /* i - 8 */
+ { V_ALTSCTR, 0 }, /* j - 9 */
+
+#else
+#error No VTOC format defined.
+#endif /* defined(i386) */
+
+ { V_UNASSIGNED, 0 }, /* k - 10 */
+ { V_UNASSIGNED, 0 }, /* l - 11 */
+ { V_UNASSIGNED, 0 }, /* m - 12 */
+ { V_UNASSIGNED, 0 }, /* n - 13 */
+ { V_UNASSIGNED, 0 }, /* o - 14 */
+ { V_UNASSIGNED, 0 }, /* p - 15 */
+#endif /* defined(_SUNOS_VTOC_16) */
+};
+
+int efi_debug = 0;
+
+static int efi_read(int, struct dk_gpt *);
+
+/*
+ * Return a 32-bit CRC of the contents of the buffer. Pre-and-post
+ * one's conditioning will be handled by crc32() internally.
+ */
+static uint32_t
+efi_crc32(const unsigned char *buf, unsigned int size)
+{
+ uint32_t crc = crc32(0, Z_NULL, 0);
+
+ crc = crc32(crc, buf, size);
+
+ return (crc);
+}
+
+static int
+read_disk_info(int fd, diskaddr_t *capacity, uint_t *lbsize)
+{
+ int sector_size;
+ unsigned long long capacity_size;
+
+ if (ioctl(fd, BLKSSZGET, &sector_size) < 0)
+ return (-1);
+
+ if (ioctl(fd, BLKGETSIZE64, &capacity_size) < 0)
+ return (-1);
+
+ *lbsize = (uint_t)sector_size;
+ *capacity = (diskaddr_t)(capacity_size / sector_size);
+
+ return (0);
+}
+
+static int
+efi_get_info(int fd, struct dk_cinfo *dki_info)
+{
+ char *path;
+ char *dev_path;
+ int rval = 0;
+
+ memset(dki_info, 0, sizeof (*dki_info));
+
+ path = calloc(1, PATH_MAX);
+ if (path == NULL)
+ goto error;
+
+ /*
+ * The simplest way to get the partition number under linux is
+ * to parse it out of the /dev/<disk><partition> block device name.
+ * The kernel creates this using the partition number when it
+ * populates /dev/ so it may be trusted. The tricky bit here is
+ * that the naming convention is based on the block device type.
+ * So we need to take this in to account when parsing out the
+ * partition information. Another issue is that the libefi API
+ * API only provides the open fd and not the file path. To handle
+ * this realpath(3) is used to resolve the block device name from
+ * /proc/self/fd/<fd>. Aside from the partition number we collect
+ * some additional device info.
+ */
+ (void) sprintf(path, "/proc/self/fd/%d", fd);
+ dev_path = realpath(path, NULL);
+ free(path);
+
+ if (dev_path == NULL)
+ goto error;
+
+ if ((strncmp(dev_path, "/dev/sd", 7) == 0)) {
+ strcpy(dki_info->dki_cname, "sd");
+ dki_info->dki_ctype = DKC_SCSI_CCS;
+ rval = sscanf(dev_path, "/dev/%[a-zA-Z]%hu",
+ dki_info->dki_dname,
+ &dki_info->dki_partition);
+ } else if ((strncmp(dev_path, "/dev/hd", 7) == 0)) {
+ strcpy(dki_info->dki_cname, "hd");
+ dki_info->dki_ctype = DKC_DIRECT;
+ rval = sscanf(dev_path, "/dev/%[a-zA-Z]%hu",
+ dki_info->dki_dname,
+ &dki_info->dki_partition);
+ } else if ((strncmp(dev_path, "/dev/md", 7) == 0)) {
+ strcpy(dki_info->dki_cname, "pseudo");
+ dki_info->dki_ctype = DKC_MD;
+ strcpy(dki_info->dki_dname, "md");
+ rval = sscanf(dev_path, "/dev/md%[0-9]p%hu",
+ dki_info->dki_dname + 2,
+ &dki_info->dki_partition);
+ } else if ((strncmp(dev_path, "/dev/vd", 7) == 0)) {
+ strcpy(dki_info->dki_cname, "vd");
+ dki_info->dki_ctype = DKC_MD;
+ rval = sscanf(dev_path, "/dev/%[a-zA-Z]%hu",
+ dki_info->dki_dname,
+ &dki_info->dki_partition);
+ } else if ((strncmp(dev_path, "/dev/xvd", 8) == 0)) {
+ strcpy(dki_info->dki_cname, "xvd");
+ dki_info->dki_ctype = DKC_MD;
+ rval = sscanf(dev_path, "/dev/%[a-zA-Z]%hu",
+ dki_info->dki_dname,
+ &dki_info->dki_partition);
+ } else if ((strncmp(dev_path, "/dev/zd", 7) == 0)) {
+ strcpy(dki_info->dki_cname, "zd");
+ dki_info->dki_ctype = DKC_MD;
+ strcpy(dki_info->dki_dname, "zd");
+ rval = sscanf(dev_path, "/dev/zd%[0-9]p%hu",
+ dki_info->dki_dname + 2,
+ &dki_info->dki_partition);
+ } else if ((strncmp(dev_path, "/dev/dm-", 8) == 0)) {
+ strcpy(dki_info->dki_cname, "pseudo");
+ dki_info->dki_ctype = DKC_VBD;
+ strcpy(dki_info->dki_dname, "dm-");
+ rval = sscanf(dev_path, "/dev/dm-%[0-9]p%hu",
+ dki_info->dki_dname + 3,
+ &dki_info->dki_partition);
+ } else if ((strncmp(dev_path, "/dev/ram", 8) == 0)) {
+ strcpy(dki_info->dki_cname, "pseudo");
+ dki_info->dki_ctype = DKC_PCMCIA_MEM;
+ strcpy(dki_info->dki_dname, "ram");
+ rval = sscanf(dev_path, "/dev/ram%[0-9]p%hu",
+ dki_info->dki_dname + 3,
+ &dki_info->dki_partition);
+ } else if ((strncmp(dev_path, "/dev/loop", 9) == 0)) {
+ strcpy(dki_info->dki_cname, "pseudo");
+ dki_info->dki_ctype = DKC_VBD;
+ strcpy(dki_info->dki_dname, "loop");
+ rval = sscanf(dev_path, "/dev/loop%[0-9]p%hu",
+ dki_info->dki_dname + 4,
+ &dki_info->dki_partition);
+ } else if ((strncmp(dev_path, "/dev/nvme", 9) == 0)) {
+ strcpy(dki_info->dki_cname, "nvme");
+ dki_info->dki_ctype = DKC_SCSI_CCS;
+ strcpy(dki_info->dki_dname, "nvme");
+ (void) sscanf(dev_path, "/dev/nvme%[0-9]",
+ dki_info->dki_dname + 4);
+ size_t controller_length = strlen(
+ dki_info->dki_dname);
+ strcpy(dki_info->dki_dname + controller_length,
+ "n");
+ rval = sscanf(dev_path,
+ "/dev/nvme%*[0-9]n%[0-9]p%hu",
+ dki_info->dki_dname + controller_length + 1,
+ &dki_info->dki_partition);
+ } else {
+ strcpy(dki_info->dki_dname, "unknown");
+ strcpy(dki_info->dki_cname, "unknown");
+ dki_info->dki_ctype = DKC_UNKNOWN;
+ }
+
+ switch (rval) {
+ case 0:
+ errno = EINVAL;
+ goto error;
+ case 1:
+ dki_info->dki_partition = 0;
+ }
+
+ free(dev_path);
+
+ return (0);
+error:
+ if (efi_debug)
+ (void) fprintf(stderr, "DKIOCINFO errno 0x%x\n", errno);
+
+ switch (errno) {
+ case EIO:
+ return (VT_EIO);
+ case EINVAL:
+ return (VT_EINVAL);
+ default:
+ return (VT_ERROR);
+ }
+}
+
+/*
+ * the number of blocks the EFI label takes up (round up to nearest
+ * block)
+ */
+#define NBLOCKS(p, l) (1 + ((((p) * (int)sizeof (efi_gpe_t)) + \
+ ((l) - 1)) / (l)))
+/* number of partitions -- limited by what we can malloc */
+#define MAX_PARTS ((4294967295UL - sizeof (struct dk_gpt)) / \
+ sizeof (struct dk_part))
+
+int
+efi_alloc_and_init(int fd, uint32_t nparts, struct dk_gpt **vtoc)
+{
+ diskaddr_t capacity = 0;
+ uint_t lbsize = 0;
+ uint_t nblocks;
+ size_t length;
+ struct dk_gpt *vptr;
+ struct uuid uuid;
+ struct dk_cinfo dki_info;
+
+ if (read_disk_info(fd, &capacity, &lbsize) != 0)
+ return (-1);
+
+ if (efi_get_info(fd, &dki_info) != 0)
+ return (-1);
+
+ if (dki_info.dki_partition != 0)
+ return (-1);
+
+ if ((dki_info.dki_ctype == DKC_PCMCIA_MEM) ||
+ (dki_info.dki_ctype == DKC_VBD) ||
+ (dki_info.dki_ctype == DKC_UNKNOWN))
+ return (-1);
+
+ nblocks = NBLOCKS(nparts, lbsize);
+ if ((nblocks * lbsize) < EFI_MIN_ARRAY_SIZE + lbsize) {
+ /* 16K plus one block for the GPT */
+ nblocks = EFI_MIN_ARRAY_SIZE / lbsize + 1;
+ }
+
+ if (nparts > MAX_PARTS) {
+ if (efi_debug) {
+ (void) fprintf(stderr,
+ "the maximum number of partitions supported is %lu\n",
+ MAX_PARTS);
+ }
+ return (-1);
+ }
+
+ length = sizeof (struct dk_gpt) +
+ sizeof (struct dk_part) * (nparts - 1);
+
+ vptr = calloc(1, length);
+ if (vptr == NULL)
+ return (-1);
+
+ *vtoc = vptr;
+
+ vptr->efi_version = EFI_VERSION_CURRENT;
+ vptr->efi_lbasize = lbsize;
+ vptr->efi_nparts = nparts;
+ /*
+ * add one block here for the PMBR; on disks with a 512 byte
+ * block size and 128 or fewer partitions, efi_first_u_lba
+ * should work out to "34"
+ */
+ vptr->efi_first_u_lba = nblocks + 1;
+ vptr->efi_last_lba = capacity - 1;
+ vptr->efi_altern_lba = capacity -1;
+ vptr->efi_last_u_lba = vptr->efi_last_lba - nblocks;
+
+ (void) uuid_generate((uchar_t *)&uuid);
+ UUID_LE_CONVERT(vptr->efi_disk_uguid, uuid);
+ return (0);
+}
+
+/*
+ * Read EFI - return partition number upon success.
+ */
+int
+efi_alloc_and_read(int fd, struct dk_gpt **vtoc)
+{
+ int rval;
+ uint32_t nparts;
+ int length;
+ struct dk_gpt *vptr;
+
+ /* figure out the number of entries that would fit into 16K */
+ nparts = EFI_MIN_ARRAY_SIZE / sizeof (efi_gpe_t);
+ length = (int) sizeof (struct dk_gpt) +
+ (int) sizeof (struct dk_part) * (nparts - 1);
+ vptr = calloc(1, length);
+
+ if (vptr == NULL)
+ return (VT_ERROR);
+
+ vptr->efi_nparts = nparts;
+ rval = efi_read(fd, vptr);
+
+ if ((rval == VT_EINVAL) && vptr->efi_nparts > nparts) {
+ void *tmp;
+ length = (int) sizeof (struct dk_gpt) +
+ (int) sizeof (struct dk_part) * (vptr->efi_nparts - 1);
+ nparts = vptr->efi_nparts;
+ if ((tmp = realloc(vptr, length)) == NULL) {
+ free(vptr);
+ *vtoc = NULL;
+ return (VT_ERROR);
+ } else {
+ vptr = tmp;
+ rval = efi_read(fd, vptr);
+ }
+ }
+
+ if (rval < 0) {
+ if (efi_debug) {
+ (void) fprintf(stderr,
+ "read of EFI table failed, rval=%d\n", rval);
+ }
+ free(vptr);
+ *vtoc = NULL;
+ } else {
+ *vtoc = vptr;
+ }
+
+ return (rval);
+}
+
+static int
+efi_ioctl(int fd, int cmd, dk_efi_t *dk_ioc)
+{
+ void *data = dk_ioc->dki_data;
+ int error;
+ diskaddr_t capacity;
+ uint_t lbsize;
+
+ /*
+ * When the IO is not being performed in kernel as an ioctl we need
+ * to know the sector size so we can seek to the proper byte offset.
+ */
+ if (read_disk_info(fd, &capacity, &lbsize) == -1) {
+ if (efi_debug)
+ fprintf(stderr, "unable to read disk info: %d", errno);
+
+ errno = EIO;
+ return (-1);
+ }
+
+ switch (cmd) {
+ case DKIOCGETEFI:
+ if (lbsize == 0) {
+ if (efi_debug)
+ (void) fprintf(stderr, "DKIOCGETEFI assuming "
+ "LBA %d bytes\n", DEV_BSIZE);
+
+ lbsize = DEV_BSIZE;
+ }
+
+ error = lseek(fd, dk_ioc->dki_lba * lbsize, SEEK_SET);
+ if (error == -1) {
+ if (efi_debug)
+ (void) fprintf(stderr, "DKIOCGETEFI lseek "
+ "error: %d\n", errno);
+ return (error);
+ }
+
+ error = read(fd, data, dk_ioc->dki_length);
+ if (error == -1) {
+ if (efi_debug)
+ (void) fprintf(stderr, "DKIOCGETEFI read "
+ "error: %d\n", errno);
+ return (error);
+ }
+
+ if (error != dk_ioc->dki_length) {
+ if (efi_debug)
+ (void) fprintf(stderr, "DKIOCGETEFI short "
+ "read of %d bytes\n", error);
+ errno = EIO;
+ return (-1);
+ }
+ error = 0;
+ break;
+
+ case DKIOCSETEFI:
+ if (lbsize == 0) {
+ if (efi_debug)
+ (void) fprintf(stderr, "DKIOCSETEFI unknown "
+ "LBA size\n");
+ errno = EIO;
+ return (-1);
+ }
+
+ error = lseek(fd, dk_ioc->dki_lba * lbsize, SEEK_SET);
+ if (error == -1) {
+ if (efi_debug)
+ (void) fprintf(stderr, "DKIOCSETEFI lseek "
+ "error: %d\n", errno);
+ return (error);
+ }
+
+ error = write(fd, data, dk_ioc->dki_length);
+ if (error == -1) {
+ if (efi_debug)
+ (void) fprintf(stderr, "DKIOCSETEFI write "
+ "error: %d\n", errno);
+ return (error);
+ }
+
+ if (error != dk_ioc->dki_length) {
+ if (efi_debug)
+ (void) fprintf(stderr, "DKIOCSETEFI short "
+ "write of %d bytes\n", error);
+ errno = EIO;
+ return (-1);
+ }
+
+ /* Sync the new EFI table to disk */
+ error = fsync(fd);
+ if (error == -1)
+ return (error);
+
+ /* Ensure any local disk cache is also flushed */
+ if (ioctl(fd, BLKFLSBUF, 0) == -1)
+ return (error);
+
+ error = 0;
+ break;
+
+ default:
+ if (efi_debug)
+ (void) fprintf(stderr, "unsupported ioctl()\n");
+
+ errno = EIO;
+ return (-1);
+ }
+
+ return (error);
+}
+
+int
+efi_rescan(int fd)
+{
+ int retry = 10;
+ int error;
+
+ /* Notify the kernel a devices partition table has been updated */
+ while ((error = ioctl(fd, BLKRRPART)) != 0) {
+ if ((--retry == 0) || (errno != EBUSY)) {
+ (void) fprintf(stderr, "the kernel failed to rescan "
+ "the partition table: %d\n", errno);
+ return (-1);
+ }
+ usleep(50000);
+ }
+
+ return (0);
+}
+
+static int
+check_label(int fd, dk_efi_t *dk_ioc)
+{
+ efi_gpt_t *efi;
+ uint_t crc;
+
+ if (efi_ioctl(fd, DKIOCGETEFI, dk_ioc) == -1) {
+ switch (errno) {
+ case EIO:
+ return (VT_EIO);
+ default:
+ return (VT_ERROR);
+ }
+ }
+ efi = dk_ioc->dki_data;
+ if (efi->efi_gpt_Signature != LE_64(EFI_SIGNATURE)) {
+ if (efi_debug)
+ (void) fprintf(stderr,
+ "Bad EFI signature: 0x%llx != 0x%llx\n",
+ (long long)efi->efi_gpt_Signature,
+ (long long)LE_64(EFI_SIGNATURE));
+ return (VT_EINVAL);
+ }
+
+ /*
+ * check CRC of the header; the size of the header should
+ * never be larger than one block
+ */
+ crc = efi->efi_gpt_HeaderCRC32;
+ efi->efi_gpt_HeaderCRC32 = 0;
+ len_t headerSize = (len_t)LE_32(efi->efi_gpt_HeaderSize);
+
+ if (headerSize < EFI_MIN_LABEL_SIZE || headerSize > EFI_LABEL_SIZE) {
+ if (efi_debug)
+ (void) fprintf(stderr,
+ "Invalid EFI HeaderSize %llu. Assuming %d.\n",
+ headerSize, EFI_MIN_LABEL_SIZE);
+ }
+
+ if ((headerSize > dk_ioc->dki_length) ||
+ crc != LE_32(efi_crc32((unsigned char *)efi, headerSize))) {
+ if (efi_debug)
+ (void) fprintf(stderr,
+ "Bad EFI CRC: 0x%x != 0x%x\n",
+ crc, LE_32(efi_crc32((unsigned char *)efi,
+ headerSize)));
+ return (VT_EINVAL);
+ }
+
+ return (0);
+}
+
+static int
+efi_read(int fd, struct dk_gpt *vtoc)
+{
+ int i, j;
+ int label_len;
+ int rval = 0;
+ int md_flag = 0;
+ int vdc_flag = 0;
+ diskaddr_t capacity = 0;
+ uint_t lbsize = 0;
+ struct dk_minfo disk_info;
+ dk_efi_t dk_ioc;
+ efi_gpt_t *efi;
+ efi_gpe_t *efi_parts;
+ struct dk_cinfo dki_info;
+ uint32_t user_length;
+ boolean_t legacy_label = B_FALSE;
+
+ /*
+ * get the partition number for this file descriptor.
+ */
+ if ((rval = efi_get_info(fd, &dki_info)) != 0)
+ return (rval);
+
+ if ((strncmp(dki_info.dki_cname, "pseudo", 7) == 0) &&
+ (strncmp(dki_info.dki_dname, "md", 3) == 0)) {
+ md_flag++;
+ } else if ((strncmp(dki_info.dki_cname, "vdc", 4) == 0) &&
+ (strncmp(dki_info.dki_dname, "vdc", 4) == 0)) {
+ /*
+ * The controller and drive name "vdc" (virtual disk client)
+ * indicates a LDoms virtual disk.
+ */
+ vdc_flag++;
+ }
+
+ /* get the LBA size */
+ if (read_disk_info(fd, &capacity, &lbsize) == -1) {
+ if (efi_debug) {
+ (void) fprintf(stderr,
+ "unable to read disk info: %d",
+ errno);
+ }
+ return (VT_EINVAL);
+ }
+
+ disk_info.dki_lbsize = lbsize;
+ disk_info.dki_capacity = capacity;
+
+ if (disk_info.dki_lbsize == 0) {
+ if (efi_debug) {
+ (void) fprintf(stderr,
+ "efi_read: assuming LBA 512 bytes\n");
+ }
+ disk_info.dki_lbsize = DEV_BSIZE;
+ }
+ /*
+ * Read the EFI GPT to figure out how many partitions we need
+ * to deal with.
+ */
+ dk_ioc.dki_lba = 1;
+ if (NBLOCKS(vtoc->efi_nparts, disk_info.dki_lbsize) < 34) {
+ label_len = EFI_MIN_ARRAY_SIZE + disk_info.dki_lbsize;
+ } else {
+ label_len = vtoc->efi_nparts * (int) sizeof (efi_gpe_t) +
+ disk_info.dki_lbsize;
+ if (label_len % disk_info.dki_lbsize) {
+ /* pad to physical sector size */
+ label_len += disk_info.dki_lbsize;
+ label_len &= ~(disk_info.dki_lbsize - 1);
+ }
+ }
+
+ if (posix_memalign((void **)&dk_ioc.dki_data,
+ disk_info.dki_lbsize, label_len))
+ return (VT_ERROR);
+
+ memset(dk_ioc.dki_data, 0, label_len);
+ dk_ioc.dki_length = disk_info.dki_lbsize;
+ user_length = vtoc->efi_nparts;
+ efi = dk_ioc.dki_data;
+ if (md_flag) {
+ dk_ioc.dki_length = label_len;
+ if (efi_ioctl(fd, DKIOCGETEFI, &dk_ioc) == -1) {
+ switch (errno) {
+ case EIO:
+ return (VT_EIO);
+ default:
+ return (VT_ERROR);
+ }
+ }
+ } else if ((rval = check_label(fd, &dk_ioc)) == VT_EINVAL) {
+ /*
+ * No valid label here; try the alternate. Note that here
+ * we just read GPT header and save it into dk_ioc.data,
+ * Later, we will read GUID partition entry array if we
+ * can get valid GPT header.
+ */
+
+ /*
+ * This is a workaround for legacy systems. In the past, the
+ * last sector of SCSI disk was invisible on x86 platform. At
+ * that time, backup label was saved on the next to the last
+ * sector. It is possible for users to move a disk from previous
+ * solaris system to present system. Here, we attempt to search
+ * legacy backup EFI label first.
+ */
+ dk_ioc.dki_lba = disk_info.dki_capacity - 2;
+ dk_ioc.dki_length = disk_info.dki_lbsize;
+ rval = check_label(fd, &dk_ioc);
+ if (rval == VT_EINVAL) {
+ /*
+ * we didn't find legacy backup EFI label, try to
+ * search backup EFI label in the last block.
+ */
+ dk_ioc.dki_lba = disk_info.dki_capacity - 1;
+ dk_ioc.dki_length = disk_info.dki_lbsize;
+ rval = check_label(fd, &dk_ioc);
+ if (rval == 0) {
+ legacy_label = B_TRUE;
+ if (efi_debug)
+ (void) fprintf(stderr,
+ "efi_read: primary label corrupt; "
+ "using EFI backup label located on"
+ " the last block\n");
+ }
+ } else {
+ if ((efi_debug) && (rval == 0))
+ (void) fprintf(stderr, "efi_read: primary label"
+ " corrupt; using legacy EFI backup label "
+ " located on the next to last block\n");
+ }
+
+ if (rval == 0) {
+ dk_ioc.dki_lba = LE_64(efi->efi_gpt_PartitionEntryLBA);
+ vtoc->efi_flags |= EFI_GPT_PRIMARY_CORRUPT;
+ vtoc->efi_nparts =
+ LE_32(efi->efi_gpt_NumberOfPartitionEntries);
+ /*
+ * Partition tables are between backup GPT header
+ * table and ParitionEntryLBA (the starting LBA of
+ * the GUID partition entries array). Now that we
+ * already got valid GPT header and saved it in
+ * dk_ioc.dki_data, we try to get GUID partition
+ * entry array here.
+ */
+ /* LINTED */
+ dk_ioc.dki_data = (efi_gpt_t *)((char *)dk_ioc.dki_data
+ + disk_info.dki_lbsize);
+ if (legacy_label)
+ dk_ioc.dki_length = disk_info.dki_capacity - 1 -
+ dk_ioc.dki_lba;
+ else
+ dk_ioc.dki_length = disk_info.dki_capacity - 2 -
+ dk_ioc.dki_lba;
+ dk_ioc.dki_length *= disk_info.dki_lbsize;
+ if (dk_ioc.dki_length >
+ ((len_t)label_len - sizeof (*dk_ioc.dki_data))) {
+ rval = VT_EINVAL;
+ } else {
+ /*
+ * read GUID partition entry array
+ */
+ rval = efi_ioctl(fd, DKIOCGETEFI, &dk_ioc);
+ }
+ }
+
+ } else if (rval == 0) {
+
+ dk_ioc.dki_lba = LE_64(efi->efi_gpt_PartitionEntryLBA);
+ /* LINTED */
+ dk_ioc.dki_data = (efi_gpt_t *)((char *)dk_ioc.dki_data
+ + disk_info.dki_lbsize);
+ dk_ioc.dki_length = label_len - disk_info.dki_lbsize;
+ rval = efi_ioctl(fd, DKIOCGETEFI, &dk_ioc);
+
+ } else if (vdc_flag && rval == VT_ERROR && errno == EINVAL) {
+ /*
+ * When the device is a LDoms virtual disk, the DKIOCGETEFI
+ * ioctl can fail with EINVAL if the virtual disk backend
+ * is a ZFS volume serviced by a domain running an old version
+ * of Solaris. This is because the DKIOCGETEFI ioctl was
+ * initially incorrectly implemented for a ZFS volume and it
+ * expected the GPT and GPE to be retrieved with a single ioctl.
+ * So we try to read the GPT and the GPE using that old style
+ * ioctl.
+ */
+ dk_ioc.dki_lba = 1;
+ dk_ioc.dki_length = label_len;
+ rval = check_label(fd, &dk_ioc);
+ }
+
+ if (rval < 0) {
+ free(efi);
+ return (rval);
+ }
+
+ /* LINTED -- always longlong aligned */
+ efi_parts = (efi_gpe_t *)(((char *)efi) + disk_info.dki_lbsize);
+
+ /*
+ * Assemble this into a "dk_gpt" struct for easier
+ * digestibility by applications.
+ */
+ vtoc->efi_version = LE_32(efi->efi_gpt_Revision);
+ vtoc->efi_nparts = LE_32(efi->efi_gpt_NumberOfPartitionEntries);
+ vtoc->efi_part_size = LE_32(efi->efi_gpt_SizeOfPartitionEntry);
+ vtoc->efi_lbasize = disk_info.dki_lbsize;
+ vtoc->efi_last_lba = disk_info.dki_capacity - 1;
+ vtoc->efi_first_u_lba = LE_64(efi->efi_gpt_FirstUsableLBA);
+ vtoc->efi_last_u_lba = LE_64(efi->efi_gpt_LastUsableLBA);
+ vtoc->efi_altern_lba = LE_64(efi->efi_gpt_AlternateLBA);
+ UUID_LE_CONVERT(vtoc->efi_disk_uguid, efi->efi_gpt_DiskGUID);
+
+ /*
+ * If the array the user passed in is too small, set the length
+ * to what it needs to be and return
+ */
+ if (user_length < vtoc->efi_nparts) {
+ return (VT_EINVAL);
+ }
+
+ for (i = 0; i < vtoc->efi_nparts; i++) {
+
+ UUID_LE_CONVERT(vtoc->efi_parts[i].p_guid,
+ efi_parts[i].efi_gpe_PartitionTypeGUID);
+
+ for (j = 0;
+ j < sizeof (conversion_array)
+ / sizeof (struct uuid_to_ptag); j++) {
+
+ if (bcmp(&vtoc->efi_parts[i].p_guid,
+ &conversion_array[j].uuid,
+ sizeof (struct uuid)) == 0) {
+ vtoc->efi_parts[i].p_tag = j;
+ break;
+ }
+ }
+ if (vtoc->efi_parts[i].p_tag == V_UNASSIGNED)
+ continue;
+ vtoc->efi_parts[i].p_flag =
+ LE_16(efi_parts[i].efi_gpe_Attributes.PartitionAttrs);
+ vtoc->efi_parts[i].p_start =
+ LE_64(efi_parts[i].efi_gpe_StartingLBA);
+ vtoc->efi_parts[i].p_size =
+ LE_64(efi_parts[i].efi_gpe_EndingLBA) -
+ vtoc->efi_parts[i].p_start + 1;
+ for (j = 0; j < EFI_PART_NAME_LEN; j++) {
+ vtoc->efi_parts[i].p_name[j] =
+ (uchar_t)LE_16(
+ efi_parts[i].efi_gpe_PartitionName[j]);
+ }
+
+ UUID_LE_CONVERT(vtoc->efi_parts[i].p_uguid,
+ efi_parts[i].efi_gpe_UniquePartitionGUID);
+ }
+ free(efi);
+
+ return (dki_info.dki_partition);
+}
+
+/* writes a "protective" MBR */
+static int
+write_pmbr(int fd, struct dk_gpt *vtoc)
+{
+ dk_efi_t dk_ioc;
+ struct mboot mb;
+ uchar_t *cp;
+ diskaddr_t size_in_lba;
+ uchar_t *buf;
+ int len;
+
+ len = (vtoc->efi_lbasize == 0) ? sizeof (mb) : vtoc->efi_lbasize;
+ if (posix_memalign((void **)&buf, len, len))
+ return (VT_ERROR);
+
+ /*
+ * Preserve any boot code and disk signature if the first block is
+ * already an MBR.
+ */
+ memset(buf, 0, len);
+ dk_ioc.dki_lba = 0;
+ dk_ioc.dki_length = len;
+ /* LINTED -- always longlong aligned */
+ dk_ioc.dki_data = (efi_gpt_t *)buf;
+ if (efi_ioctl(fd, DKIOCGETEFI, &dk_ioc) == -1) {
+ (void) memcpy(&mb, buf, sizeof (mb));
+ bzero(&mb, sizeof (mb));
+ mb.signature = LE_16(MBB_MAGIC);
+ } else {
+ (void) memcpy(&mb, buf, sizeof (mb));
+ if (mb.signature != LE_16(MBB_MAGIC)) {
+ bzero(&mb, sizeof (mb));
+ mb.signature = LE_16(MBB_MAGIC);
+ }
+ }
+
+ bzero(&mb.parts, sizeof (mb.parts));
+ cp = (uchar_t *)&mb.parts[0];
+ /* bootable or not */
+ *cp++ = 0;
+ /* beginning CHS; 0xffffff if not representable */
+ *cp++ = 0xff;
+ *cp++ = 0xff;
+ *cp++ = 0xff;
+ /* OS type */
+ *cp++ = EFI_PMBR;
+ /* ending CHS; 0xffffff if not representable */
+ *cp++ = 0xff;
+ *cp++ = 0xff;
+ *cp++ = 0xff;
+ /* starting LBA: 1 (little endian format) by EFI definition */
+ *cp++ = 0x01;
+ *cp++ = 0x00;
+ *cp++ = 0x00;
+ *cp++ = 0x00;
+ /* ending LBA: last block on the disk (little endian format) */
+ size_in_lba = vtoc->efi_last_lba;
+ if (size_in_lba < 0xffffffff) {
+ *cp++ = (size_in_lba & 0x000000ff);
+ *cp++ = (size_in_lba & 0x0000ff00) >> 8;
+ *cp++ = (size_in_lba & 0x00ff0000) >> 16;
+ *cp++ = (size_in_lba & 0xff000000) >> 24;
+ } else {
+ *cp++ = 0xff;
+ *cp++ = 0xff;
+ *cp++ = 0xff;
+ *cp++ = 0xff;
+ }
+
+ (void) memcpy(buf, &mb, sizeof (mb));
+ /* LINTED -- always longlong aligned */
+ dk_ioc.dki_data = (efi_gpt_t *)buf;
+ dk_ioc.dki_lba = 0;
+ dk_ioc.dki_length = len;
+ if (efi_ioctl(fd, DKIOCSETEFI, &dk_ioc) == -1) {
+ free(buf);
+ switch (errno) {
+ case EIO:
+ return (VT_EIO);
+ case EINVAL:
+ return (VT_EINVAL);
+ default:
+ return (VT_ERROR);
+ }
+ }
+ free(buf);
+ return (0);
+}
+
+/* make sure the user specified something reasonable */
+static int
+check_input(struct dk_gpt *vtoc)
+{
+ int resv_part = -1;
+ int i, j;
+ diskaddr_t istart, jstart, isize, jsize, endsect;
+
+ /*
+ * Sanity-check the input (make sure no partitions overlap)
+ */
+ for (i = 0; i < vtoc->efi_nparts; i++) {
+ /* It can't be unassigned and have an actual size */
+ if ((vtoc->efi_parts[i].p_tag == V_UNASSIGNED) &&
+ (vtoc->efi_parts[i].p_size != 0)) {
+ if (efi_debug) {
+ (void) fprintf(stderr, "partition %d is "
+ "\"unassigned\" but has a size of %llu",
+ i, vtoc->efi_parts[i].p_size);
+ }
+ return (VT_EINVAL);
+ }
+ if (vtoc->efi_parts[i].p_tag == V_UNASSIGNED) {
+ if (uuid_is_null((uchar_t *)&vtoc->efi_parts[i].p_guid))
+ continue;
+ /* we have encountered an unknown uuid */
+ vtoc->efi_parts[i].p_tag = 0xff;
+ }
+ if (vtoc->efi_parts[i].p_tag == V_RESERVED) {
+ if (resv_part != -1) {
+ if (efi_debug) {
+ (void) fprintf(stderr, "found "
+ "duplicate reserved partition "
+ "at %d\n", i);
+ }
+ return (VT_EINVAL);
+ }
+ resv_part = i;
+ }
+ if ((vtoc->efi_parts[i].p_start < vtoc->efi_first_u_lba) ||
+ (vtoc->efi_parts[i].p_start > vtoc->efi_last_u_lba)) {
+ if (efi_debug) {
+ (void) fprintf(stderr,
+ "Partition %d starts at %llu. ",
+ i,
+ vtoc->efi_parts[i].p_start);
+ (void) fprintf(stderr,
+ "It must be between %llu and %llu.\n",
+ vtoc->efi_first_u_lba,
+ vtoc->efi_last_u_lba);
+ }
+ return (VT_EINVAL);
+ }
+ if ((vtoc->efi_parts[i].p_start +
+ vtoc->efi_parts[i].p_size <
+ vtoc->efi_first_u_lba) ||
+ (vtoc->efi_parts[i].p_start +
+ vtoc->efi_parts[i].p_size >
+ vtoc->efi_last_u_lba + 1)) {
+ if (efi_debug) {
+ (void) fprintf(stderr,
+ "Partition %d ends at %llu. ",
+ i,
+ vtoc->efi_parts[i].p_start +
+ vtoc->efi_parts[i].p_size);
+ (void) fprintf(stderr,
+ "It must be between %llu and %llu.\n",
+ vtoc->efi_first_u_lba,
+ vtoc->efi_last_u_lba);
+ }
+ return (VT_EINVAL);
+ }
+
+ for (j = 0; j < vtoc->efi_nparts; j++) {
+ isize = vtoc->efi_parts[i].p_size;
+ jsize = vtoc->efi_parts[j].p_size;
+ istart = vtoc->efi_parts[i].p_start;
+ jstart = vtoc->efi_parts[j].p_start;
+ if ((i != j) && (isize != 0) && (jsize != 0)) {
+ endsect = jstart + jsize -1;
+ if ((jstart <= istart) &&
+ (istart <= endsect)) {
+ if (efi_debug) {
+ (void) fprintf(stderr,
+ "Partition %d overlaps "
+ "partition %d.", i, j);
+ }
+ return (VT_EINVAL);
+ }
+ }
+ }
+ }
+ /* just a warning for now */
+ if ((resv_part == -1) && efi_debug) {
+ (void) fprintf(stderr,
+ "no reserved partition found\n");
+ }
+ return (0);
+}
+
+/*
+ * add all the unallocated space to the current label
+ */
+int
+efi_use_whole_disk(int fd)
+{
+ struct dk_gpt *efi_label = NULL;
+ int rval;
+ int i;
+ uint_t resv_index = 0, data_index = 0;
+ diskaddr_t resv_start = 0, data_start = 0;
+ diskaddr_t data_size, limit, difference;
+ boolean_t sync_needed = B_FALSE;
+ uint_t nblocks;
+
+ rval = efi_alloc_and_read(fd, &efi_label);
+ if (rval < 0) {
+ if (efi_label != NULL)
+ efi_free(efi_label);
+ return (rval);
+ }
+
+ /*
+ * Find the last physically non-zero partition.
+ * This should be the reserved partition.
+ */
+ for (i = 0; i < efi_label->efi_nparts; i ++) {
+ if (resv_start < efi_label->efi_parts[i].p_start) {
+ resv_start = efi_label->efi_parts[i].p_start;
+ resv_index = i;
+ }
+ }
+
+ /*
+ * Find the last physically non-zero partition before that.
+ * This is the data partition.
+ */
+ for (i = 0; i < resv_index; i ++) {
+ if (data_start < efi_label->efi_parts[i].p_start) {
+ data_start = efi_label->efi_parts[i].p_start;
+ data_index = i;
+ }
+ }
+ data_size = efi_label->efi_parts[data_index].p_size;
+
+ /*
+ * See the "efi_alloc_and_init" function for more information
+ * about where this "nblocks" value comes from.
+ */
+ nblocks = efi_label->efi_first_u_lba - 1;
+
+ /*
+ * Determine if the EFI label is out of sync. We check that:
+ *
+ * 1. the data partition ends at the limit we set, and
+ * 2. the reserved partition starts at the limit we set.
+ *
+ * If either of these conditions is not met, then we need to
+ * resync the EFI label.
+ *
+ * The limit is the last usable LBA, determined by the last LBA
+ * and the first usable LBA fields on the EFI label of the disk
+ * (see the lines directly above). Additionally, we factor in
+ * EFI_MIN_RESV_SIZE (per its use in "zpool_label_disk") and
+ * P2ALIGN it to ensure the partition boundaries are aligned
+ * (for performance reasons). The alignment should match the
+ * alignment used by the "zpool_label_disk" function.
+ */
+ limit = P2ALIGN(efi_label->efi_last_lba - nblocks - EFI_MIN_RESV_SIZE,
+ PARTITION_END_ALIGNMENT);
+ if (data_start + data_size != limit || resv_start != limit)
+ sync_needed = B_TRUE;
+
+ if (efi_debug && sync_needed)
+ (void) fprintf(stderr, "efi_use_whole_disk: sync needed\n");
+
+ /*
+ * If alter_lba is 1, we are using the backup label.
+ * Since we can locate the backup label by disk capacity,
+ * there must be no unallocated space.
+ */
+ if ((efi_label->efi_altern_lba == 1) || (efi_label->efi_altern_lba
+ >= efi_label->efi_last_lba && !sync_needed)) {
+ if (efi_debug) {
+ (void) fprintf(stderr,
+ "efi_use_whole_disk: requested space not found\n");
+ }
+ efi_free(efi_label);
+ return (VT_ENOSPC);
+ }
+
+ /*
+ * Verify that we've found the reserved partition by checking
+ * that it looks the way it did when we created it in zpool_label_disk.
+ * If we've found the incorrect partition, then we know that this
+ * device was reformatted and no longer is solely used by ZFS.
+ */
+ if ((efi_label->efi_parts[resv_index].p_size != EFI_MIN_RESV_SIZE) ||
+ (efi_label->efi_parts[resv_index].p_tag != V_RESERVED) ||
+ (resv_index != 8)) {
+ if (efi_debug) {
+ (void) fprintf(stderr,
+ "efi_use_whole_disk: wholedisk not available\n");
+ }
+ efi_free(efi_label);
+ return (VT_ENOSPC);
+ }
+
+ if (data_start + data_size != resv_start) {
+ if (efi_debug) {
+ (void) fprintf(stderr,
+ "efi_use_whole_disk: "
+ "data_start (%lli) + "
+ "data_size (%lli) != "
+ "resv_start (%lli)\n",
+ data_start, data_size, resv_start);
+ }
+
+ return (VT_EINVAL);
+ }
+
+ if (limit < resv_start) {
+ if (efi_debug) {
+ (void) fprintf(stderr,
+ "efi_use_whole_disk: "
+ "limit (%lli) < resv_start (%lli)\n",
+ limit, resv_start);
+ }
+
+ return (VT_EINVAL);
+ }
+
+ difference = limit - resv_start;
+
+ if (efi_debug)
+ (void) fprintf(stderr,
+ "efi_use_whole_disk: difference is %lli\n", difference);
+
+ /*
+ * Move the reserved partition. There is currently no data in
+ * here except fabricated devids (which get generated via
+ * efi_write()). So there is no need to copy data.
+ */
+ efi_label->efi_parts[data_index].p_size += difference;
+ efi_label->efi_parts[resv_index].p_start += difference;
+ efi_label->efi_last_u_lba = efi_label->efi_last_lba - nblocks;
+
+ rval = efi_write(fd, efi_label);
+ if (rval < 0) {
+ if (efi_debug) {
+ (void) fprintf(stderr,
+ "efi_use_whole_disk:fail to write label, rval=%d\n",
+ rval);
+ }
+ efi_free(efi_label);
+ return (rval);
+ }
+
+ efi_free(efi_label);
+ return (0);
+}
+
+/*
+ * write EFI label and backup label
+ */
+int
+efi_write(int fd, struct dk_gpt *vtoc)
+{
+ dk_efi_t dk_ioc;
+ efi_gpt_t *efi;
+ efi_gpe_t *efi_parts;
+ int i, j;
+ struct dk_cinfo dki_info;
+ int rval;
+ int md_flag = 0;
+ int nblocks;
+ diskaddr_t lba_backup_gpt_hdr;
+
+ if ((rval = efi_get_info(fd, &dki_info)) != 0)
+ return (rval);
+
+ /* check if we are dealing with a metadevice */
+ if ((strncmp(dki_info.dki_cname, "pseudo", 7) == 0) &&
+ (strncmp(dki_info.dki_dname, "md", 3) == 0)) {
+ md_flag = 1;
+ }
+
+ if (check_input(vtoc)) {
+ /*
+ * not valid; if it's a metadevice just pass it down
+ * because SVM will do its own checking
+ */
+ if (md_flag == 0) {
+ return (VT_EINVAL);
+ }
+ }
+
+ dk_ioc.dki_lba = 1;
+ if (NBLOCKS(vtoc->efi_nparts, vtoc->efi_lbasize) < 34) {
+ dk_ioc.dki_length = EFI_MIN_ARRAY_SIZE + vtoc->efi_lbasize;
+ } else {
+ dk_ioc.dki_length = NBLOCKS(vtoc->efi_nparts,
+ vtoc->efi_lbasize) *
+ vtoc->efi_lbasize;
+ }
+
+ /*
+ * the number of blocks occupied by GUID partition entry array
+ */
+ nblocks = dk_ioc.dki_length / vtoc->efi_lbasize - 1;
+
+ /*
+ * Backup GPT header is located on the block after GUID
+ * partition entry array. Here, we calculate the address
+ * for backup GPT header.
+ */
+ lba_backup_gpt_hdr = vtoc->efi_last_u_lba + 1 + nblocks;
+ if (posix_memalign((void **)&dk_ioc.dki_data,
+ vtoc->efi_lbasize, dk_ioc.dki_length))
+ return (VT_ERROR);
+
+ memset(dk_ioc.dki_data, 0, dk_ioc.dki_length);
+ efi = dk_ioc.dki_data;
+
+ /* stuff user's input into EFI struct */
+ efi->efi_gpt_Signature = LE_64(EFI_SIGNATURE);
+ efi->efi_gpt_Revision = LE_32(vtoc->efi_version); /* 0x02000100 */
+ efi->efi_gpt_HeaderSize = LE_32(sizeof (struct efi_gpt) - LEN_EFI_PAD);
+ efi->efi_gpt_Reserved1 = 0;
+ efi->efi_gpt_MyLBA = LE_64(1ULL);
+ efi->efi_gpt_AlternateLBA = LE_64(lba_backup_gpt_hdr);
+ efi->efi_gpt_FirstUsableLBA = LE_64(vtoc->efi_first_u_lba);
+ efi->efi_gpt_LastUsableLBA = LE_64(vtoc->efi_last_u_lba);
+ efi->efi_gpt_PartitionEntryLBA = LE_64(2ULL);
+ efi->efi_gpt_NumberOfPartitionEntries = LE_32(vtoc->efi_nparts);
+ efi->efi_gpt_SizeOfPartitionEntry = LE_32(sizeof (struct efi_gpe));
+ UUID_LE_CONVERT(efi->efi_gpt_DiskGUID, vtoc->efi_disk_uguid);
+
+ /* LINTED -- always longlong aligned */
+ efi_parts = (efi_gpe_t *)((char *)dk_ioc.dki_data + vtoc->efi_lbasize);
+
+ for (i = 0; i < vtoc->efi_nparts; i++) {
+ for (j = 0;
+ j < sizeof (conversion_array) /
+ sizeof (struct uuid_to_ptag); j++) {
+
+ if (vtoc->efi_parts[i].p_tag == j) {
+ UUID_LE_CONVERT(
+ efi_parts[i].efi_gpe_PartitionTypeGUID,
+ conversion_array[j].uuid);
+ break;
+ }
+ }
+
+ if (j == sizeof (conversion_array) /
+ sizeof (struct uuid_to_ptag)) {
+ /*
+ * If we didn't have a matching uuid match, bail here.
+ * Don't write a label with unknown uuid.
+ */
+ if (efi_debug) {
+ (void) fprintf(stderr,
+ "Unknown uuid for p_tag %d\n",
+ vtoc->efi_parts[i].p_tag);
+ }
+ return (VT_EINVAL);
+ }
+
+ /* Zero's should be written for empty partitions */
+ if (vtoc->efi_parts[i].p_tag == V_UNASSIGNED)
+ continue;
+
+ efi_parts[i].efi_gpe_StartingLBA =
+ LE_64(vtoc->efi_parts[i].p_start);
+ efi_parts[i].efi_gpe_EndingLBA =
+ LE_64(vtoc->efi_parts[i].p_start +
+ vtoc->efi_parts[i].p_size - 1);
+ efi_parts[i].efi_gpe_Attributes.PartitionAttrs =
+ LE_16(vtoc->efi_parts[i].p_flag);
+ for (j = 0; j < EFI_PART_NAME_LEN; j++) {
+ efi_parts[i].efi_gpe_PartitionName[j] =
+ LE_16((ushort_t)vtoc->efi_parts[i].p_name[j]);
+ }
+ if ((vtoc->efi_parts[i].p_tag != V_UNASSIGNED) &&
+ uuid_is_null((uchar_t *)&vtoc->efi_parts[i].p_uguid)) {
+ (void) uuid_generate((uchar_t *)
+ &vtoc->efi_parts[i].p_uguid);
+ }
+ bcopy(&vtoc->efi_parts[i].p_uguid,
+ &efi_parts[i].efi_gpe_UniquePartitionGUID,
+ sizeof (uuid_t));
+ }
+ efi->efi_gpt_PartitionEntryArrayCRC32 =
+ LE_32(efi_crc32((unsigned char *)efi_parts,
+ vtoc->efi_nparts * (int)sizeof (struct efi_gpe)));
+ efi->efi_gpt_HeaderCRC32 =
+ LE_32(efi_crc32((unsigned char *)efi,
+ LE_32(efi->efi_gpt_HeaderSize)));
+
+ if (efi_ioctl(fd, DKIOCSETEFI, &dk_ioc) == -1) {
+ free(dk_ioc.dki_data);
+ switch (errno) {
+ case EIO:
+ return (VT_EIO);
+ case EINVAL:
+ return (VT_EINVAL);
+ default:
+ return (VT_ERROR);
+ }
+ }
+ /* if it's a metadevice we're done */
+ if (md_flag) {
+ free(dk_ioc.dki_data);
+ return (0);
+ }
+
+ /* write backup partition array */
+ dk_ioc.dki_lba = vtoc->efi_last_u_lba + 1;
+ dk_ioc.dki_length -= vtoc->efi_lbasize;
+ /* LINTED */
+ dk_ioc.dki_data = (efi_gpt_t *)((char *)dk_ioc.dki_data +
+ vtoc->efi_lbasize);
+
+ if (efi_ioctl(fd, DKIOCSETEFI, &dk_ioc) == -1) {
+ /*
+ * we wrote the primary label okay, so don't fail
+ */
+ if (efi_debug) {
+ (void) fprintf(stderr,
+ "write of backup partitions to block %llu "
+ "failed, errno %d\n",
+ vtoc->efi_last_u_lba + 1,
+ errno);
+ }
+ }
+ /*
+ * now swap MyLBA and AlternateLBA fields and write backup
+ * partition table header
+ */
+ dk_ioc.dki_lba = lba_backup_gpt_hdr;
+ dk_ioc.dki_length = vtoc->efi_lbasize;
+ /* LINTED */
+ dk_ioc.dki_data = (efi_gpt_t *)((char *)dk_ioc.dki_data -
+ vtoc->efi_lbasize);
+ efi->efi_gpt_AlternateLBA = LE_64(1ULL);
+ efi->efi_gpt_MyLBA = LE_64(lba_backup_gpt_hdr);
+ efi->efi_gpt_PartitionEntryLBA = LE_64(vtoc->efi_last_u_lba + 1);
+ efi->efi_gpt_HeaderCRC32 = 0;
+ efi->efi_gpt_HeaderCRC32 =
+ LE_32(efi_crc32((unsigned char *)dk_ioc.dki_data,
+ LE_32(efi->efi_gpt_HeaderSize)));
+
+ if (efi_ioctl(fd, DKIOCSETEFI, &dk_ioc) == -1) {
+ if (efi_debug) {
+ (void) fprintf(stderr,
+ "write of backup header to block %llu failed, "
+ "errno %d\n",
+ lba_backup_gpt_hdr,
+ errno);
+ }
+ }
+ /* write the PMBR */
+ (void) write_pmbr(fd, vtoc);
+ free(dk_ioc.dki_data);
+
+ return (0);
+}
+
+void
+efi_free(struct dk_gpt *ptr)
+{
+ free(ptr);
+}
+
+/*
+ * Input: File descriptor
+ * Output: 1 if disk has an EFI label, or > 2TB with no VTOC or legacy MBR.
+ * Otherwise 0.
+ */
+int
+efi_type(int fd)
+{
+#if 0
+ struct vtoc vtoc;
+ struct extvtoc extvtoc;
+
+ if (ioctl(fd, DKIOCGEXTVTOC, &extvtoc) == -1) {
+ if (errno == ENOTSUP)
+ return (1);
+ else if (errno == ENOTTY) {
+ if (ioctl(fd, DKIOCGVTOC, &vtoc) == -1)
+ if (errno == ENOTSUP)
+ return (1);
+ }
+ }
+ return (0);
+#else
+ return (ENOSYS);
+#endif
+}
+
+void
+efi_err_check(struct dk_gpt *vtoc)
+{
+ int resv_part = -1;
+ int i, j;
+ diskaddr_t istart, jstart, isize, jsize, endsect;
+ int overlap = 0;
+
+ /*
+ * make sure no partitions overlap
+ */
+ for (i = 0; i < vtoc->efi_nparts; i++) {
+ /* It can't be unassigned and have an actual size */
+ if ((vtoc->efi_parts[i].p_tag == V_UNASSIGNED) &&
+ (vtoc->efi_parts[i].p_size != 0)) {
+ (void) fprintf(stderr,
+ "partition %d is \"unassigned\" but has a size "
+ "of %llu\n", i, vtoc->efi_parts[i].p_size);
+ }
+ if (vtoc->efi_parts[i].p_tag == V_UNASSIGNED) {
+ continue;
+ }
+ if (vtoc->efi_parts[i].p_tag == V_RESERVED) {
+ if (resv_part != -1) {
+ (void) fprintf(stderr,
+ "found duplicate reserved partition at "
+ "%d\n", i);
+ }
+ resv_part = i;
+ if (vtoc->efi_parts[i].p_size != EFI_MIN_RESV_SIZE)
+ (void) fprintf(stderr,
+ "Warning: reserved partition size must "
+ "be %d sectors\n", EFI_MIN_RESV_SIZE);
+ }
+ if ((vtoc->efi_parts[i].p_start < vtoc->efi_first_u_lba) ||
+ (vtoc->efi_parts[i].p_start > vtoc->efi_last_u_lba)) {
+ (void) fprintf(stderr,
+ "Partition %d starts at %llu\n",
+ i,
+ vtoc->efi_parts[i].p_start);
+ (void) fprintf(stderr,
+ "It must be between %llu and %llu.\n",
+ vtoc->efi_first_u_lba,
+ vtoc->efi_last_u_lba);
+ }
+ if ((vtoc->efi_parts[i].p_start +
+ vtoc->efi_parts[i].p_size <
+ vtoc->efi_first_u_lba) ||
+ (vtoc->efi_parts[i].p_start +
+ vtoc->efi_parts[i].p_size >
+ vtoc->efi_last_u_lba + 1)) {
+ (void) fprintf(stderr,
+ "Partition %d ends at %llu\n",
+ i,
+ vtoc->efi_parts[i].p_start +
+ vtoc->efi_parts[i].p_size);
+ (void) fprintf(stderr,
+ "It must be between %llu and %llu.\n",
+ vtoc->efi_first_u_lba,
+ vtoc->efi_last_u_lba);
+ }
+
+ for (j = 0; j < vtoc->efi_nparts; j++) {
+ isize = vtoc->efi_parts[i].p_size;
+ jsize = vtoc->efi_parts[j].p_size;
+ istart = vtoc->efi_parts[i].p_start;
+ jstart = vtoc->efi_parts[j].p_start;
+ if ((i != j) && (isize != 0) && (jsize != 0)) {
+ endsect = jstart + jsize -1;
+ if ((jstart <= istart) &&
+ (istart <= endsect)) {
+ if (!overlap) {
+ (void) fprintf(stderr,
+ "label error: EFI Labels do not "
+ "support overlapping partitions\n");
+ }
+ (void) fprintf(stderr,
+ "Partition %d overlaps partition "
+ "%d.\n", i, j);
+ overlap = 1;
+ }
+ }
+ }
+ }
+ /* make sure there is a reserved partition */
+ if (resv_part == -1) {
+ (void) fprintf(stderr,
+ "no reserved partition found\n");
+ }
+}
+
+/*
+ * We need to get information necessary to construct a *new* efi
+ * label type
+ */
+int
+efi_auto_sense(int fd, struct dk_gpt **vtoc)
+{
+
+ int i;
+
+ /*
+ * Now build the default partition table
+ */
+ if (efi_alloc_and_init(fd, EFI_NUMPAR, vtoc) != 0) {
+ if (efi_debug) {
+ (void) fprintf(stderr, "efi_alloc_and_init failed.\n");
+ }
+ return (-1);
+ }
+
+ for (i = 0; i < MIN((*vtoc)->efi_nparts, V_NUMPAR); i++) {
+ (*vtoc)->efi_parts[i].p_tag = default_vtoc_map[i].p_tag;
+ (*vtoc)->efi_parts[i].p_flag = default_vtoc_map[i].p_flag;
+ (*vtoc)->efi_parts[i].p_start = 0;
+ (*vtoc)->efi_parts[i].p_size = 0;
+ }
+ /*
+ * Make constants first
+ * and variable partitions later
+ */
+
+ /* root partition - s0 128 MB */
+ (*vtoc)->efi_parts[0].p_start = 34;
+ (*vtoc)->efi_parts[0].p_size = 262144;
+
+ /* partition - s1 128 MB */
+ (*vtoc)->efi_parts[1].p_start = 262178;
+ (*vtoc)->efi_parts[1].p_size = 262144;
+
+ /* partition -s2 is NOT the Backup disk */
+ (*vtoc)->efi_parts[2].p_tag = V_UNASSIGNED;
+
+ /* partition -s6 /usr partition - HOG */
+ (*vtoc)->efi_parts[6].p_start = 524322;
+ (*vtoc)->efi_parts[6].p_size = (*vtoc)->efi_last_u_lba - 524322
+ - (1024 * 16);
+
+ /* efi reserved partition - s9 16K */
+ (*vtoc)->efi_parts[8].p_start = (*vtoc)->efi_last_u_lba - (1024 * 16);
+ (*vtoc)->efi_parts[8].p_size = (1024 * 16);
+ (*vtoc)->efi_parts[8].p_tag = V_RESERVED;
+ return (0);
+}
diff --git a/lib/libicp/Makefile.am b/lib/libicp/Makefile.am
new file mode 100644
index 000000000000..6d3c65ea324f
--- /dev/null
+++ b/lib/libicp/Makefile.am
@@ -0,0 +1,73 @@
+include $(top_srcdir)/config/Rules.am
+
+VPATH = \
+ $(top_srcdir)/module/icp \
+ $(top_srcdir)/lib/libicp
+
+# Includes kernel code, generate warnings for large stack frames
+AM_CFLAGS += $(FRAME_LARGER_THAN)
+
+noinst_LTLIBRARIES = libicp.la
+
+if TARGET_CPU_X86_64
+ASM_SOURCES_C = asm-x86_64/aes/aeskey.c
+ASM_SOURCES_AS = \
+ asm-x86_64/aes/aes_amd64.S \
+ asm-x86_64/aes/aes_aesni.S \
+ asm-x86_64/modes/gcm_pclmulqdq.S \
+ asm-x86_64/modes/aesni-gcm-x86_64.S \
+ asm-x86_64/modes/ghash-x86_64.S \
+ asm-x86_64/sha1/sha1-x86_64.S \
+ asm-x86_64/sha2/sha256_impl.S \
+ asm-x86_64/sha2/sha512_impl.S
+else
+ASM_SOURCES_C =
+ASM_SOURCES_AS =
+endif
+
+KERNEL_C = \
+ spi/kcf_spi.c \
+ api/kcf_ctxops.c \
+ api/kcf_digest.c \
+ api/kcf_cipher.c \
+ api/kcf_miscapi.c \
+ api/kcf_mac.c \
+ algs/aes/aes_impl_aesni.c \
+ algs/aes/aes_impl_generic.c \
+ algs/aes/aes_impl_x86-64.c \
+ algs/aes/aes_impl.c \
+ algs/aes/aes_modes.c \
+ algs/edonr/edonr.c \
+ algs/modes/modes.c \
+ algs/modes/cbc.c \
+ algs/modes/gcm_generic.c \
+ algs/modes/gcm_pclmulqdq.c \
+ algs/modes/gcm.c \
+ algs/modes/ctr.c \
+ algs/modes/ccm.c \
+ algs/modes/ecb.c \
+ algs/sha1/sha1.c \
+ algs/sha2/sha2.c \
+ algs/skein/skein.c \
+ algs/skein/skein_block.c \
+ algs/skein/skein_iv.c \
+ illumos-crypto.c \
+ io/aes.c \
+ io/edonr_mod.c \
+ io/sha1_mod.c \
+ io/sha2_mod.c \
+ io/skein_mod.c \
+ os/modhash.c \
+ os/modconf.c \
+ core/kcf_sched.c \
+ core/kcf_prov_lib.c \
+ core/kcf_callprov.c \
+ core/kcf_mech_tabs.c \
+ core/kcf_prov_tabs.c \
+ $(ASM_SOURCES_C)
+
+KERNEL_ASM = $(ASM_SOURCES_AS)
+
+nodist_libicp_la_SOURCES = \
+ $(KERNEL_C) \
+ $(KERNEL_ASM)
diff --git a/lib/libnvpair/Makefile.am b/lib/libnvpair/Makefile.am
new file mode 100644
index 000000000000..ec16c5d526c5
--- /dev/null
+++ b/lib/libnvpair/Makefile.am
@@ -0,0 +1,44 @@
+include $(top_srcdir)/config/Rules.am
+
+VPATH = \
+ $(top_srcdir)/module/nvpair \
+ $(top_srcdir)/lib/libnvpair
+
+# Includes kernel code, generate warnings for large stack frames
+# and required CFLAGS for libtirpc
+AM_CFLAGS += $(FRAME_LARGER_THAN) $(LIBTIRPC_CFLAGS)
+
+lib_LTLIBRARIES = libnvpair.la
+
+USER_C = \
+ libnvpair.c \
+ libnvpair_json.c \
+ nvpair_alloc_system.c
+
+KERNEL_C = \
+ nvpair_alloc_fixed.c \
+ nvpair.c \
+ fnvpair.c
+
+dist_libnvpair_la_SOURCES = \
+ $(USER_C)
+
+nodist_libnvpair_la_SOURCES = \
+ $(KERNEL_C)
+
+libnvpair_la_LIBADD = \
+ $(abs_top_builddir)/lib/libspl/libspl_assert.la
+
+libnvpair_la_LIBADD += $(LIBTIRPC_LIBS) $(LTLIBINTL)
+
+libnvpair_la_LDFLAGS =
+
+if !ASAN_ENABLED
+libnvpair_la_LDFLAGS += -Wl,-z,defs
+endif
+
+if BUILD_FREEBSD
+libnvpair_la_LDFLAGS += -version-info 3:0:0
+else
+libnvpair_la_LDFLAGS += -version-info 1:1:0
+endif
diff --git a/lib/libnvpair/libnvpair.c b/lib/libnvpair/libnvpair.c
new file mode 100644
index 000000000000..2e9ea1c174e9
--- /dev/null
+++ b/lib/libnvpair/libnvpair.c
@@ -0,0 +1,1277 @@
+/*
+ * 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) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012 by Delphix. All rights reserved.
+ */
+
+#include <unistd.h>
+#include <string.h>
+#include <libintl.h>
+#include <sys/types.h>
+#include <sys/inttypes.h>
+#include <sys/note.h>
+#include <stdarg.h>
+#include "libnvpair.h"
+
+/*
+ * libnvpair - A tools library for manipulating <name, value> pairs.
+ *
+ * This library provides routines packing an unpacking nv pairs
+ * for transporting data across process boundaries, transporting
+ * between kernel and userland, and possibly saving onto disk files.
+ */
+
+/*
+ * Print control structure.
+ */
+
+#define DEFINEOP(opname, vtype) \
+ struct { \
+ int (*op)(struct nvlist_prtctl *, void *, nvlist_t *, \
+ const char *, vtype); \
+ void *arg; \
+ } opname
+
+#define DEFINEARROP(opname, vtype) \
+ struct { \
+ int (*op)(struct nvlist_prtctl *, void *, nvlist_t *, \
+ const char *, vtype, uint_t); \
+ void *arg; \
+ } opname
+
+struct nvlist_printops {
+ DEFINEOP(print_boolean, int);
+ DEFINEOP(print_boolean_value, boolean_t);
+ DEFINEOP(print_byte, uchar_t);
+ DEFINEOP(print_int8, int8_t);
+ DEFINEOP(print_uint8, uint8_t);
+ DEFINEOP(print_int16, int16_t);
+ DEFINEOP(print_uint16, uint16_t);
+ DEFINEOP(print_int32, int32_t);
+ DEFINEOP(print_uint32, uint32_t);
+ DEFINEOP(print_int64, int64_t);
+ DEFINEOP(print_uint64, uint64_t);
+ DEFINEOP(print_double, double);
+ DEFINEOP(print_string, char *);
+ DEFINEOP(print_hrtime, hrtime_t);
+ DEFINEOP(print_nvlist, nvlist_t *);
+ DEFINEARROP(print_boolean_array, boolean_t *);
+ DEFINEARROP(print_byte_array, uchar_t *);
+ DEFINEARROP(print_int8_array, int8_t *);
+ DEFINEARROP(print_uint8_array, uint8_t *);
+ DEFINEARROP(print_int16_array, int16_t *);
+ DEFINEARROP(print_uint16_array, uint16_t *);
+ DEFINEARROP(print_int32_array, int32_t *);
+ DEFINEARROP(print_uint32_array, uint32_t *);
+ DEFINEARROP(print_int64_array, int64_t *);
+ DEFINEARROP(print_uint64_array, uint64_t *);
+ DEFINEARROP(print_string_array, char **);
+ DEFINEARROP(print_nvlist_array, nvlist_t **);
+};
+
+struct nvlist_prtctl {
+ FILE *nvprt_fp; /* output destination */
+ enum nvlist_indent_mode nvprt_indent_mode; /* see above */
+ int nvprt_indent; /* absolute indent, or tab depth */
+ int nvprt_indentinc; /* indent or tab increment */
+ const char *nvprt_nmfmt; /* member name format, max one %s */
+ const char *nvprt_eomfmt; /* after member format, e.g. "\n" */
+ const char *nvprt_btwnarrfmt; /* between array members */
+ int nvprt_btwnarrfmt_nl; /* nvprt_eoamfmt includes newline? */
+ struct nvlist_printops *nvprt_dfltops;
+ struct nvlist_printops *nvprt_custops;
+};
+
+#define DFLTPRTOP(pctl, type) \
+ ((pctl)->nvprt_dfltops->print_##type.op)
+
+#define DFLTPRTOPARG(pctl, type) \
+ ((pctl)->nvprt_dfltops->print_##type.arg)
+
+#define CUSTPRTOP(pctl, type) \
+ ((pctl)->nvprt_custops->print_##type.op)
+
+#define CUSTPRTOPARG(pctl, type) \
+ ((pctl)->nvprt_custops->print_##type.arg)
+
+#define RENDER(pctl, type, nvl, name, val) \
+ { \
+ int done = 0; \
+ if ((pctl)->nvprt_custops && CUSTPRTOP(pctl, type)) { \
+ done = CUSTPRTOP(pctl, type)(pctl, \
+ CUSTPRTOPARG(pctl, type), nvl, name, val); \
+ } \
+ if (!done) { \
+ (void) DFLTPRTOP(pctl, type)(pctl, \
+ DFLTPRTOPARG(pctl, type), nvl, name, val); \
+ } \
+ (void) fprintf(pctl->nvprt_fp, "%s", pctl->nvprt_eomfmt); \
+ }
+
+#define ARENDER(pctl, type, nvl, name, arrp, count) \
+ { \
+ int done = 0; \
+ if ((pctl)->nvprt_custops && CUSTPRTOP(pctl, type)) { \
+ done = CUSTPRTOP(pctl, type)(pctl, \
+ CUSTPRTOPARG(pctl, type), nvl, name, arrp, count); \
+ } \
+ if (!done) { \
+ (void) DFLTPRTOP(pctl, type)(pctl, \
+ DFLTPRTOPARG(pctl, type), nvl, name, arrp, count); \
+ } \
+ (void) fprintf(pctl->nvprt_fp, "%s", pctl->nvprt_eomfmt); \
+ }
+
+static void nvlist_print_with_indent(nvlist_t *, nvlist_prtctl_t);
+
+/*
+ * ======================================================================
+ * | |
+ * | Indentation |
+ * | |
+ * ======================================================================
+ */
+
+static void
+indent(nvlist_prtctl_t pctl, int onemore)
+{
+ int depth;
+
+ switch (pctl->nvprt_indent_mode) {
+ case NVLIST_INDENT_ABS:
+ (void) fprintf(pctl->nvprt_fp, "%*s",
+ pctl->nvprt_indent + onemore * pctl->nvprt_indentinc, "");
+ break;
+
+ case NVLIST_INDENT_TABBED:
+ depth = pctl->nvprt_indent + onemore;
+ while (depth-- > 0)
+ (void) fprintf(pctl->nvprt_fp, "\t");
+ }
+}
+
+/*
+ * ======================================================================
+ * | |
+ * | Default nvlist member rendering functions. |
+ * | |
+ * ======================================================================
+ */
+
+/*
+ * Generate functions to print single-valued nvlist members.
+ *
+ * type_and_variant - suffix to form function name
+ * vtype - C type for the member value
+ * ptype - C type to cast value to for printing
+ * vfmt - format string for pair value, e.g "%d" or "0x%llx"
+ */
+
+#define NVLIST_PRTFUNC(type_and_variant, vtype, ptype, vfmt) \
+static int \
+nvprint_##type_and_variant(nvlist_prtctl_t pctl, void *private, \
+ nvlist_t *nvl, const char *name, vtype value) \
+{ \
+ FILE *fp = pctl->nvprt_fp; \
+ NOTE(ARGUNUSED(private)) \
+ NOTE(ARGUNUSED(nvl)) \
+ indent(pctl, 1); \
+ (void) fprintf(fp, pctl->nvprt_nmfmt, name); \
+ (void) fprintf(fp, vfmt, (ptype)value); \
+ return (1); \
+}
+
+NVLIST_PRTFUNC(boolean, int, int, "%d")
+NVLIST_PRTFUNC(boolean_value, boolean_t, int, "%d")
+NVLIST_PRTFUNC(byte, uchar_t, uchar_t, "0x%2.2x")
+NVLIST_PRTFUNC(int8, int8_t, int, "%d")
+NVLIST_PRTFUNC(uint8, uint8_t, uint8_t, "0x%x")
+NVLIST_PRTFUNC(int16, int16_t, int16_t, "%d")
+NVLIST_PRTFUNC(uint16, uint16_t, uint16_t, "0x%x")
+NVLIST_PRTFUNC(int32, int32_t, int32_t, "%d")
+NVLIST_PRTFUNC(uint32, uint32_t, uint32_t, "0x%x")
+NVLIST_PRTFUNC(int64, int64_t, longlong_t, "%lld")
+NVLIST_PRTFUNC(uint64, uint64_t, u_longlong_t, "0x%llx")
+NVLIST_PRTFUNC(double, double, double, "0x%f")
+NVLIST_PRTFUNC(string, char *, char *, "%s")
+NVLIST_PRTFUNC(hrtime, hrtime_t, hrtime_t, "0x%llx")
+
+/*
+ * Generate functions to print array-valued nvlist members.
+ */
+
+#define NVLIST_ARRPRTFUNC(type_and_variant, vtype, ptype, vfmt) \
+static int \
+nvaprint_##type_and_variant(nvlist_prtctl_t pctl, void *private, \
+ nvlist_t *nvl, const char *name, vtype *valuep, uint_t count) \
+{ \
+ FILE *fp = pctl->nvprt_fp; \
+ uint_t i; \
+ NOTE(ARGUNUSED(private)) \
+ NOTE(ARGUNUSED(nvl)) \
+ for (i = 0; i < count; i++) { \
+ if (i == 0 || pctl->nvprt_btwnarrfmt_nl) { \
+ indent(pctl, 1); \
+ (void) fprintf(fp, pctl->nvprt_nmfmt, name); \
+ if (pctl->nvprt_btwnarrfmt_nl) \
+ (void) fprintf(fp, "[%d]: ", i); \
+ } \
+ if (i != 0) \
+ (void) fprintf(fp, "%s", pctl->nvprt_btwnarrfmt); \
+ (void) fprintf(fp, vfmt, (ptype)valuep[i]); \
+ } \
+ return (1); \
+}
+
+NVLIST_ARRPRTFUNC(boolean_array, boolean_t, boolean_t, "%d")
+NVLIST_ARRPRTFUNC(byte_array, uchar_t, uchar_t, "0x%2.2x")
+NVLIST_ARRPRTFUNC(int8_array, int8_t, int8_t, "%d")
+NVLIST_ARRPRTFUNC(uint8_array, uint8_t, uint8_t, "0x%x")
+NVLIST_ARRPRTFUNC(int16_array, int16_t, int16_t, "%d")
+NVLIST_ARRPRTFUNC(uint16_array, uint16_t, uint16_t, "0x%x")
+NVLIST_ARRPRTFUNC(int32_array, int32_t, int32_t, "%d")
+NVLIST_ARRPRTFUNC(uint32_array, uint32_t, uint32_t, "0x%x")
+NVLIST_ARRPRTFUNC(int64_array, int64_t, longlong_t, "%lld")
+NVLIST_ARRPRTFUNC(uint64_array, uint64_t, u_longlong_t, "0x%llx")
+NVLIST_ARRPRTFUNC(string_array, char *, char *, "%s")
+
+/*ARGSUSED*/
+static int
+nvprint_nvlist(nvlist_prtctl_t pctl, void *private,
+ nvlist_t *nvl, const char *name, nvlist_t *value)
+{
+ FILE *fp = pctl->nvprt_fp;
+
+ indent(pctl, 1);
+ (void) fprintf(fp, "%s = (embedded nvlist)\n", name);
+
+ pctl->nvprt_indent += pctl->nvprt_indentinc;
+ nvlist_print_with_indent(value, pctl);
+ pctl->nvprt_indent -= pctl->nvprt_indentinc;
+
+ indent(pctl, 1);
+ (void) fprintf(fp, "(end %s)\n", name);
+
+ return (1);
+}
+
+/*ARGSUSED*/
+static int
+nvaprint_nvlist_array(nvlist_prtctl_t pctl, void *private,
+ nvlist_t *nvl, const char *name, nvlist_t **valuep, uint_t count)
+{
+ FILE *fp = pctl->nvprt_fp;
+ uint_t i;
+
+ indent(pctl, 1);
+ (void) fprintf(fp, "%s = (array of embedded nvlists)\n", name);
+
+ for (i = 0; i < count; i++) {
+ indent(pctl, 1);
+ (void) fprintf(fp, "(start %s[%d])\n", name, i);
+
+ pctl->nvprt_indent += pctl->nvprt_indentinc;
+ nvlist_print_with_indent(valuep[i], pctl);
+ pctl->nvprt_indent -= pctl->nvprt_indentinc;
+
+ indent(pctl, 1);
+ (void) fprintf(fp, "(end %s[%d])\n", name, i);
+ }
+
+ return (1);
+}
+
+/*
+ * ======================================================================
+ * | |
+ * | Interfaces that allow control over formatting. |
+ * | |
+ * ======================================================================
+ */
+
+void
+nvlist_prtctl_setdest(nvlist_prtctl_t pctl, FILE *fp)
+{
+ pctl->nvprt_fp = fp;
+}
+
+FILE *
+nvlist_prtctl_getdest(nvlist_prtctl_t pctl)
+{
+ return (pctl->nvprt_fp);
+}
+
+
+void
+nvlist_prtctl_setindent(nvlist_prtctl_t pctl, enum nvlist_indent_mode mode,
+ int start, int inc)
+{
+ if (mode < NVLIST_INDENT_ABS || mode > NVLIST_INDENT_TABBED)
+ mode = NVLIST_INDENT_TABBED;
+
+ if (start < 0)
+ start = 0;
+
+ if (inc < 0)
+ inc = 1;
+
+ pctl->nvprt_indent_mode = mode;
+ pctl->nvprt_indent = start;
+ pctl->nvprt_indentinc = inc;
+}
+
+void
+nvlist_prtctl_doindent(nvlist_prtctl_t pctl, int onemore)
+{
+ indent(pctl, onemore);
+}
+
+
+void
+nvlist_prtctl_setfmt(nvlist_prtctl_t pctl, enum nvlist_prtctl_fmt which,
+ const char *fmt)
+{
+ switch (which) {
+ case NVLIST_FMT_MEMBER_NAME:
+ if (fmt == NULL)
+ fmt = "%s = ";
+ pctl->nvprt_nmfmt = fmt;
+ break;
+
+ case NVLIST_FMT_MEMBER_POSTAMBLE:
+ if (fmt == NULL)
+ fmt = "\n";
+ pctl->nvprt_eomfmt = fmt;
+ break;
+
+ case NVLIST_FMT_BTWN_ARRAY:
+ if (fmt == NULL) {
+ pctl->nvprt_btwnarrfmt = " ";
+ pctl->nvprt_btwnarrfmt_nl = 0;
+ } else {
+ pctl->nvprt_btwnarrfmt = fmt;
+ pctl->nvprt_btwnarrfmt_nl = (strstr(fmt, "\n") != NULL);
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+
+void
+nvlist_prtctl_dofmt(nvlist_prtctl_t pctl, enum nvlist_prtctl_fmt which, ...)
+{
+ FILE *fp = pctl->nvprt_fp;
+ va_list ap;
+ char *name;
+
+ va_start(ap, which);
+
+ switch (which) {
+ case NVLIST_FMT_MEMBER_NAME:
+ name = va_arg(ap, char *);
+ (void) fprintf(fp, pctl->nvprt_nmfmt, name);
+ break;
+
+ case NVLIST_FMT_MEMBER_POSTAMBLE:
+ (void) fprintf(fp, "%s", pctl->nvprt_eomfmt);
+ break;
+
+ case NVLIST_FMT_BTWN_ARRAY:
+ (void) fprintf(fp, "%s", pctl->nvprt_btwnarrfmt);
+ break;
+
+ default:
+ break;
+ }
+
+ va_end(ap);
+}
+
+/*
+ * ======================================================================
+ * | |
+ * | Interfaces to allow appointment of replacement rendering functions.|
+ * | |
+ * ======================================================================
+ */
+
+#define NVLIST_PRINTCTL_REPLACE(type, vtype) \
+void \
+nvlist_prtctlop_##type(nvlist_prtctl_t pctl, \
+ int (*func)(nvlist_prtctl_t, void *, nvlist_t *, const char *, vtype), \
+ void *private) \
+{ \
+ CUSTPRTOP(pctl, type) = func; \
+ CUSTPRTOPARG(pctl, type) = private; \
+}
+
+NVLIST_PRINTCTL_REPLACE(boolean, int)
+NVLIST_PRINTCTL_REPLACE(boolean_value, boolean_t)
+NVLIST_PRINTCTL_REPLACE(byte, uchar_t)
+NVLIST_PRINTCTL_REPLACE(int8, int8_t)
+NVLIST_PRINTCTL_REPLACE(uint8, uint8_t)
+NVLIST_PRINTCTL_REPLACE(int16, int16_t)
+NVLIST_PRINTCTL_REPLACE(uint16, uint16_t)
+NVLIST_PRINTCTL_REPLACE(int32, int32_t)
+NVLIST_PRINTCTL_REPLACE(uint32, uint32_t)
+NVLIST_PRINTCTL_REPLACE(int64, int64_t)
+NVLIST_PRINTCTL_REPLACE(uint64, uint64_t)
+NVLIST_PRINTCTL_REPLACE(double, double)
+NVLIST_PRINTCTL_REPLACE(string, char *)
+NVLIST_PRINTCTL_REPLACE(hrtime, hrtime_t)
+NVLIST_PRINTCTL_REPLACE(nvlist, nvlist_t *)
+
+#define NVLIST_PRINTCTL_AREPLACE(type, vtype) \
+void \
+nvlist_prtctlop_##type(nvlist_prtctl_t pctl, \
+ int (*func)(nvlist_prtctl_t, void *, nvlist_t *, const char *, vtype, \
+ uint_t), void *private) \
+{ \
+ CUSTPRTOP(pctl, type) = func; \
+ CUSTPRTOPARG(pctl, type) = private; \
+}
+
+NVLIST_PRINTCTL_AREPLACE(boolean_array, boolean_t *)
+NVLIST_PRINTCTL_AREPLACE(byte_array, uchar_t *)
+NVLIST_PRINTCTL_AREPLACE(int8_array, int8_t *)
+NVLIST_PRINTCTL_AREPLACE(uint8_array, uint8_t *)
+NVLIST_PRINTCTL_AREPLACE(int16_array, int16_t *)
+NVLIST_PRINTCTL_AREPLACE(uint16_array, uint16_t *)
+NVLIST_PRINTCTL_AREPLACE(int32_array, int32_t *)
+NVLIST_PRINTCTL_AREPLACE(uint32_array, uint32_t *)
+NVLIST_PRINTCTL_AREPLACE(int64_array, int64_t *)
+NVLIST_PRINTCTL_AREPLACE(uint64_array, uint64_t *)
+NVLIST_PRINTCTL_AREPLACE(string_array, char **)
+NVLIST_PRINTCTL_AREPLACE(nvlist_array, nvlist_t **)
+
+/*
+ * ======================================================================
+ * | |
+ * | Interfaces to manage nvlist_prtctl_t cookies. |
+ * | |
+ * ======================================================================
+ */
+
+
+static const struct nvlist_printops defprtops =
+{
+ { nvprint_boolean, NULL },
+ { nvprint_boolean_value, NULL },
+ { nvprint_byte, NULL },
+ { nvprint_int8, NULL },
+ { nvprint_uint8, NULL },
+ { nvprint_int16, NULL },
+ { nvprint_uint16, NULL },
+ { nvprint_int32, NULL },
+ { nvprint_uint32, NULL },
+ { nvprint_int64, NULL },
+ { nvprint_uint64, NULL },
+ { nvprint_double, NULL },
+ { nvprint_string, NULL },
+ { nvprint_hrtime, NULL },
+ { nvprint_nvlist, NULL },
+ { nvaprint_boolean_array, NULL },
+ { nvaprint_byte_array, NULL },
+ { nvaprint_int8_array, NULL },
+ { nvaprint_uint8_array, NULL },
+ { nvaprint_int16_array, NULL },
+ { nvaprint_uint16_array, NULL },
+ { nvaprint_int32_array, NULL },
+ { nvaprint_uint32_array, NULL },
+ { nvaprint_int64_array, NULL },
+ { nvaprint_uint64_array, NULL },
+ { nvaprint_string_array, NULL },
+ { nvaprint_nvlist_array, NULL },
+};
+
+static void
+prtctl_defaults(FILE *fp, struct nvlist_prtctl *pctl,
+ struct nvlist_printops *ops)
+{
+ pctl->nvprt_fp = fp;
+ pctl->nvprt_indent_mode = NVLIST_INDENT_TABBED;
+ pctl->nvprt_indent = 0;
+ pctl->nvprt_indentinc = 1;
+ pctl->nvprt_nmfmt = "%s = ";
+ pctl->nvprt_eomfmt = "\n";
+ pctl->nvprt_btwnarrfmt = " ";
+ pctl->nvprt_btwnarrfmt_nl = 0;
+
+ pctl->nvprt_dfltops = (struct nvlist_printops *)&defprtops;
+ pctl->nvprt_custops = ops;
+}
+
+nvlist_prtctl_t
+nvlist_prtctl_alloc(void)
+{
+ struct nvlist_prtctl *pctl;
+ struct nvlist_printops *ops;
+
+ if ((pctl = malloc(sizeof (*pctl))) == NULL)
+ return (NULL);
+
+ if ((ops = calloc(1, sizeof (*ops))) == NULL) {
+ free(pctl);
+ return (NULL);
+ }
+
+ prtctl_defaults(stdout, pctl, ops);
+
+ return (pctl);
+}
+
+void
+nvlist_prtctl_free(nvlist_prtctl_t pctl)
+{
+ if (pctl != NULL) {
+ free(pctl->nvprt_custops);
+ free(pctl);
+ }
+}
+
+/*
+ * ======================================================================
+ * | |
+ * | Top-level print request interfaces. |
+ * | |
+ * ======================================================================
+ */
+
+/*
+ * nvlist_print - Prints elements in an event buffer
+ */
+static void
+nvlist_print_with_indent(nvlist_t *nvl, nvlist_prtctl_t pctl)
+{
+ FILE *fp = pctl->nvprt_fp;
+ char *name;
+ uint_t nelem;
+ nvpair_t *nvp;
+
+ if (nvl == NULL)
+ return;
+
+ indent(pctl, 0);
+ (void) fprintf(fp, "nvlist version: %d\n", NVL_VERSION(nvl));
+
+ nvp = nvlist_next_nvpair(nvl, NULL);
+
+ while (nvp) {
+ data_type_t type = nvpair_type(nvp);
+
+ name = nvpair_name(nvp);
+ nelem = 0;
+
+ switch (type) {
+ case DATA_TYPE_BOOLEAN: {
+ RENDER(pctl, boolean, nvl, name, 1);
+ break;
+ }
+ case DATA_TYPE_BOOLEAN_VALUE: {
+ boolean_t val;
+ (void) nvpair_value_boolean_value(nvp, &val);
+ RENDER(pctl, boolean_value, nvl, name, val);
+ break;
+ }
+ case DATA_TYPE_BYTE: {
+ uchar_t val;
+ (void) nvpair_value_byte(nvp, &val);
+ RENDER(pctl, byte, nvl, name, val);
+ break;
+ }
+ case DATA_TYPE_INT8: {
+ int8_t val;
+ (void) nvpair_value_int8(nvp, &val);
+ RENDER(pctl, int8, nvl, name, val);
+ break;
+ }
+ case DATA_TYPE_UINT8: {
+ uint8_t val;
+ (void) nvpair_value_uint8(nvp, &val);
+ RENDER(pctl, uint8, nvl, name, val);
+ break;
+ }
+ case DATA_TYPE_INT16: {
+ int16_t val;
+ (void) nvpair_value_int16(nvp, &val);
+ RENDER(pctl, int16, nvl, name, val);
+ break;
+ }
+ case DATA_TYPE_UINT16: {
+ uint16_t val;
+ (void) nvpair_value_uint16(nvp, &val);
+ RENDER(pctl, uint16, nvl, name, val);
+ break;
+ }
+ case DATA_TYPE_INT32: {
+ int32_t val;
+ (void) nvpair_value_int32(nvp, &val);
+ RENDER(pctl, int32, nvl, name, val);
+ break;
+ }
+ case DATA_TYPE_UINT32: {
+ uint32_t val;
+ (void) nvpair_value_uint32(nvp, &val);
+ RENDER(pctl, uint32, nvl, name, val);
+ break;
+ }
+ case DATA_TYPE_INT64: {
+ int64_t val;
+ (void) nvpair_value_int64(nvp, &val);
+ RENDER(pctl, int64, nvl, name, val);
+ break;
+ }
+ case DATA_TYPE_UINT64: {
+ uint64_t val;
+ (void) nvpair_value_uint64(nvp, &val);
+ RENDER(pctl, uint64, nvl, name, val);
+ break;
+ }
+ case DATA_TYPE_DOUBLE: {
+ double val;
+ (void) nvpair_value_double(nvp, &val);
+ RENDER(pctl, double, nvl, name, val);
+ break;
+ }
+ case DATA_TYPE_STRING: {
+ char *val;
+ (void) nvpair_value_string(nvp, &val);
+ RENDER(pctl, string, nvl, name, val);
+ break;
+ }
+ case DATA_TYPE_BOOLEAN_ARRAY: {
+ boolean_t *val;
+ (void) nvpair_value_boolean_array(nvp, &val, &nelem);
+ ARENDER(pctl, boolean_array, nvl, name, val, nelem);
+ break;
+ }
+ case DATA_TYPE_BYTE_ARRAY: {
+ uchar_t *val;
+ (void) nvpair_value_byte_array(nvp, &val, &nelem);
+ ARENDER(pctl, byte_array, nvl, name, val, nelem);
+ break;
+ }
+ case DATA_TYPE_INT8_ARRAY: {
+ int8_t *val;
+ (void) nvpair_value_int8_array(nvp, &val, &nelem);
+ ARENDER(pctl, int8_array, nvl, name, val, nelem);
+ break;
+ }
+ case DATA_TYPE_UINT8_ARRAY: {
+ uint8_t *val;
+ (void) nvpair_value_uint8_array(nvp, &val, &nelem);
+ ARENDER(pctl, uint8_array, nvl, name, val, nelem);
+ break;
+ }
+ case DATA_TYPE_INT16_ARRAY: {
+ int16_t *val;
+ (void) nvpair_value_int16_array(nvp, &val, &nelem);
+ ARENDER(pctl, int16_array, nvl, name, val, nelem);
+ break;
+ }
+ case DATA_TYPE_UINT16_ARRAY: {
+ uint16_t *val;
+ (void) nvpair_value_uint16_array(nvp, &val, &nelem);
+ ARENDER(pctl, uint16_array, nvl, name, val, nelem);
+ break;
+ }
+ case DATA_TYPE_INT32_ARRAY: {
+ int32_t *val;
+ (void) nvpair_value_int32_array(nvp, &val, &nelem);
+ ARENDER(pctl, int32_array, nvl, name, val, nelem);
+ break;
+ }
+ case DATA_TYPE_UINT32_ARRAY: {
+ uint32_t *val;
+ (void) nvpair_value_uint32_array(nvp, &val, &nelem);
+ ARENDER(pctl, uint32_array, nvl, name, val, nelem);
+ break;
+ }
+ case DATA_TYPE_INT64_ARRAY: {
+ int64_t *val;
+ (void) nvpair_value_int64_array(nvp, &val, &nelem);
+ ARENDER(pctl, int64_array, nvl, name, val, nelem);
+ break;
+ }
+ case DATA_TYPE_UINT64_ARRAY: {
+ uint64_t *val;
+ (void) nvpair_value_uint64_array(nvp, &val, &nelem);
+ ARENDER(pctl, uint64_array, nvl, name, val, nelem);
+ break;
+ }
+ case DATA_TYPE_STRING_ARRAY: {
+ char **val;
+ (void) nvpair_value_string_array(nvp, &val, &nelem);
+ ARENDER(pctl, string_array, nvl, name, val, nelem);
+ break;
+ }
+ case DATA_TYPE_HRTIME: {
+ hrtime_t val;
+ (void) nvpair_value_hrtime(nvp, &val);
+ RENDER(pctl, hrtime, nvl, name, val);
+ break;
+ }
+ case DATA_TYPE_NVLIST: {
+ nvlist_t *val;
+ (void) nvpair_value_nvlist(nvp, &val);
+ RENDER(pctl, nvlist, nvl, name, val);
+ break;
+ }
+ case DATA_TYPE_NVLIST_ARRAY: {
+ nvlist_t **val;
+ (void) nvpair_value_nvlist_array(nvp, &val, &nelem);
+ ARENDER(pctl, nvlist_array, nvl, name, val, nelem);
+ break;
+ }
+ default:
+ (void) fprintf(fp, " unknown data type (%d)", type);
+ break;
+ }
+ nvp = nvlist_next_nvpair(nvl, nvp);
+ }
+}
+
+void
+nvlist_print(FILE *fp, nvlist_t *nvl)
+{
+ struct nvlist_prtctl pc;
+
+ prtctl_defaults(fp, &pc, NULL);
+ nvlist_print_with_indent(nvl, &pc);
+}
+
+void
+nvlist_prt(nvlist_t *nvl, nvlist_prtctl_t pctl)
+{
+ nvlist_print_with_indent(nvl, pctl);
+}
+
+#define NVP(elem, type, vtype, ptype, format) { \
+ vtype value; \
+\
+ (void) nvpair_value_##type(elem, &value); \
+ (void) printf("%*s%s: " format "\n", indent, "", \
+ nvpair_name(elem), (ptype)value); \
+}
+
+#define NVPA(elem, type, vtype, ptype, format) { \
+ uint_t i, count; \
+ vtype *value; \
+\
+ (void) nvpair_value_##type(elem, &value, &count); \
+ for (i = 0; i < count; i++) { \
+ (void) printf("%*s%s[%d]: " format "\n", indent, "", \
+ nvpair_name(elem), i, (ptype)value[i]); \
+ } \
+}
+
+/*
+ * Similar to nvlist_print() but handles arrays slightly differently.
+ */
+void
+dump_nvlist(nvlist_t *list, int indent)
+{
+ nvpair_t *elem = NULL;
+ boolean_t bool_value;
+ nvlist_t *nvlist_value;
+ nvlist_t **nvlist_array_value;
+ uint_t i, count;
+
+ if (list == NULL) {
+ return;
+ }
+
+ while ((elem = nvlist_next_nvpair(list, elem)) != NULL) {
+ switch (nvpair_type(elem)) {
+ case DATA_TYPE_BOOLEAN:
+ (void) printf("%*s%s\n", indent, "", nvpair_name(elem));
+ break;
+
+ case DATA_TYPE_BOOLEAN_VALUE:
+ (void) nvpair_value_boolean_value(elem, &bool_value);
+ (void) printf("%*s%s: %s\n", indent, "",
+ nvpair_name(elem), bool_value ? "true" : "false");
+ break;
+
+ case DATA_TYPE_BYTE:
+ NVP(elem, byte, uchar_t, int, "%u");
+ break;
+
+ case DATA_TYPE_INT8:
+ NVP(elem, int8, int8_t, int, "%d");
+ break;
+
+ case DATA_TYPE_UINT8:
+ NVP(elem, uint8, uint8_t, int, "%u");
+ break;
+
+ case DATA_TYPE_INT16:
+ NVP(elem, int16, int16_t, int, "%d");
+ break;
+
+ case DATA_TYPE_UINT16:
+ NVP(elem, uint16, uint16_t, int, "%u");
+ break;
+
+ case DATA_TYPE_INT32:
+ NVP(elem, int32, int32_t, long, "%ld");
+ break;
+
+ case DATA_TYPE_UINT32:
+ NVP(elem, uint32, uint32_t, ulong_t, "%lu");
+ break;
+
+ case DATA_TYPE_INT64:
+ NVP(elem, int64, int64_t, longlong_t, "%lld");
+ break;
+
+ case DATA_TYPE_UINT64:
+ NVP(elem, uint64, uint64_t, u_longlong_t, "%llu");
+ break;
+
+ case DATA_TYPE_STRING:
+ NVP(elem, string, char *, char *, "'%s'");
+ break;
+
+ case DATA_TYPE_BYTE_ARRAY:
+ NVPA(elem, byte_array, uchar_t, int, "%u");
+ break;
+
+ case DATA_TYPE_INT8_ARRAY:
+ NVPA(elem, int8_array, int8_t, int, "%d");
+ break;
+
+ case DATA_TYPE_UINT8_ARRAY:
+ NVPA(elem, uint8_array, uint8_t, int, "%u");
+ break;
+
+ case DATA_TYPE_INT16_ARRAY:
+ NVPA(elem, int16_array, int16_t, int, "%d");
+ break;
+
+ case DATA_TYPE_UINT16_ARRAY:
+ NVPA(elem, uint16_array, uint16_t, int, "%u");
+ break;
+
+ case DATA_TYPE_INT32_ARRAY:
+ NVPA(elem, int32_array, int32_t, long, "%ld");
+ break;
+
+ case DATA_TYPE_UINT32_ARRAY:
+ NVPA(elem, uint32_array, uint32_t, ulong_t, "%lu");
+ break;
+
+ case DATA_TYPE_INT64_ARRAY:
+ NVPA(elem, int64_array, int64_t, longlong_t, "%lld");
+ break;
+
+ case DATA_TYPE_UINT64_ARRAY:
+ NVPA(elem, uint64_array, uint64_t, u_longlong_t,
+ "%llu");
+ break;
+
+ case DATA_TYPE_STRING_ARRAY:
+ NVPA(elem, string_array, char *, char *, "'%s'");
+ break;
+
+ case DATA_TYPE_NVLIST:
+ (void) nvpair_value_nvlist(elem, &nvlist_value);
+ (void) printf("%*s%s:\n", indent, "",
+ nvpair_name(elem));
+ dump_nvlist(nvlist_value, indent + 4);
+ break;
+
+ case DATA_TYPE_NVLIST_ARRAY:
+ (void) nvpair_value_nvlist_array(elem,
+ &nvlist_array_value, &count);
+ for (i = 0; i < count; i++) {
+ (void) printf("%*s%s[%u]:\n", indent, "",
+ nvpair_name(elem), i);
+ dump_nvlist(nvlist_array_value[i], indent + 4);
+ }
+ break;
+
+ default:
+ (void) printf(dgettext(TEXT_DOMAIN, "bad config type "
+ "%d for %s\n"), nvpair_type(elem),
+ nvpair_name(elem));
+ }
+ }
+}
+
+/*
+ * ======================================================================
+ * | |
+ * | Misc private interface. |
+ * | |
+ * ======================================================================
+ */
+
+/*
+ * Determine if string 'value' matches 'nvp' value. The 'value' string is
+ * converted, depending on the type of 'nvp', prior to match. For numeric
+ * types, a radix independent sscanf conversion of 'value' is used. If 'nvp'
+ * is an array type, 'ai' is the index into the array against which we are
+ * checking for match. If nvp is of DATA_TYPE_STRING*, the caller can pass
+ * in a regex_t compilation of value in 'value_regex' to trigger regular
+ * expression string match instead of simple strcmp().
+ *
+ * Return 1 on match, 0 on no-match, and -1 on error. If the error is
+ * related to value syntax error and 'ep' is non-NULL, *ep will point into
+ * the 'value' string at the location where the error exists.
+ *
+ * NOTE: It may be possible to move the non-regex_t version of this into
+ * common code used by library/kernel/boot.
+ */
+int
+nvpair_value_match_regex(nvpair_t *nvp, int ai,
+ char *value, regex_t *value_regex, char **ep)
+{
+ char *evalue;
+ uint_t a_len;
+ int sr;
+
+ if (ep)
+ *ep = NULL;
+
+ if ((nvp == NULL) || (value == NULL))
+ return (-1); /* error fail match - invalid args */
+
+ /* make sure array and index combination make sense */
+ if ((nvpair_type_is_array(nvp) && (ai < 0)) ||
+ (!nvpair_type_is_array(nvp) && (ai >= 0)))
+ return (-1); /* error fail match - bad index */
+
+ /* non-string values should be single 'chunk' */
+ if ((nvpair_type(nvp) != DATA_TYPE_STRING) &&
+ (nvpair_type(nvp) != DATA_TYPE_STRING_ARRAY)) {
+ value += strspn(value, " \t");
+ evalue = value + strcspn(value, " \t");
+ if (*evalue) {
+ if (ep)
+ *ep = evalue;
+ return (-1); /* error fail match - syntax */
+ }
+ }
+
+ sr = EOF;
+ switch (nvpair_type(nvp)) {
+ case DATA_TYPE_STRING: {
+ char *val;
+
+ /* check string value for match */
+ if (nvpair_value_string(nvp, &val) == 0) {
+ if (value_regex) {
+ if (regexec(value_regex, val,
+ (size_t)0, NULL, 0) == 0)
+ return (1); /* match */
+ } else {
+ if (strcmp(value, val) == 0)
+ return (1); /* match */
+ }
+ }
+ break;
+ }
+ case DATA_TYPE_STRING_ARRAY: {
+ char **val_array;
+
+ /* check indexed string value of array for match */
+ if ((nvpair_value_string_array(nvp, &val_array, &a_len) == 0) &&
+ (ai < a_len)) {
+ if (value_regex) {
+ if (regexec(value_regex, val_array[ai],
+ (size_t)0, NULL, 0) == 0)
+ return (1);
+ } else {
+ if (strcmp(value, val_array[ai]) == 0)
+ return (1);
+ }
+ }
+ break;
+ }
+ case DATA_TYPE_BYTE: {
+ uchar_t val, val_arg;
+
+ /* scanf uchar_t from value and check for match */
+ sr = sscanf(value, "%c", &val_arg);
+ if ((sr == 1) && (nvpair_value_byte(nvp, &val) == 0) &&
+ (val == val_arg))
+ return (1);
+ break;
+ }
+ case DATA_TYPE_BYTE_ARRAY: {
+ uchar_t *val_array, val_arg;
+
+
+ /* check indexed value of array for match */
+ sr = sscanf(value, "%c", &val_arg);
+ if ((sr == 1) &&
+ (nvpair_value_byte_array(nvp, &val_array, &a_len) == 0) &&
+ (ai < a_len) &&
+ (val_array[ai] == val_arg))
+ return (1);
+ break;
+ }
+ case DATA_TYPE_INT8: {
+ int8_t val, val_arg;
+
+ /* scanf int8_t from value and check for match */
+ sr = sscanf(value, "%"SCNi8, &val_arg);
+ if ((sr == 1) &&
+ (nvpair_value_int8(nvp, &val) == 0) &&
+ (val == val_arg))
+ return (1);
+ break;
+ }
+ case DATA_TYPE_INT8_ARRAY: {
+ int8_t *val_array, val_arg;
+
+ /* check indexed value of array for match */
+ sr = sscanf(value, "%"SCNi8, &val_arg);
+ if ((sr == 1) &&
+ (nvpair_value_int8_array(nvp, &val_array, &a_len) == 0) &&
+ (ai < a_len) &&
+ (val_array[ai] == val_arg))
+ return (1);
+ break;
+ }
+ case DATA_TYPE_UINT8: {
+ uint8_t val, val_arg;
+
+ /* scanf uint8_t from value and check for match */
+ sr = sscanf(value, "%"SCNi8, (int8_t *)&val_arg);
+ if ((sr == 1) &&
+ (nvpair_value_uint8(nvp, &val) == 0) &&
+ (val == val_arg))
+ return (1);
+ break;
+ }
+ case DATA_TYPE_UINT8_ARRAY: {
+ uint8_t *val_array, val_arg;
+
+ /* check indexed value of array for match */
+ sr = sscanf(value, "%"SCNi8, (int8_t *)&val_arg);
+ if ((sr == 1) &&
+ (nvpair_value_uint8_array(nvp, &val_array, &a_len) == 0) &&
+ (ai < a_len) &&
+ (val_array[ai] == val_arg))
+ return (1);
+ break;
+ }
+ case DATA_TYPE_INT16: {
+ int16_t val, val_arg;
+
+ /* scanf int16_t from value and check for match */
+ sr = sscanf(value, "%"SCNi16, &val_arg);
+ if ((sr == 1) &&
+ (nvpair_value_int16(nvp, &val) == 0) &&
+ (val == val_arg))
+ return (1);
+ break;
+ }
+ case DATA_TYPE_INT16_ARRAY: {
+ int16_t *val_array, val_arg;
+
+ /* check indexed value of array for match */
+ sr = sscanf(value, "%"SCNi16, &val_arg);
+ if ((sr == 1) &&
+ (nvpair_value_int16_array(nvp, &val_array, &a_len) == 0) &&
+ (ai < a_len) &&
+ (val_array[ai] == val_arg))
+ return (1);
+ break;
+ }
+ case DATA_TYPE_UINT16: {
+ uint16_t val, val_arg;
+
+ /* scanf uint16_t from value and check for match */
+ sr = sscanf(value, "%"SCNi16, (int16_t *)&val_arg);
+ if ((sr == 1) &&
+ (nvpair_value_uint16(nvp, &val) == 0) &&
+ (val == val_arg))
+ return (1);
+ break;
+ }
+ case DATA_TYPE_UINT16_ARRAY: {
+ uint16_t *val_array, val_arg;
+
+ /* check indexed value of array for match */
+ sr = sscanf(value, "%"SCNi16, (int16_t *)&val_arg);
+ if ((sr == 1) &&
+ (nvpair_value_uint16_array(nvp, &val_array, &a_len) == 0) &&
+ (ai < a_len) &&
+ (val_array[ai] == val_arg))
+ return (1);
+ break;
+ }
+ case DATA_TYPE_INT32: {
+ int32_t val, val_arg;
+
+ /* scanf int32_t from value and check for match */
+ sr = sscanf(value, "%"SCNi32, &val_arg);
+ if ((sr == 1) &&
+ (nvpair_value_int32(nvp, &val) == 0) &&
+ (val == val_arg))
+ return (1);
+ break;
+ }
+ case DATA_TYPE_INT32_ARRAY: {
+ int32_t *val_array, val_arg;
+
+ /* check indexed value of array for match */
+ sr = sscanf(value, "%"SCNi32, &val_arg);
+ if ((sr == 1) &&
+ (nvpair_value_int32_array(nvp, &val_array, &a_len) == 0) &&
+ (ai < a_len) &&
+ (val_array[ai] == val_arg))
+ return (1);
+ break;
+ }
+ case DATA_TYPE_UINT32: {
+ uint32_t val, val_arg;
+
+ /* scanf uint32_t from value and check for match */
+ sr = sscanf(value, "%"SCNi32, (int32_t *)&val_arg);
+ if ((sr == 1) &&
+ (nvpair_value_uint32(nvp, &val) == 0) &&
+ (val == val_arg))
+ return (1);
+ break;
+ }
+ case DATA_TYPE_UINT32_ARRAY: {
+ uint32_t *val_array, val_arg;
+
+ /* check indexed value of array for match */
+ sr = sscanf(value, "%"SCNi32, (int32_t *)&val_arg);
+ if ((sr == 1) &&
+ (nvpair_value_uint32_array(nvp, &val_array, &a_len) == 0) &&
+ (ai < a_len) &&
+ (val_array[ai] == val_arg))
+ return (1);
+ break;
+ }
+ case DATA_TYPE_INT64: {
+ int64_t val, val_arg;
+
+ /* scanf int64_t from value and check for match */
+ sr = sscanf(value, "%"SCNi64, &val_arg);
+ if ((sr == 1) &&
+ (nvpair_value_int64(nvp, &val) == 0) &&
+ (val == val_arg))
+ return (1);
+ break;
+ }
+ case DATA_TYPE_INT64_ARRAY: {
+ int64_t *val_array, val_arg;
+
+ /* check indexed value of array for match */
+ sr = sscanf(value, "%"SCNi64, &val_arg);
+ if ((sr == 1) &&
+ (nvpair_value_int64_array(nvp, &val_array, &a_len) == 0) &&
+ (ai < a_len) &&
+ (val_array[ai] == val_arg))
+ return (1);
+ break;
+ }
+ case DATA_TYPE_UINT64: {
+ uint64_t val_arg, val;
+
+ /* scanf uint64_t from value and check for match */
+ sr = sscanf(value, "%"SCNi64, (int64_t *)&val_arg);
+ if ((sr == 1) &&
+ (nvpair_value_uint64(nvp, &val) == 0) &&
+ (val == val_arg))
+ return (1);
+ break;
+ }
+ case DATA_TYPE_UINT64_ARRAY: {
+ uint64_t *val_array, val_arg;
+
+ /* check indexed value of array for match */
+ sr = sscanf(value, "%"SCNi64, (int64_t *)&val_arg);
+ if ((sr == 1) &&
+ (nvpair_value_uint64_array(nvp, &val_array, &a_len) == 0) &&
+ (ai < a_len) &&
+ (val_array[ai] == val_arg))
+ return (1);
+ break;
+ }
+ case DATA_TYPE_BOOLEAN_VALUE: {
+ int32_t val_arg;
+ boolean_t val;
+
+ /* scanf boolean_t from value and check for match */
+ sr = sscanf(value, "%"SCNi32, (int32_t *)&val_arg);
+ if ((sr == 1) &&
+ (nvpair_value_boolean_value(nvp, &val) == 0) &&
+ (val == val_arg))
+ return (1);
+ break;
+ }
+ case DATA_TYPE_BOOLEAN_ARRAY: {
+ boolean_t *val_array;
+ int32_t val_arg;
+
+ /* check indexed value of array for match */
+ sr = sscanf(value, "%"SCNi32, (int32_t *)&val_arg);
+ if ((sr == 1) &&
+ (nvpair_value_boolean_array(nvp,
+ &val_array, &a_len) == 0) &&
+ (ai < a_len) &&
+ (val_array[ai] == val_arg))
+ return (1);
+ break;
+ }
+ case DATA_TYPE_HRTIME:
+ case DATA_TYPE_NVLIST:
+ case DATA_TYPE_NVLIST_ARRAY:
+ case DATA_TYPE_BOOLEAN:
+ case DATA_TYPE_DOUBLE:
+ case DATA_TYPE_UNKNOWN:
+ default:
+ /*
+ * unknown/unsupported data type
+ */
+ return (-1); /* error fail match */
+ }
+
+ /*
+ * check to see if sscanf failed conversion, return approximate
+ * pointer to problem
+ */
+ if (sr != 1) {
+ if (ep)
+ *ep = value;
+ return (-1); /* error fail match - syntax */
+ }
+
+ return (0); /* fail match */
+}
+
+int
+nvpair_value_match(nvpair_t *nvp, int ai, char *value, char **ep)
+{
+ return (nvpair_value_match_regex(nvp, ai, value, NULL, ep));
+}
diff --git a/lib/libnvpair/libnvpair_json.c b/lib/libnvpair/libnvpair_json.c
new file mode 100644
index 000000000000..37a392391fb0
--- /dev/null
+++ b/lib/libnvpair/libnvpair_json.c
@@ -0,0 +1,406 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+/*
+ * Copyright (c) 2014, Joyent, Inc.
+ * Copyright (c) 2017 by Delphix. All rights reserved.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <wchar.h>
+#include <sys/debug.h>
+
+#include "libnvpair.h"
+
+#define FPRINTF(fp, ...) \
+ do { \
+ if (fprintf(fp, __VA_ARGS__) < 0) \
+ return (-1); \
+ } while (0)
+
+/*
+ * When formatting a string for JSON output we must escape certain characters,
+ * as described in RFC4627. This applies to both member names and
+ * DATA_TYPE_STRING values.
+ *
+ * This function will only operate correctly if the following conditions are
+ * met:
+ *
+ * 1. The input String is encoded in the current locale.
+ *
+ * 2. The current locale includes the Basic Multilingual Plane (plane 0)
+ * as defined in the Unicode standard.
+ *
+ * The output will be entirely 7-bit ASCII (as a subset of UTF-8) with all
+ * representable Unicode characters included in their escaped numeric form.
+ */
+static int
+nvlist_print_json_string(FILE *fp, const char *input)
+{
+ mbstate_t mbr;
+ wchar_t c;
+ size_t sz;
+
+ bzero(&mbr, sizeof (mbr));
+
+ FPRINTF(fp, "\"");
+ while ((sz = mbrtowc(&c, input, MB_CUR_MAX, &mbr)) > 0) {
+ switch (c) {
+ case '"':
+ FPRINTF(fp, "\\\"");
+ break;
+ case '\n':
+ FPRINTF(fp, "\\n");
+ break;
+ case '\r':
+ FPRINTF(fp, "\\r");
+ break;
+ case '\\':
+ FPRINTF(fp, "\\\\");
+ break;
+ case '\f':
+ FPRINTF(fp, "\\f");
+ break;
+ case '\t':
+ FPRINTF(fp, "\\t");
+ break;
+ case '\b':
+ FPRINTF(fp, "\\b");
+ break;
+ default:
+ if ((c >= 0x00 && c <= 0x1f) ||
+ (c > 0x7f && c <= 0xffff)) {
+ /*
+ * Render both Control Characters and Unicode
+ * characters in the Basic Multilingual Plane
+ * as JSON-escaped multibyte characters.
+ */
+ FPRINTF(fp, "\\u%04x", (int)(0xffff & c));
+ } else if (c >= 0x20 && c <= 0x7f) {
+ /*
+ * Render other 7-bit ASCII characters directly
+ * and drop other, unrepresentable characters.
+ */
+ FPRINTF(fp, "%c", (int)(0xff & c));
+ }
+ break;
+ }
+ input += sz;
+ }
+
+ if (sz == (size_t)-1 || sz == (size_t)-2) {
+ /*
+ * We last read an invalid multibyte character sequence,
+ * so return an error.
+ */
+ return (-1);
+ }
+
+ FPRINTF(fp, "\"");
+ return (0);
+}
+
+/*
+ * Dump a JSON-formatted representation of an nvlist to the provided FILE *.
+ * This routine does not output any new-lines or additional whitespace other
+ * than that contained in strings, nor does it call fflush(3C).
+ */
+int
+nvlist_print_json(FILE *fp, nvlist_t *nvl)
+{
+ nvpair_t *curr;
+ boolean_t first = B_TRUE;
+
+ FPRINTF(fp, "{");
+
+ for (curr = nvlist_next_nvpair(nvl, NULL); curr;
+ curr = nvlist_next_nvpair(nvl, curr)) {
+ data_type_t type = nvpair_type(curr);
+
+ if (!first)
+ FPRINTF(fp, ",");
+ else
+ first = B_FALSE;
+
+ if (nvlist_print_json_string(fp, nvpair_name(curr)) == -1)
+ return (-1);
+ FPRINTF(fp, ":");
+
+ switch (type) {
+ case DATA_TYPE_STRING: {
+ char *string = fnvpair_value_string(curr);
+ if (nvlist_print_json_string(fp, string) == -1)
+ return (-1);
+ break;
+ }
+
+ case DATA_TYPE_BOOLEAN: {
+ FPRINTF(fp, "true");
+ break;
+ }
+
+ case DATA_TYPE_BOOLEAN_VALUE: {
+ FPRINTF(fp, "%s", fnvpair_value_boolean_value(curr) ==
+ B_TRUE ? "true" : "false");
+ break;
+ }
+
+ case DATA_TYPE_BYTE: {
+ FPRINTF(fp, "%hhu", fnvpair_value_byte(curr));
+ break;
+ }
+
+ case DATA_TYPE_INT8: {
+ FPRINTF(fp, "%hhd", fnvpair_value_int8(curr));
+ break;
+ }
+
+ case DATA_TYPE_UINT8: {
+ FPRINTF(fp, "%hhu", fnvpair_value_uint8(curr));
+ break;
+ }
+
+ case DATA_TYPE_INT16: {
+ FPRINTF(fp, "%hd", fnvpair_value_int16(curr));
+ break;
+ }
+
+ case DATA_TYPE_UINT16: {
+ FPRINTF(fp, "%hu", fnvpair_value_uint16(curr));
+ break;
+ }
+
+ case DATA_TYPE_INT32: {
+ FPRINTF(fp, "%d", fnvpair_value_int32(curr));
+ break;
+ }
+
+ case DATA_TYPE_UINT32: {
+ FPRINTF(fp, "%u", fnvpair_value_uint32(curr));
+ break;
+ }
+
+ case DATA_TYPE_INT64: {
+ FPRINTF(fp, "%lld",
+ (long long)fnvpair_value_int64(curr));
+ break;
+ }
+
+ case DATA_TYPE_UINT64: {
+ FPRINTF(fp, "%llu",
+ (unsigned long long)fnvpair_value_uint64(curr));
+ break;
+ }
+
+ case DATA_TYPE_HRTIME: {
+ hrtime_t val;
+ VERIFY0(nvpair_value_hrtime(curr, &val));
+ FPRINTF(fp, "%llu", (unsigned long long)val);
+ break;
+ }
+
+ case DATA_TYPE_DOUBLE: {
+ double val;
+ VERIFY0(nvpair_value_double(curr, &val));
+ FPRINTF(fp, "%f", val);
+ break;
+ }
+
+ case DATA_TYPE_NVLIST: {
+ if (nvlist_print_json(fp,
+ fnvpair_value_nvlist(curr)) == -1)
+ return (-1);
+ break;
+ }
+
+ case DATA_TYPE_STRING_ARRAY: {
+ char **val;
+ uint_t valsz, i;
+ VERIFY0(nvpair_value_string_array(curr, &val, &valsz));
+ FPRINTF(fp, "[");
+ for (i = 0; i < valsz; i++) {
+ if (i > 0)
+ FPRINTF(fp, ",");
+ if (nvlist_print_json_string(fp, val[i]) == -1)
+ return (-1);
+ }
+ FPRINTF(fp, "]");
+ break;
+ }
+
+ case DATA_TYPE_NVLIST_ARRAY: {
+ nvlist_t **val;
+ uint_t valsz, i;
+ VERIFY0(nvpair_value_nvlist_array(curr, &val, &valsz));
+ FPRINTF(fp, "[");
+ for (i = 0; i < valsz; i++) {
+ if (i > 0)
+ FPRINTF(fp, ",");
+ if (nvlist_print_json(fp, val[i]) == -1)
+ return (-1);
+ }
+ FPRINTF(fp, "]");
+ break;
+ }
+
+ case DATA_TYPE_BOOLEAN_ARRAY: {
+ boolean_t *val;
+ uint_t valsz, i;
+ VERIFY0(nvpair_value_boolean_array(curr, &val, &valsz));
+ FPRINTF(fp, "[");
+ for (i = 0; i < valsz; i++) {
+ if (i > 0)
+ FPRINTF(fp, ",");
+ FPRINTF(fp, val[i] == B_TRUE ?
+ "true" : "false");
+ }
+ FPRINTF(fp, "]");
+ break;
+ }
+
+ case DATA_TYPE_BYTE_ARRAY: {
+ uchar_t *val;
+ uint_t valsz, i;
+ VERIFY0(nvpair_value_byte_array(curr, &val, &valsz));
+ FPRINTF(fp, "[");
+ for (i = 0; i < valsz; i++) {
+ if (i > 0)
+ FPRINTF(fp, ",");
+ FPRINTF(fp, "%hhu", val[i]);
+ }
+ FPRINTF(fp, "]");
+ break;
+ }
+
+ case DATA_TYPE_UINT8_ARRAY: {
+ uint8_t *val;
+ uint_t valsz, i;
+ VERIFY0(nvpair_value_uint8_array(curr, &val, &valsz));
+ FPRINTF(fp, "[");
+ for (i = 0; i < valsz; i++) {
+ if (i > 0)
+ FPRINTF(fp, ",");
+ FPRINTF(fp, "%hhu", val[i]);
+ }
+ FPRINTF(fp, "]");
+ break;
+ }
+
+ case DATA_TYPE_INT8_ARRAY: {
+ int8_t *val;
+ uint_t valsz, i;
+ VERIFY0(nvpair_value_int8_array(curr, &val, &valsz));
+ FPRINTF(fp, "[");
+ for (i = 0; i < valsz; i++) {
+ if (i > 0)
+ FPRINTF(fp, ",");
+ FPRINTF(fp, "%hhd", val[i]);
+ }
+ FPRINTF(fp, "]");
+ break;
+ }
+
+ case DATA_TYPE_UINT16_ARRAY: {
+ uint16_t *val;
+ uint_t valsz, i;
+ VERIFY0(nvpair_value_uint16_array(curr, &val, &valsz));
+ FPRINTF(fp, "[");
+ for (i = 0; i < valsz; i++) {
+ if (i > 0)
+ FPRINTF(fp, ",");
+ FPRINTF(fp, "%hu", val[i]);
+ }
+ FPRINTF(fp, "]");
+ break;
+ }
+
+ case DATA_TYPE_INT16_ARRAY: {
+ int16_t *val;
+ uint_t valsz, i;
+ VERIFY0(nvpair_value_int16_array(curr, &val, &valsz));
+ FPRINTF(fp, "[");
+ for (i = 0; i < valsz; i++) {
+ if (i > 0)
+ FPRINTF(fp, ",");
+ FPRINTF(fp, "%hd", val[i]);
+ }
+ FPRINTF(fp, "]");
+ break;
+ }
+
+ case DATA_TYPE_UINT32_ARRAY: {
+ uint32_t *val;
+ uint_t valsz, i;
+ VERIFY0(nvpair_value_uint32_array(curr, &val, &valsz));
+ FPRINTF(fp, "[");
+ for (i = 0; i < valsz; i++) {
+ if (i > 0)
+ FPRINTF(fp, ",");
+ FPRINTF(fp, "%u", val[i]);
+ }
+ FPRINTF(fp, "]");
+ break;
+ }
+
+ case DATA_TYPE_INT32_ARRAY: {
+ int32_t *val;
+ uint_t valsz, i;
+ VERIFY0(nvpair_value_int32_array(curr, &val, &valsz));
+ FPRINTF(fp, "[");
+ for (i = 0; i < valsz; i++) {
+ if (i > 0)
+ FPRINTF(fp, ",");
+ FPRINTF(fp, "%d", val[i]);
+ }
+ FPRINTF(fp, "]");
+ break;
+ }
+
+ case DATA_TYPE_UINT64_ARRAY: {
+ uint64_t *val;
+ uint_t valsz, i;
+ VERIFY0(nvpair_value_uint64_array(curr, &val, &valsz));
+ FPRINTF(fp, "[");
+ for (i = 0; i < valsz; i++) {
+ if (i > 0)
+ FPRINTF(fp, ",");
+ FPRINTF(fp, "%llu",
+ (unsigned long long)val[i]);
+ }
+ FPRINTF(fp, "]");
+ break;
+ }
+
+ case DATA_TYPE_INT64_ARRAY: {
+ int64_t *val;
+ uint_t valsz, i;
+ VERIFY0(nvpair_value_int64_array(curr, &val, &valsz));
+ FPRINTF(fp, "[");
+ for (i = 0; i < valsz; i++) {
+ if (i > 0)
+ FPRINTF(fp, ",");
+ FPRINTF(fp, "%lld", (long long)val[i]);
+ }
+ FPRINTF(fp, "]");
+ break;
+ }
+
+ case DATA_TYPE_UNKNOWN:
+ case DATA_TYPE_DONTCARE:
+ return (-1);
+ }
+
+ }
+
+ FPRINTF(fp, "}");
+ return (0);
+}
diff --git a/lib/libnvpair/nvpair_alloc_system.c b/lib/libnvpair/nvpair_alloc_system.c
new file mode 100644
index 000000000000..54dde1ee644f
--- /dev/null
+++ b/lib/libnvpair/nvpair_alloc_system.c
@@ -0,0 +1,65 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2004 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+
+
+#include <rpc/types.h>
+#include <sys/kmem.h>
+#include <sys/nvpair.h>
+
+static void *
+nv_alloc_sys(nv_alloc_t *nva, size_t size)
+{
+ return (kmem_alloc(size, (int)(uintptr_t)nva->nva_arg));
+}
+
+/*ARGSUSED*/
+static void
+nv_free_sys(nv_alloc_t *nva, void *buf, size_t size)
+{
+ kmem_free(buf, size);
+}
+
+static const nv_alloc_ops_t system_ops = {
+ NULL, /* nv_ao_init() */
+ NULL, /* nv_ao_fini() */
+ nv_alloc_sys, /* nv_ao_alloc() */
+ nv_free_sys, /* nv_ao_free() */
+ NULL /* nv_ao_reset() */
+};
+
+nv_alloc_t nv_alloc_sleep_def = {
+ &system_ops,
+ (void *)KM_SLEEP
+};
+
+nv_alloc_t nv_alloc_nosleep_def = {
+ &system_ops,
+ (void *)KM_NOSLEEP
+};
+
+nv_alloc_t *nv_alloc_sleep = &nv_alloc_sleep_def;
+nv_alloc_t *nv_alloc_nosleep = &nv_alloc_nosleep_def;
diff --git a/lib/libshare/Makefile.am b/lib/libshare/Makefile.am
new file mode 100644
index 000000000000..e730ee3bea1b
--- /dev/null
+++ b/lib/libshare/Makefile.am
@@ -0,0 +1,25 @@
+include $(top_srcdir)/config/Rules.am
+
+DEFAULT_INCLUDES += -I$(srcdir)
+
+noinst_LTLIBRARIES = libshare.la
+
+USER_C = \
+ libshare_impl.h \
+ libshare.c \
+ nfs.h \
+ smb.h
+
+if BUILD_LINUX
+USER_C += \
+ os/linux/nfs.c \
+ os/linux/smb.c
+endif
+
+if BUILD_FREEBSD
+USER_C += \
+ os/freebsd/nfs.c \
+ os/freebsd/smb.c
+endif
+
+libshare_la_SOURCES = $(USER_C)
diff --git a/lib/libshare/libshare.c b/lib/libshare/libshare.c
new file mode 100644
index 000000000000..d32a282a36e3
--- /dev/null
+++ b/lib/libshare/libshare.c
@@ -0,0 +1,366 @@
+/*
+ * 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) 2002, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011 Gunnar Beutner
+ * Copyright (c) 2018, 2020 by Delphix. All rights reserved.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <strings.h>
+#include <libintl.h>
+#include <sys/file.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <libzfs.h>
+#include <libshare.h>
+#include "libzfs_impl.h"
+#include "libshare_impl.h"
+#include "nfs.h"
+#include "smb.h"
+
+static sa_share_impl_t alloc_share(const char *zfsname, const char *path);
+static void free_share(sa_share_impl_t share);
+
+static int fstypes_count;
+static sa_fstype_t *fstypes;
+
+sa_fstype_t *
+register_fstype(const char *name, const sa_share_ops_t *ops)
+{
+ sa_fstype_t *fstype;
+
+ fstype = calloc(1, sizeof (sa_fstype_t));
+
+ if (fstype == NULL)
+ return (NULL);
+
+ fstype->name = name;
+ fstype->ops = ops;
+ fstype->fsinfo_index = fstypes_count;
+
+ fstypes_count++;
+
+ fstype->next = fstypes;
+ fstypes = fstype;
+
+ return (fstype);
+}
+
+__attribute__((constructor)) static void
+libshare_init(void)
+{
+ libshare_nfs_init();
+ libshare_smb_init();
+}
+
+int
+sa_enable_share(const char *zfsname, const char *mountpoint,
+ const char *shareopts, char *protocol)
+{
+ int rc, ret = SA_OK;
+ boolean_t found_protocol = B_FALSE;
+ sa_fstype_t *fstype;
+
+ sa_share_impl_t impl_share = alloc_share(zfsname, mountpoint);
+ if (impl_share == NULL)
+ return (SA_NO_MEMORY);
+
+ fstype = fstypes;
+ while (fstype != NULL) {
+ if (strcmp(fstype->name, protocol) == 0) {
+
+ rc = fstype->ops->update_shareopts(impl_share,
+ shareopts);
+ if (rc != SA_OK)
+ break;
+
+ rc = fstype->ops->enable_share(impl_share);
+ if (rc != SA_OK)
+ ret = rc;
+
+ found_protocol = B_TRUE;
+ }
+
+ fstype = fstype->next;
+ }
+ free_share(impl_share);
+
+ return (found_protocol ? ret : SA_INVALID_PROTOCOL);
+}
+
+int
+sa_disable_share(const char *mountpoint, char *protocol)
+{
+ int rc, ret = SA_OK;
+ boolean_t found_protocol = B_FALSE;
+ sa_fstype_t *fstype;
+
+ sa_share_impl_t impl_share = alloc_share(NULL, mountpoint);
+ if (impl_share == NULL)
+ return (SA_NO_MEMORY);
+
+ fstype = fstypes;
+ while (fstype != NULL) {
+ if (strcmp(fstype->name, protocol) == 0) {
+
+ rc = fstype->ops->disable_share(impl_share);
+ if (rc != SA_OK)
+ ret = rc;
+
+ found_protocol = B_TRUE;
+ }
+
+ fstype = fstype->next;
+ }
+ free_share(impl_share);
+
+ return (found_protocol ? ret : SA_INVALID_PROTOCOL);
+}
+
+boolean_t
+sa_is_shared(const char *mountpoint, char *protocol)
+{
+ sa_fstype_t *fstype;
+ boolean_t ret = B_FALSE;
+
+ /* guid value is not used */
+ sa_share_impl_t impl_share = alloc_share(NULL, mountpoint);
+ if (impl_share == NULL)
+ return (B_FALSE);
+
+ fstype = fstypes;
+ while (fstype != NULL) {
+ if (strcmp(fstype->name, protocol) == 0) {
+ ret = fstype->ops->is_shared(impl_share);
+ }
+ fstype = fstype->next;
+ }
+ free_share(impl_share);
+ return (ret);
+}
+
+void
+sa_commit_shares(const char *protocol)
+{
+ sa_fstype_t *fstype = fstypes;
+ while (fstype != NULL) {
+ if (strcmp(fstype->name, protocol) == 0)
+ fstype->ops->commit_shares();
+ fstype = fstype->next;
+ }
+}
+
+/*
+ * sa_errorstr(err)
+ *
+ * convert an error value to an error string
+ */
+char *
+sa_errorstr(int err)
+{
+ static char errstr[32];
+ char *ret = NULL;
+
+ switch (err) {
+ case SA_OK:
+ ret = dgettext(TEXT_DOMAIN, "ok");
+ break;
+ case SA_NO_SUCH_PATH:
+ ret = dgettext(TEXT_DOMAIN, "path doesn't exist");
+ break;
+ case SA_NO_MEMORY:
+ ret = dgettext(TEXT_DOMAIN, "no memory");
+ break;
+ case SA_DUPLICATE_NAME:
+ ret = dgettext(TEXT_DOMAIN, "name in use");
+ break;
+ case SA_BAD_PATH:
+ ret = dgettext(TEXT_DOMAIN, "bad path");
+ break;
+ case SA_NO_SUCH_GROUP:
+ ret = dgettext(TEXT_DOMAIN, "no such group");
+ break;
+ case SA_CONFIG_ERR:
+ ret = dgettext(TEXT_DOMAIN, "configuration error");
+ break;
+ case SA_SYSTEM_ERR:
+ ret = dgettext(TEXT_DOMAIN, "system error");
+ break;
+ case SA_SYNTAX_ERR:
+ ret = dgettext(TEXT_DOMAIN, "syntax error");
+ break;
+ case SA_NO_PERMISSION:
+ ret = dgettext(TEXT_DOMAIN, "no permission");
+ break;
+ case SA_BUSY:
+ ret = dgettext(TEXT_DOMAIN, "busy");
+ break;
+ case SA_NO_SUCH_PROP:
+ ret = dgettext(TEXT_DOMAIN, "no such property");
+ break;
+ case SA_INVALID_NAME:
+ ret = dgettext(TEXT_DOMAIN, "invalid name");
+ break;
+ case SA_INVALID_PROTOCOL:
+ ret = dgettext(TEXT_DOMAIN, "invalid protocol");
+ break;
+ case SA_NOT_ALLOWED:
+ ret = dgettext(TEXT_DOMAIN, "operation not allowed");
+ break;
+ case SA_BAD_VALUE:
+ ret = dgettext(TEXT_DOMAIN, "bad property value");
+ break;
+ case SA_INVALID_SECURITY:
+ ret = dgettext(TEXT_DOMAIN, "invalid security type");
+ break;
+ case SA_NO_SUCH_SECURITY:
+ ret = dgettext(TEXT_DOMAIN, "security type not found");
+ break;
+ case SA_VALUE_CONFLICT:
+ ret = dgettext(TEXT_DOMAIN, "property value conflict");
+ break;
+ case SA_NOT_IMPLEMENTED:
+ ret = dgettext(TEXT_DOMAIN, "not implemented");
+ break;
+ case SA_INVALID_PATH:
+ ret = dgettext(TEXT_DOMAIN, "invalid path");
+ break;
+ case SA_NOT_SUPPORTED:
+ ret = dgettext(TEXT_DOMAIN, "operation not supported");
+ break;
+ case SA_PROP_SHARE_ONLY:
+ ret = dgettext(TEXT_DOMAIN, "property not valid for group");
+ break;
+ case SA_NOT_SHARED:
+ ret = dgettext(TEXT_DOMAIN, "not shared");
+ break;
+ case SA_NO_SUCH_RESOURCE:
+ ret = dgettext(TEXT_DOMAIN, "no such resource");
+ break;
+ case SA_RESOURCE_REQUIRED:
+ ret = dgettext(TEXT_DOMAIN, "resource name required");
+ break;
+ case SA_MULTIPLE_ERROR:
+ ret = dgettext(TEXT_DOMAIN, "errors from multiple protocols");
+ break;
+ case SA_PATH_IS_SUBDIR:
+ ret = dgettext(TEXT_DOMAIN, "path is a subpath of share");
+ break;
+ case SA_PATH_IS_PARENTDIR:
+ ret = dgettext(TEXT_DOMAIN, "path is parent of a share");
+ break;
+ case SA_NO_SECTION:
+ ret = dgettext(TEXT_DOMAIN, "protocol requires a section");
+ break;
+ case SA_NO_PROPERTIES:
+ ret = dgettext(TEXT_DOMAIN, "properties not found");
+ break;
+ case SA_NO_SUCH_SECTION:
+ ret = dgettext(TEXT_DOMAIN, "section not found");
+ break;
+ case SA_PASSWORD_ENC:
+ ret = dgettext(TEXT_DOMAIN, "passwords must be encrypted");
+ break;
+ case SA_SHARE_EXISTS:
+ ret = dgettext(TEXT_DOMAIN, "path or file is already shared");
+ break;
+ default:
+ (void) snprintf(errstr, sizeof (errstr),
+ dgettext(TEXT_DOMAIN, "unknown %d"), err);
+ ret = errstr;
+ }
+ return (ret);
+}
+
+int
+sa_validate_shareopts(char *options, char *proto)
+{
+ sa_fstype_t *fstype;
+
+ fstype = fstypes;
+ while (fstype != NULL) {
+ if (strcmp(fstype->name, proto) != 0) {
+ fstype = fstype->next;
+ continue;
+ }
+
+ return (fstype->ops->validate_shareopts(options));
+ }
+
+ return (SA_INVALID_PROTOCOL);
+}
+
+static sa_share_impl_t
+alloc_share(const char *zfsname, const char *mountpoint)
+{
+ sa_share_impl_t impl_share;
+
+ impl_share = calloc(1, sizeof (struct sa_share_impl));
+
+ if (impl_share == NULL)
+ return (NULL);
+
+ if (mountpoint != NULL &&
+ ((impl_share->sa_mountpoint = strdup(mountpoint)) == NULL)) {
+ free(impl_share);
+ return (NULL);
+ }
+
+ if (zfsname != NULL &&
+ ((impl_share->sa_zfsname = strdup(zfsname)) == NULL)) {
+ free(impl_share->sa_mountpoint);
+ free(impl_share);
+ return (NULL);
+ }
+
+ impl_share->sa_fsinfo = calloc(fstypes_count,
+ sizeof (sa_share_fsinfo_t));
+ if (impl_share->sa_fsinfo == NULL) {
+ free(impl_share->sa_mountpoint);
+ free(impl_share->sa_zfsname);
+ free(impl_share);
+ return (NULL);
+ }
+
+ return (impl_share);
+}
+
+static void
+free_share(sa_share_impl_t impl_share)
+{
+ sa_fstype_t *fstype;
+
+ fstype = fstypes;
+ while (fstype != NULL) {
+ fstype->ops->clear_shareopts(impl_share);
+ fstype = fstype->next;
+ }
+
+ free(impl_share->sa_mountpoint);
+ free(impl_share->sa_zfsname);
+ free(impl_share->sa_fsinfo);
+ free(impl_share);
+}
diff --git a/lib/libshare/libshare_impl.h b/lib/libshare/libshare_impl.h
new file mode 100644
index 000000000000..cc5ef40085b1
--- /dev/null
+++ b/lib/libshare/libshare_impl.h
@@ -0,0 +1,61 @@
+/*
+ * 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) 2002, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011 Gunnar Beutner
+ * Copyright (c) 2019, 2020 by Delphix. All rights reserved.
+ */
+
+typedef struct sa_share_fsinfo {
+ char *shareopts;
+} sa_share_fsinfo_t;
+
+typedef struct sa_share_impl {
+ char *sa_mountpoint;
+ char *sa_zfsname;
+
+ sa_share_fsinfo_t *sa_fsinfo; /* per-fstype information */
+} *sa_share_impl_t;
+
+#define FSINFO(impl_share, fstype) \
+ (&(impl_share->sa_fsinfo[fstype->fsinfo_index]))
+
+typedef struct sa_share_ops {
+ int (*enable_share)(sa_share_impl_t share);
+ int (*disable_share)(sa_share_impl_t share);
+ boolean_t (*is_shared)(sa_share_impl_t share);
+ int (*validate_shareopts)(const char *shareopts);
+ int (*update_shareopts)(sa_share_impl_t impl_share,
+ const char *shareopts);
+ void (*clear_shareopts)(sa_share_impl_t impl_share);
+ int (*commit_shares)(void);
+} sa_share_ops_t;
+
+typedef struct sa_fstype {
+ struct sa_fstype *next;
+
+ const char *name;
+ const sa_share_ops_t *ops;
+ int fsinfo_index;
+} sa_fstype_t;
+
+sa_fstype_t *register_fstype(const char *name, const sa_share_ops_t *ops);
diff --git a/lib/libshare/nfs.h b/lib/libshare/nfs.h
new file mode 100644
index 000000000000..b9ea6ee2f8cf
--- /dev/null
+++ b/lib/libshare/nfs.h
@@ -0,0 +1,27 @@
+/*
+ * 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) 2002, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011 Gunnar Beutner
+ */
+
+void libshare_nfs_init(void);
diff --git a/lib/libshare/os/freebsd/nfs.c b/lib/libshare/os/freebsd/nfs.c
new file mode 100644
index 000000000000..65f3b11bf9b3
--- /dev/null
+++ b/lib/libshare/os/freebsd/nfs.c
@@ -0,0 +1,448 @@
+/*
+ * Copyright (c) 2007 Pawel Jakub Dawidek <pjd@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * Copyright (c) 2020 by Delphix. All rights reserved.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/vfs.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libutil.h>
+#include <signal.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <libintl.h>
+
+#include "libzfs_impl.h"
+#include "libshare_impl.h"
+#include "nfs.h"
+
+#define _PATH_MOUNTDPID "/var/run/mountd.pid"
+#define FILE_HEADER "# !!! DO NOT EDIT THIS FILE MANUALLY !!!\n\n"
+#define OPTSSIZE 1024
+#define MAXLINESIZE (PATH_MAX + OPTSSIZE)
+#define ZFS_EXPORTS_FILE "/etc/zfs/exports"
+#define ZFS_EXPORTS_LOCK ZFS_EXPORTS_FILE".lock"
+
+static sa_fstype_t *nfs_fstype;
+
+static int nfs_lock_fd = -1;
+
+/*
+ * The nfs_exports_[lock|unlock] is used to guard against conconcurrent
+ * updates to the exports file. Each protocol is responsible for
+ * providing the necessary locking to ensure consistency.
+ */
+static int
+nfs_exports_lock(void)
+{
+ nfs_lock_fd = open(ZFS_EXPORTS_LOCK,
+ O_RDWR | O_CREAT, 0600);
+ if (nfs_lock_fd == -1) {
+ fprintf(stderr, "failed to lock %s: %s\n",
+ ZFS_EXPORTS_LOCK, strerror(errno));
+ return (errno);
+ }
+ if (flock(nfs_lock_fd, LOCK_EX) != 0) {
+ fprintf(stderr, "failed to lock %s: %s\n",
+ ZFS_EXPORTS_LOCK, strerror(errno));
+ return (errno);
+ }
+ return (0);
+}
+
+static void
+nfs_exports_unlock(void)
+{
+ verify(nfs_lock_fd > 0);
+
+ if (flock(nfs_lock_fd, LOCK_UN) != 0) {
+ fprintf(stderr, "failed to unlock %s: %s\n",
+ ZFS_EXPORTS_LOCK, strerror(errno));
+ }
+ close(nfs_lock_fd);
+ nfs_lock_fd = -1;
+}
+
+/*
+ * Read one line from a file. Skip comments, empty lines and a line with a
+ * mountpoint specified in the 'skip' argument.
+ *
+ * NOTE: This function returns a static buffer and thus is not thread-safe.
+ */
+static char *
+zgetline(FILE *fd, const char *skip)
+{
+ static char line[MAXLINESIZE];
+ size_t len, skiplen = 0;
+ char *s, last;
+
+ if (skip != NULL)
+ skiplen = strlen(skip);
+ for (;;) {
+ s = fgets(line, sizeof (line), fd);
+ if (s == NULL)
+ return (NULL);
+ /* Skip empty lines and comments. */
+ if (line[0] == '\n' || line[0] == '#')
+ continue;
+ len = strlen(line);
+ if (line[len - 1] == '\n')
+ line[len - 1] = '\0';
+ last = line[skiplen];
+ /* Skip the given mountpoint. */
+ if (skip != NULL && strncmp(skip, line, skiplen) == 0 &&
+ (last == '\t' || last == ' ' || last == '\0')) {
+ continue;
+ }
+ break;
+ }
+ return (line);
+}
+
+/*
+ * This function translate options to a format acceptable by exports(5), eg.
+ *
+ * -ro -network=192.168.0.0 -mask=255.255.255.0 -maproot=0 \
+ * zfs.freebsd.org 69.147.83.54
+ *
+ * Accepted input formats:
+ *
+ * ro,network=192.168.0.0,mask=255.255.255.0,maproot=0,zfs.freebsd.org
+ * ro network=192.168.0.0 mask=255.255.255.0 maproot=0 zfs.freebsd.org
+ * -ro,-network=192.168.0.0,-mask=255.255.255.0,-maproot=0,zfs.freebsd.org
+ * -ro -network=192.168.0.0 -mask=255.255.255.0 -maproot=0 \
+ * zfs.freebsd.org
+ *
+ * Recognized keywords:
+ *
+ * ro, maproot, mapall, mask, network, sec, alldirs, public, webnfs,
+ * index, quiet
+ *
+ * NOTE: This function returns a static buffer and thus is not thread-safe.
+ */
+static char *
+translate_opts(const char *shareopts)
+{
+ static const char *known_opts[] = { "ro", "maproot", "mapall", "mask",
+ "network", "sec", "alldirs", "public", "webnfs", "index", "quiet",
+ NULL };
+ static char newopts[OPTSSIZE];
+ char oldopts[OPTSSIZE];
+ char *o, *s = NULL;
+ unsigned int i;
+ size_t len;
+
+ strlcpy(oldopts, shareopts, sizeof (oldopts));
+ newopts[0] = '\0';
+ s = oldopts;
+ while ((o = strsep(&s, "-, ")) != NULL) {
+ if (o[0] == '\0')
+ continue;
+ for (i = 0; known_opts[i] != NULL; i++) {
+ len = strlen(known_opts[i]);
+ if (strncmp(known_opts[i], o, len) == 0 &&
+ (o[len] == '\0' || o[len] == '=')) {
+ strlcat(newopts, "-", sizeof (newopts));
+ break;
+ }
+ }
+ strlcat(newopts, o, sizeof (newopts));
+ strlcat(newopts, " ", sizeof (newopts));
+ }
+ return (newopts);
+}
+
+static char *
+nfs_init_tmpfile(void)
+{
+ char *tmpfile = NULL;
+
+ if (asprintf(&tmpfile, "%s%s", ZFS_EXPORTS_FILE, ".XXXXXXXX") == -1) {
+ fprintf(stderr, "Unable to allocate buffer for temporary "
+ "file name\n");
+ return (NULL);
+ }
+
+ int fd = mkstemp(tmpfile);
+ if (fd == -1) {
+ fprintf(stderr, "Unable to create temporary file: %s",
+ strerror(errno));
+ free(tmpfile);
+ return (NULL);
+ }
+ close(fd);
+ return (tmpfile);
+}
+
+static int
+nfs_fini_tmpfile(char *tmpfile)
+{
+ if (rename(tmpfile, ZFS_EXPORTS_FILE) == -1) {
+ fprintf(stderr, "Unable to rename %s: %s\n", tmpfile,
+ strerror(errno));
+ unlink(tmpfile);
+ free(tmpfile);
+ return (SA_SYSTEM_ERR);
+ }
+ free(tmpfile);
+ return (SA_OK);
+}
+
+/*
+ * This function copies all entries from the exports file to "filename",
+ * omitting any entries for the specified mountpoint.
+ */
+static int
+nfs_copy_entries(char *filename, const char *mountpoint)
+{
+ int error = SA_OK;
+ char *line;
+
+ /*
+ * If the file doesn't exist then there is nothing more
+ * we need to do.
+ */
+ FILE *oldfp = fopen(ZFS_EXPORTS_FILE, "r");
+ if (oldfp == NULL)
+ return (SA_OK);
+
+ FILE *newfp = fopen(filename, "w+");
+ fputs(FILE_HEADER, newfp);
+ while ((line = zgetline(oldfp, mountpoint)) != NULL)
+ fprintf(newfp, "%s\n", line);
+ if (ferror(oldfp) != 0) {
+ error = ferror(oldfp);
+ }
+ if (error == 0 && ferror(newfp) != 0) {
+ error = ferror(newfp);
+ }
+
+ if (fclose(newfp) != 0) {
+ fprintf(stderr, "Unable to close file %s: %s\n",
+ filename, strerror(errno));
+ error = error != 0 ? error : SA_SYSTEM_ERR;
+ }
+ fclose(oldfp);
+
+ return (error);
+}
+
+static int
+nfs_enable_share(sa_share_impl_t impl_share)
+{
+ char *filename = NULL;
+ int error;
+
+ if ((filename = nfs_init_tmpfile()) == NULL)
+ return (SA_SYSTEM_ERR);
+
+ error = nfs_exports_lock();
+ if (error != 0) {
+ unlink(filename);
+ free(filename);
+ return (error);
+ }
+
+ error = nfs_copy_entries(filename, impl_share->sa_mountpoint);
+ if (error != SA_OK) {
+ unlink(filename);
+ free(filename);
+ nfs_exports_unlock();
+ return (error);
+ }
+
+ FILE *fp = fopen(filename, "a+");
+ if (fp == NULL) {
+ fprintf(stderr, "failed to open %s file: %s", filename,
+ strerror(errno));
+ unlink(filename);
+ free(filename);
+ nfs_exports_unlock();
+ return (SA_SYSTEM_ERR);
+ }
+ char *shareopts = FSINFO(impl_share, nfs_fstype)->shareopts;
+ if (strcmp(shareopts, "on") == 0)
+ shareopts = "";
+
+ if (fprintf(fp, "%s\t%s\n", impl_share->sa_mountpoint,
+ translate_opts(shareopts)) < 0) {
+ fprintf(stderr, "failed to write to %s\n", filename);
+ fclose(fp);
+ unlink(filename);
+ free(filename);
+ nfs_exports_unlock();
+ return (SA_SYSTEM_ERR);
+ }
+
+ if (fclose(fp) != 0) {
+ fprintf(stderr, "Unable to close file %s: %s\n",
+ filename, strerror(errno));
+ unlink(filename);
+ free(filename);
+ nfs_exports_unlock();
+ return (SA_SYSTEM_ERR);
+ }
+ error = nfs_fini_tmpfile(filename);
+ nfs_exports_unlock();
+ return (error);
+}
+
+static int
+nfs_disable_share(sa_share_impl_t impl_share)
+{
+ int error;
+ char *filename = NULL;
+
+ if ((filename = nfs_init_tmpfile()) == NULL)
+ return (SA_SYSTEM_ERR);
+
+ error = nfs_exports_lock();
+ if (error != 0) {
+ unlink(filename);
+ free(filename);
+ return (error);
+ }
+
+ error = nfs_copy_entries(filename, impl_share->sa_mountpoint);
+ if (error != SA_OK) {
+ unlink(filename);
+ free(filename);
+ nfs_exports_unlock();
+ return (error);
+ }
+
+ error = nfs_fini_tmpfile(filename);
+ nfs_exports_unlock();
+ return (error);
+}
+
+/*
+ * NOTE: This function returns a static buffer and thus is not thread-safe.
+ */
+static boolean_t
+nfs_is_shared(sa_share_impl_t impl_share)
+{
+ static char line[MAXLINESIZE];
+ char *s, last;
+ size_t len;
+ char *mntpoint = impl_share->sa_mountpoint;
+ size_t mntlen = strlen(mntpoint);
+
+ FILE *fp = fopen(ZFS_EXPORTS_FILE, "r");
+ if (fp == NULL)
+ return (B_FALSE);
+
+ for (;;) {
+ s = fgets(line, sizeof (line), fp);
+ if (s == NULL)
+ return (B_FALSE);
+ /* Skip empty lines and comments. */
+ if (line[0] == '\n' || line[0] == '#')
+ continue;
+ len = strlen(line);
+ if (line[len - 1] == '\n')
+ line[len - 1] = '\0';
+ last = line[mntlen];
+ /* Skip the given mountpoint. */
+ if (strncmp(mntpoint, line, mntlen) == 0 &&
+ (last == '\t' || last == ' ' || last == '\0')) {
+ fclose(fp);
+ return (B_TRUE);
+ }
+ }
+ fclose(fp);
+ return (B_FALSE);
+}
+
+static int
+nfs_validate_shareopts(const char *shareopts)
+{
+ return (SA_OK);
+}
+
+static int
+nfs_update_shareopts(sa_share_impl_t impl_share, const char *shareopts)
+{
+ FSINFO(impl_share, nfs_fstype)->shareopts = (char *)shareopts;
+ return (SA_OK);
+}
+
+static void
+nfs_clear_shareopts(sa_share_impl_t impl_share)
+{
+ FSINFO(impl_share, nfs_fstype)->shareopts = NULL;
+}
+
+/*
+ * Commit the shares by restarting mountd.
+ */
+static int
+nfs_commit_shares(void)
+{
+ struct pidfh *pfh;
+ pid_t mountdpid;
+
+ pfh = pidfile_open(_PATH_MOUNTDPID, 0600, &mountdpid);
+ if (pfh != NULL) {
+ /* Mountd is not running. */
+ pidfile_remove(pfh);
+ return (SA_OK);
+ }
+ if (errno != EEXIST) {
+ /* Cannot open pidfile for some reason. */
+ return (SA_SYSTEM_ERR);
+ }
+ /* We have mountd(8) PID in mountdpid variable. */
+ kill(mountdpid, SIGHUP);
+ return (SA_OK);
+}
+
+static const sa_share_ops_t nfs_shareops = {
+ .enable_share = nfs_enable_share,
+ .disable_share = nfs_disable_share,
+ .is_shared = nfs_is_shared,
+
+ .validate_shareopts = nfs_validate_shareopts,
+ .update_shareopts = nfs_update_shareopts,
+ .clear_shareopts = nfs_clear_shareopts,
+ .commit_shares = nfs_commit_shares,
+};
+
+/*
+ * Initializes the NFS functionality of libshare.
+ */
+void
+libshare_nfs_init(void)
+{
+ nfs_fstype = register_fstype("nfs", &nfs_shareops);
+}
diff --git a/lib/libshare/os/freebsd/smb.c b/lib/libshare/os/freebsd/smb.c
new file mode 100644
index 000000000000..5b606ab96955
--- /dev/null
+++ b/lib/libshare/os/freebsd/smb.c
@@ -0,0 +1,128 @@
+/*
+ * 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) 2020 by Delphix. All rights reserved.
+ */
+
+#include <time.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+#include <fcntl.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <libzfs.h>
+#include <libshare.h>
+#include "libshare_impl.h"
+#include "smb.h"
+
+static sa_fstype_t *smb_fstype;
+
+/*
+ * Enables SMB sharing for the specified share.
+ */
+static int
+smb_enable_share(sa_share_impl_t impl_share)
+{
+ fprintf(stderr, "No SMB support in FreeBSD yet.\n");
+ return (SA_NOT_SUPPORTED);
+}
+/*
+ * Disables SMB sharing for the specified share.
+ */
+static int
+smb_disable_share(sa_share_impl_t impl_share)
+{
+ fprintf(stderr, "No SMB support in FreeBSD yet.\n");
+ return (SA_NOT_SUPPORTED);
+}
+
+/*
+ * Checks whether the specified SMB share options are syntactically correct.
+ */
+static int
+smb_validate_shareopts(const char *shareopts)
+{
+ fprintf(stderr, "No SMB support in FreeBSD yet.\n");
+ return (SA_NOT_SUPPORTED);
+}
+
+/*
+ * Checks whether a share is currently active.
+ */
+static boolean_t
+smb_is_share_active(sa_share_impl_t impl_share)
+{
+ return (B_FALSE);
+}
+
+/*
+ * Called to update a share's options. A share's options might be out of
+ * date if the share was loaded from disk and the "sharesmb" dataset
+ * property has changed in the meantime. This function also takes care
+ * of re-enabling the share if necessary.
+ */
+static int
+smb_update_shareopts(sa_share_impl_t impl_share, const char *shareopts)
+{
+ return (SA_OK);
+}
+
+static int
+smb_update_shares(void)
+{
+ /* Not implemented */
+ return (0);
+}
+/*
+ * Clears a share's SMB options. Used by libshare to
+ * clean up shares that are about to be free()'d.
+ */
+static void
+smb_clear_shareopts(sa_share_impl_t impl_share)
+{
+ FSINFO(impl_share, smb_fstype)->shareopts = NULL;
+}
+
+static const sa_share_ops_t smb_shareops = {
+ .enable_share = smb_enable_share,
+ .disable_share = smb_disable_share,
+ .is_shared = smb_is_share_active,
+
+ .validate_shareopts = smb_validate_shareopts,
+ .update_shareopts = smb_update_shareopts,
+ .clear_shareopts = smb_clear_shareopts,
+ .commit_shares = smb_update_shares,
+};
+
+/*
+ * Initializes the SMB functionality of libshare.
+ */
+void
+libshare_smb_init(void)
+{
+ smb_fstype = register_fstype("smb", &smb_shareops);
+}
diff --git a/lib/libshare/os/linux/nfs.c b/lib/libshare/os/linux/nfs.c
new file mode 100644
index 000000000000..bc949213f69c
--- /dev/null
+++ b/lib/libshare/os/linux/nfs.c
@@ -0,0 +1,712 @@
+/*
+ * 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) 2002, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011 Gunnar Beutner
+ * Copyright (c) 2012 Cyril Plisko. All rights reserved.
+ * Copyright (c) 2019, 2020 by Delphix. All rights reserved.
+ */
+
+#include <dirent.h>
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+#include <errno.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <libzfs.h>
+#include <libshare.h>
+#include "libshare_impl.h"
+#include "nfs.h"
+
+#define FILE_HEADER "# !!! DO NOT EDIT THIS FILE MANUALLY !!!\n\n"
+#define ZFS_EXPORTS_DIR "/etc/exports.d"
+#define ZFS_EXPORTS_FILE ZFS_EXPORTS_DIR"/zfs.exports"
+#define ZFS_EXPORTS_LOCK ZFS_EXPORTS_FILE".lock"
+
+static sa_fstype_t *nfs_fstype;
+
+typedef int (*nfs_shareopt_callback_t)(const char *opt, const char *value,
+ void *cookie);
+
+typedef int (*nfs_host_callback_t)(const char *sharepath, const char *filename,
+ const char *host, const char *security, const char *access, void *cookie);
+
+static int nfs_lock_fd = -1;
+
+/*
+ * The nfs_exports_[lock|unlock] is used to guard against conconcurrent
+ * updates to the exports file. Each protocol is responsible for
+ * providing the necessary locking to ensure consistency.
+ */
+static int
+nfs_exports_lock(void)
+{
+ nfs_lock_fd = open(ZFS_EXPORTS_LOCK,
+ O_RDWR | O_CREAT, 0600);
+ if (nfs_lock_fd == -1) {
+ fprintf(stderr, "failed to lock %s: %s\n",
+ ZFS_EXPORTS_LOCK, strerror(errno));
+ return (errno);
+ }
+ if (flock(nfs_lock_fd, LOCK_EX) != 0) {
+ fprintf(stderr, "failed to lock %s: %s\n",
+ ZFS_EXPORTS_LOCK, strerror(errno));
+ return (errno);
+ }
+ return (0);
+}
+
+static void
+nfs_exports_unlock(void)
+{
+ verify(nfs_lock_fd > 0);
+
+ if (flock(nfs_lock_fd, LOCK_UN) != 0) {
+ fprintf(stderr, "failed to unlock %s: %s\n",
+ ZFS_EXPORTS_LOCK, strerror(errno));
+ }
+ close(nfs_lock_fd);
+ nfs_lock_fd = -1;
+}
+
+/*
+ * Invokes the specified callback function for each Solaris share option
+ * listed in the specified string.
+ */
+static int
+foreach_nfs_shareopt(const char *shareopts,
+ nfs_shareopt_callback_t callback, void *cookie)
+{
+ char *shareopts_dup, *opt, *cur, *value;
+ int was_nul, error;
+
+ if (shareopts == NULL)
+ return (SA_OK);
+
+ if (strcmp(shareopts, "on") == 0)
+ shareopts = "rw,crossmnt";
+
+ shareopts_dup = strdup(shareopts);
+
+
+ if (shareopts_dup == NULL)
+ return (SA_NO_MEMORY);
+
+ opt = shareopts_dup;
+ was_nul = 0;
+
+ while (1) {
+ cur = opt;
+
+ while (*cur != ',' && *cur != '\0')
+ cur++;
+
+ if (*cur == '\0')
+ was_nul = 1;
+
+ *cur = '\0';
+
+ if (cur > opt) {
+ value = strchr(opt, '=');
+
+ if (value != NULL) {
+ *value = '\0';
+ value++;
+ }
+
+ error = callback(opt, value, cookie);
+
+ if (error != SA_OK) {
+ free(shareopts_dup);
+ return (error);
+ }
+ }
+
+ opt = cur + 1;
+
+ if (was_nul)
+ break;
+ }
+
+ free(shareopts_dup);
+
+ return (SA_OK);
+}
+
+typedef struct nfs_host_cookie_s {
+ nfs_host_callback_t callback;
+ const char *sharepath;
+ void *cookie;
+ const char *filename;
+ const char *security;
+} nfs_host_cookie_t;
+
+/*
+ * Helper function for foreach_nfs_host. This function checks whether the
+ * current share option is a host specification and invokes a callback
+ * function with information about the host.
+ */
+static int
+foreach_nfs_host_cb(const char *opt, const char *value, void *pcookie)
+{
+ int error;
+ const char *access;
+ char *host_dup, *host, *next;
+ nfs_host_cookie_t *udata = (nfs_host_cookie_t *)pcookie;
+
+#ifdef DEBUG
+ fprintf(stderr, "foreach_nfs_host_cb: key=%s, value=%s\n", opt, value);
+#endif
+
+ if (strcmp(opt, "sec") == 0)
+ udata->security = value;
+
+ if (strcmp(opt, "rw") == 0 || strcmp(opt, "ro") == 0) {
+ if (value == NULL)
+ value = "*";
+
+ access = opt;
+
+ host_dup = strdup(value);
+
+ if (host_dup == NULL)
+ return (SA_NO_MEMORY);
+
+ host = host_dup;
+
+ do {
+ next = strchr(host, ':');
+ if (next != NULL) {
+ *next = '\0';
+ next++;
+ }
+
+ error = udata->callback(udata->filename,
+ udata->sharepath, host, udata->security,
+ access, udata->cookie);
+
+ if (error != SA_OK) {
+ free(host_dup);
+
+ return (error);
+ }
+
+ host = next;
+ } while (host != NULL);
+
+ free(host_dup);
+ }
+
+ return (SA_OK);
+}
+
+/*
+ * Invokes a callback function for all NFS hosts that are set for a share.
+ */
+static int
+foreach_nfs_host(sa_share_impl_t impl_share, char *filename,
+ nfs_host_callback_t callback, void *cookie)
+{
+ nfs_host_cookie_t udata;
+ char *shareopts;
+
+ udata.callback = callback;
+ udata.sharepath = impl_share->sa_mountpoint;
+ udata.cookie = cookie;
+ udata.filename = filename;
+ udata.security = "sys";
+
+ shareopts = FSINFO(impl_share, nfs_fstype)->shareopts;
+
+ return (foreach_nfs_shareopt(shareopts, foreach_nfs_host_cb,
+ &udata));
+}
+
+/*
+ * Converts a Solaris NFS host specification to its Linux equivalent.
+ */
+static int
+get_linux_hostspec(const char *solaris_hostspec, char **plinux_hostspec)
+{
+ /*
+ * For now we just support CIDR masks (e.g. @192.168.0.0/16) and host
+ * wildcards (e.g. *.example.org).
+ */
+ if (solaris_hostspec[0] == '@') {
+ /*
+ * Solaris host specifier, e.g. @192.168.0.0/16; we just need
+ * to skip the @ in this case
+ */
+ *plinux_hostspec = strdup(solaris_hostspec + 1);
+ } else {
+ *plinux_hostspec = strdup(solaris_hostspec);
+ }
+
+ if (*plinux_hostspec == NULL) {
+ return (SA_NO_MEMORY);
+ }
+
+ return (SA_OK);
+}
+
+/*
+ * Adds a Linux share option to an array of NFS options.
+ */
+static int
+add_linux_shareopt(char **plinux_opts, const char *key, const char *value)
+{
+ size_t len = 0;
+ char *new_linux_opts;
+
+ if (*plinux_opts != NULL)
+ len = strlen(*plinux_opts);
+
+ new_linux_opts = realloc(*plinux_opts, len + 1 + strlen(key) +
+ (value ? 1 + strlen(value) : 0) + 1);
+
+ if (new_linux_opts == NULL)
+ return (SA_NO_MEMORY);
+
+ new_linux_opts[len] = '\0';
+
+ if (len > 0)
+ strcat(new_linux_opts, ",");
+
+ strcat(new_linux_opts, key);
+
+ if (value != NULL) {
+ strcat(new_linux_opts, "=");
+ strcat(new_linux_opts, value);
+ }
+
+ *plinux_opts = new_linux_opts;
+
+ return (SA_OK);
+}
+
+/*
+ * Validates and converts a single Solaris share option to its Linux
+ * equivalent.
+ */
+static int
+get_linux_shareopts_cb(const char *key, const char *value, void *cookie)
+{
+ char **plinux_opts = (char **)cookie;
+
+ /* host-specific options, these are taken care of elsewhere */
+ if (strcmp(key, "ro") == 0 || strcmp(key, "rw") == 0 ||
+ strcmp(key, "sec") == 0)
+ return (SA_OK);
+
+ if (strcmp(key, "anon") == 0)
+ key = "anonuid";
+
+ if (strcmp(key, "root_mapping") == 0) {
+ (void) add_linux_shareopt(plinux_opts, "root_squash", NULL);
+ key = "anonuid";
+ }
+
+ if (strcmp(key, "nosub") == 0)
+ key = "subtree_check";
+
+ if (strcmp(key, "insecure") != 0 && strcmp(key, "secure") != 0 &&
+ strcmp(key, "async") != 0 && strcmp(key, "sync") != 0 &&
+ strcmp(key, "no_wdelay") != 0 && strcmp(key, "wdelay") != 0 &&
+ strcmp(key, "nohide") != 0 && strcmp(key, "hide") != 0 &&
+ strcmp(key, "crossmnt") != 0 &&
+ strcmp(key, "no_subtree_check") != 0 &&
+ strcmp(key, "subtree_check") != 0 &&
+ strcmp(key, "insecure_locks") != 0 &&
+ strcmp(key, "secure_locks") != 0 &&
+ strcmp(key, "no_auth_nlm") != 0 && strcmp(key, "auth_nlm") != 0 &&
+ strcmp(key, "no_acl") != 0 && strcmp(key, "mountpoint") != 0 &&
+ strcmp(key, "mp") != 0 && strcmp(key, "fsuid") != 0 &&
+ strcmp(key, "refer") != 0 && strcmp(key, "replicas") != 0 &&
+ strcmp(key, "root_squash") != 0 &&
+ strcmp(key, "no_root_squash") != 0 &&
+ strcmp(key, "all_squash") != 0 &&
+ strcmp(key, "no_all_squash") != 0 && strcmp(key, "fsid") != 0 &&
+ strcmp(key, "anonuid") != 0 && strcmp(key, "anongid") != 0) {
+ return (SA_SYNTAX_ERR);
+ }
+
+ (void) add_linux_shareopt(plinux_opts, key, value);
+
+ return (SA_OK);
+}
+
+/*
+ * Takes a string containing Solaris share options (e.g. "sync,no_acl") and
+ * converts them to a NULL-terminated array of Linux NFS options.
+ */
+static int
+get_linux_shareopts(const char *shareopts, char **plinux_opts)
+{
+ int error;
+
+ assert(plinux_opts != NULL);
+
+ *plinux_opts = NULL;
+
+ /* no_subtree_check - Default as of nfs-utils v1.1.0 */
+ (void) add_linux_shareopt(plinux_opts, "no_subtree_check", NULL);
+
+ /* mountpoint - Restrict exports to ZFS mountpoints */
+ (void) add_linux_shareopt(plinux_opts, "mountpoint", NULL);
+
+ error = foreach_nfs_shareopt(shareopts, get_linux_shareopts_cb,
+ plinux_opts);
+
+ if (error != SA_OK) {
+ free(*plinux_opts);
+ *plinux_opts = NULL;
+ }
+
+ return (error);
+}
+
+static char *
+nfs_init_tmpfile(void)
+{
+ char *tmpfile = NULL;
+
+ if (asprintf(&tmpfile, "%s%s", ZFS_EXPORTS_FILE, ".XXXXXXXX") == -1) {
+ fprintf(stderr, "Unable to allocate temporary file\n");
+ return (NULL);
+ }
+
+ int fd = mkstemp(tmpfile);
+ if (fd == -1) {
+ fprintf(stderr, "Unable to create temporary file: %s",
+ strerror(errno));
+ free(tmpfile);
+ return (NULL);
+ }
+ close(fd);
+ return (tmpfile);
+}
+
+static int
+nfs_fini_tmpfile(char *tmpfile)
+{
+ if (rename(tmpfile, ZFS_EXPORTS_FILE) == -1) {
+ fprintf(stderr, "Unable to rename %s: %s\n", tmpfile,
+ strerror(errno));
+ unlink(tmpfile);
+ free(tmpfile);
+ return (SA_SYSTEM_ERR);
+ }
+ free(tmpfile);
+ return (SA_OK);
+}
+
+/*
+ * This function populates an entry into /etc/exports.d/zfs.exports.
+ * This file is consumed by the linux nfs server so that zfs shares are
+ * automatically exported upon boot or whenever the nfs server restarts.
+ */
+static int
+nfs_add_entry(const char *filename, const char *sharepath,
+ const char *host, const char *security, const char *access_opts,
+ void *pcookie)
+{
+ int error;
+ char *linuxhost;
+ const char *linux_opts = (const char *)pcookie;
+
+ error = get_linux_hostspec(host, &linuxhost);
+ if (error != SA_OK)
+ return (error);
+
+ if (linux_opts == NULL)
+ linux_opts = "";
+
+ FILE *fp = fopen(filename, "a+");
+ if (fp == NULL) {
+ fprintf(stderr, "failed to open %s file: %s", filename,
+ strerror(errno));
+ free(linuxhost);
+ return (SA_SYSTEM_ERR);
+ }
+
+ if (fprintf(fp, "%s %s(sec=%s,%s,%s)\n", sharepath, linuxhost,
+ security, access_opts, linux_opts) < 0) {
+ fprintf(stderr, "failed to write to %s\n", filename);
+ free(linuxhost);
+ fclose(fp);
+ return (SA_SYSTEM_ERR);
+ }
+
+ free(linuxhost);
+ if (fclose(fp) != 0) {
+ fprintf(stderr, "Unable to close file %s: %s\n",
+ filename, strerror(errno));
+ return (SA_SYSTEM_ERR);
+ }
+ return (SA_OK);
+}
+
+/*
+ * This function copies all entries from the exports file to "filename",
+ * omitting any entries for the specified mountpoint.
+ */
+static int
+nfs_copy_entries(char *filename, const char *mountpoint)
+{
+ char *buf = NULL;
+ size_t buflen = 0;
+ int error = SA_OK;
+
+ /*
+ * If the file doesn't exist then there is nothing more
+ * we need to do.
+ */
+ FILE *oldfp = fopen(ZFS_EXPORTS_FILE, "r");
+ if (oldfp == NULL)
+ return (SA_OK);
+
+ FILE *newfp = fopen(filename, "w+");
+ fputs(FILE_HEADER, newfp);
+ while ((getline(&buf, &buflen, oldfp)) != -1) {
+ char *space = NULL;
+
+ if (buf[0] == '\n' || buf[0] == '#')
+ continue;
+
+ if ((space = strchr(buf, ' ')) != NULL) {
+ int mountpoint_len = strlen(mountpoint);
+
+ if (space - buf == mountpoint_len &&
+ strncmp(mountpoint, buf, mountpoint_len) == 0) {
+ continue;
+ }
+ }
+ fputs(buf, newfp);
+ }
+
+ if (oldfp != NULL && ferror(oldfp) != 0) {
+ error = ferror(oldfp);
+ }
+ if (error == 0 && ferror(newfp) != 0) {
+ error = ferror(newfp);
+ }
+
+ free(buf);
+ if (fclose(newfp) != 0) {
+ fprintf(stderr, "Unable to close file %s: %s\n",
+ filename, strerror(errno));
+ error = error != 0 ? error : SA_SYSTEM_ERR;
+ }
+ fclose(oldfp);
+
+ return (error);
+}
+
+/*
+ * Enables NFS sharing for the specified share.
+ */
+static int
+nfs_enable_share(sa_share_impl_t impl_share)
+{
+ char *shareopts, *linux_opts;
+ char *filename = NULL;
+ int error;
+
+ if ((filename = nfs_init_tmpfile()) == NULL)
+ return (SA_SYSTEM_ERR);
+
+ error = nfs_exports_lock();
+ if (error != 0) {
+ unlink(filename);
+ free(filename);
+ return (error);
+ }
+
+ error = nfs_copy_entries(filename, impl_share->sa_mountpoint);
+ if (error != SA_OK) {
+ unlink(filename);
+ free(filename);
+ nfs_exports_unlock();
+ return (error);
+ }
+
+ shareopts = FSINFO(impl_share, nfs_fstype)->shareopts;
+ error = get_linux_shareopts(shareopts, &linux_opts);
+ if (error != SA_OK) {
+ unlink(filename);
+ free(filename);
+ nfs_exports_unlock();
+ return (error);
+ }
+
+ error = foreach_nfs_host(impl_share, filename, nfs_add_entry,
+ linux_opts);
+ free(linux_opts);
+ if (error == 0) {
+ error = nfs_fini_tmpfile(filename);
+ } else {
+ unlink(filename);
+ free(filename);
+ }
+ nfs_exports_unlock();
+ return (error);
+}
+
+/*
+ * Disables NFS sharing for the specified share.
+ */
+static int
+nfs_disable_share(sa_share_impl_t impl_share)
+{
+ int error;
+ char *filename = NULL;
+
+ if ((filename = nfs_init_tmpfile()) == NULL)
+ return (SA_SYSTEM_ERR);
+
+ error = nfs_exports_lock();
+ if (error != 0) {
+ unlink(filename);
+ free(filename);
+ return (error);
+ }
+
+ error = nfs_copy_entries(filename, impl_share->sa_mountpoint);
+ if (error != SA_OK) {
+ unlink(filename);
+ free(filename);
+ nfs_exports_unlock();
+ return (error);
+ }
+ error = nfs_fini_tmpfile(filename);
+ nfs_exports_unlock();
+ return (error);
+}
+
+static boolean_t
+nfs_is_shared(sa_share_impl_t impl_share)
+{
+ size_t buflen = 0;
+ char *buf = NULL;
+
+ FILE *fp = fopen(ZFS_EXPORTS_FILE, "r");
+ if (fp == NULL) {
+ return (B_FALSE);
+ }
+ while ((getline(&buf, &buflen, fp)) != -1) {
+ char *space = NULL;
+
+ if ((space = strchr(buf, ' ')) != NULL) {
+ int mountpoint_len = strlen(impl_share->sa_mountpoint);
+
+ if (space - buf == mountpoint_len &&
+ strncmp(impl_share->sa_mountpoint, buf,
+ mountpoint_len) == 0) {
+ fclose(fp);
+ free(buf);
+ return (B_TRUE);
+ }
+ }
+ }
+ free(buf);
+ fclose(fp);
+ return (B_FALSE);
+}
+
+/*
+ * Checks whether the specified NFS share options are syntactically correct.
+ */
+static int
+nfs_validate_shareopts(const char *shareopts)
+{
+ char *linux_opts;
+ int error;
+
+ error = get_linux_shareopts(shareopts, &linux_opts);
+
+ if (error != SA_OK)
+ return (error);
+
+ free(linux_opts);
+ return (SA_OK);
+}
+
+static int
+nfs_update_shareopts(sa_share_impl_t impl_share, const char *shareopts)
+{
+ FSINFO(impl_share, nfs_fstype)->shareopts = (char *)shareopts;
+ return (SA_OK);
+}
+
+/*
+ * Clears a share's NFS options. Used by libshare to
+ * clean up shares that are about to be free()'d.
+ */
+static void
+nfs_clear_shareopts(sa_share_impl_t impl_share)
+{
+ FSINFO(impl_share, nfs_fstype)->shareopts = NULL;
+}
+
+static int
+nfs_commit_shares(void)
+{
+ char *argv[] = {
+ "/usr/sbin/exportfs",
+ "-ra",
+ NULL
+ };
+
+ return (libzfs_run_process(argv[0], argv, 0));
+}
+
+static const sa_share_ops_t nfs_shareops = {
+ .enable_share = nfs_enable_share,
+ .disable_share = nfs_disable_share,
+ .is_shared = nfs_is_shared,
+
+ .validate_shareopts = nfs_validate_shareopts,
+ .update_shareopts = nfs_update_shareopts,
+ .clear_shareopts = nfs_clear_shareopts,
+ .commit_shares = nfs_commit_shares,
+};
+
+/*
+ * Initializes the NFS functionality of libshare.
+ */
+void
+libshare_nfs_init(void)
+{
+ struct stat sb;
+
+ nfs_fstype = register_fstype("nfs", &nfs_shareops);
+
+ if (stat(ZFS_EXPORTS_DIR, &sb) < 0 &&
+ mkdir(ZFS_EXPORTS_DIR, 0755) < 0) {
+ fprintf(stderr, "failed to create %s: %s\n",
+ ZFS_EXPORTS_DIR, strerror(errno));
+ }
+}
diff --git a/lib/libshare/os/linux/smb.c b/lib/libshare/os/linux/smb.c
new file mode 100644
index 000000000000..3dcf666eb695
--- /dev/null
+++ b/lib/libshare/os/linux/smb.c
@@ -0,0 +1,453 @@
+/*
+ * 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) 2002, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011,2012 Turbo Fredriksson <turbo@bayour.com>, based on nfs.c
+ * by Gunnar Beutner
+ * Copyright (c) 2019, 2020 by Delphix. All rights reserved.
+ *
+ * This is an addition to the zfs device driver to add, modify and remove SMB
+ * shares using the 'net share' command that comes with Samba.
+ *
+ * TESTING
+ * Make sure that samba listens to 'localhost' (127.0.0.1) and that the options
+ * 'usershare max shares' and 'usershare owner only' have been reviewed/set
+ * accordingly (see zfs(8) for information).
+ *
+ * Once configuration in samba have been done, test that this
+ * works with the following three commands (in this case, my ZFS
+ * filesystem is called 'share/Test1'):
+ *
+ * (root)# net -U root -S 127.0.0.1 usershare add Test1 /share/Test1 \
+ * "Comment: /share/Test1" "Everyone:F"
+ * (root)# net usershare list | grep -i test
+ * (root)# net -U root -S 127.0.0.1 usershare delete Test1
+ *
+ * The first command will create a user share that gives everyone full access.
+ * To limit the access below that, use normal UNIX commands (chmod, chown etc).
+ */
+
+#include <time.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+#include <fcntl.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <libzfs.h>
+#include <libshare.h>
+#include "libshare_impl.h"
+#include "smb.h"
+
+static boolean_t smb_available(void);
+
+static sa_fstype_t *smb_fstype;
+
+smb_share_t *smb_shares;
+static int smb_disable_share(sa_share_impl_t impl_share);
+static boolean_t smb_is_share_active(sa_share_impl_t impl_share);
+
+/*
+ * Retrieve the list of SMB shares.
+ */
+static int
+smb_retrieve_shares(void)
+{
+ int rc = SA_OK;
+ char file_path[PATH_MAX], line[512], *token, *key, *value;
+ char *dup_value = NULL, *path = NULL, *comment = NULL, *name = NULL;
+ char *guest_ok = NULL;
+ DIR *shares_dir;
+ FILE *share_file_fp = NULL;
+ struct dirent *directory;
+ struct stat eStat;
+ smb_share_t *shares, *new_shares = NULL;
+
+ /* opendir(), stat() */
+ shares_dir = opendir(SHARE_DIR);
+ if (shares_dir == NULL)
+ return (SA_SYSTEM_ERR);
+
+ /* Go through the directory, looking for shares */
+ while ((directory = readdir(shares_dir))) {
+ if (directory->d_name[0] == '.')
+ continue;
+
+ snprintf(file_path, sizeof (file_path),
+ "%s/%s", SHARE_DIR, directory->d_name);
+
+ if (stat(file_path, &eStat) == -1) {
+ rc = SA_SYSTEM_ERR;
+ goto out;
+ }
+
+ if (!S_ISREG(eStat.st_mode))
+ continue;
+
+ if ((share_file_fp = fopen(file_path, "r")) == NULL) {
+ rc = SA_SYSTEM_ERR;
+ goto out;
+ }
+
+ name = strdup(directory->d_name);
+ if (name == NULL) {
+ rc = SA_NO_MEMORY;
+ goto out;
+ }
+
+ while (fgets(line, sizeof (line), share_file_fp)) {
+ if (line[0] == '#')
+ continue;
+
+ /* Trim trailing new-line character(s). */
+ while (line[strlen(line) - 1] == '\r' ||
+ line[strlen(line) - 1] == '\n')
+ line[strlen(line) - 1] = '\0';
+
+ /* Split the line in two, separated by '=' */
+ token = strchr(line, '=');
+ if (token == NULL)
+ continue;
+
+ key = line;
+ value = token + 1;
+ *token = '\0';
+
+ dup_value = strdup(value);
+ if (dup_value == NULL) {
+ rc = SA_NO_MEMORY;
+ goto out;
+ }
+
+ if (strcmp(key, "path") == 0) {
+ free(path);
+ path = dup_value;
+ } else if (strcmp(key, "comment") == 0) {
+ free(comment);
+ comment = dup_value;
+ } else if (strcmp(key, "guest_ok") == 0) {
+ free(guest_ok);
+ guest_ok = dup_value;
+ } else
+ free(dup_value);
+
+ dup_value = NULL;
+
+ if (path == NULL || comment == NULL || guest_ok == NULL)
+ continue; /* Incomplete share definition */
+ else {
+ shares = (smb_share_t *)
+ malloc(sizeof (smb_share_t));
+ if (shares == NULL) {
+ rc = SA_NO_MEMORY;
+ goto out;
+ }
+
+ (void) strlcpy(shares->name, name,
+ sizeof (shares->name));
+
+ (void) strlcpy(shares->path, path,
+ sizeof (shares->path));
+
+ (void) strlcpy(shares->comment, comment,
+ sizeof (shares->comment));
+
+ shares->guest_ok = atoi(guest_ok);
+
+ shares->next = new_shares;
+ new_shares = shares;
+
+ free(path);
+ free(comment);
+ free(guest_ok);
+
+ path = NULL;
+ comment = NULL;
+ guest_ok = NULL;
+ }
+ }
+
+out:
+ if (share_file_fp != NULL) {
+ fclose(share_file_fp);
+ share_file_fp = NULL;
+ }
+
+ free(name);
+ free(path);
+ free(comment);
+ free(guest_ok);
+
+ name = NULL;
+ path = NULL;
+ comment = NULL;
+ guest_ok = NULL;
+ }
+ closedir(shares_dir);
+
+ smb_shares = new_shares;
+
+ return (rc);
+}
+
+/*
+ * Used internally by smb_enable_share to enable sharing for a single host.
+ */
+static int
+smb_enable_share_one(const char *sharename, const char *sharepath)
+{
+ char *argv[10], *pos;
+ char name[SMB_NAME_MAX], comment[SMB_COMMENT_MAX];
+ int rc;
+
+ /* Support ZFS share name regexp '[[:alnum:]_-.: ]' */
+ strlcpy(name, sharename, sizeof (name));
+ name [sizeof (name)-1] = '\0';
+
+ pos = name;
+ while (*pos != '\0') {
+ switch (*pos) {
+ case '/':
+ case '-':
+ case ':':
+ case ' ':
+ *pos = '_';
+ }
+
+ ++pos;
+ }
+
+ /*
+ * CMD: net -S NET_CMD_ARG_HOST usershare add Test1 /share/Test1 \
+ * "Comment" "Everyone:F"
+ */
+ snprintf(comment, sizeof (comment), "Comment: %s", sharepath);
+
+ argv[0] = NET_CMD_PATH;
+ argv[1] = (char *)"-S";
+ argv[2] = NET_CMD_ARG_HOST;
+ argv[3] = (char *)"usershare";
+ argv[4] = (char *)"add";
+ argv[5] = (char *)name;
+ argv[6] = (char *)sharepath;
+ argv[7] = (char *)comment;
+ argv[8] = "Everyone:F";
+ argv[9] = NULL;
+
+ rc = libzfs_run_process(argv[0], argv, 0);
+ if (rc < 0)
+ return (SA_SYSTEM_ERR);
+
+ /* Reload the share file */
+ (void) smb_retrieve_shares();
+
+ return (SA_OK);
+}
+
+/*
+ * Enables SMB sharing for the specified share.
+ */
+static int
+smb_enable_share(sa_share_impl_t impl_share)
+{
+ char *shareopts;
+
+ if (!smb_available())
+ return (SA_SYSTEM_ERR);
+
+ if (smb_is_share_active(impl_share))
+ smb_disable_share(impl_share);
+
+ shareopts = FSINFO(impl_share, smb_fstype)->shareopts;
+ if (shareopts == NULL) /* on/off */
+ return (SA_SYSTEM_ERR);
+
+ if (strcmp(shareopts, "off") == 0)
+ return (SA_OK);
+
+ /* Magic: Enable (i.e., 'create new') share */
+ return (smb_enable_share_one(impl_share->sa_zfsname,
+ impl_share->sa_mountpoint));
+}
+
+/*
+ * Used internally by smb_disable_share to disable sharing for a single host.
+ */
+static int
+smb_disable_share_one(const char *sharename)
+{
+ int rc;
+ char *argv[7];
+
+ /* CMD: net -S NET_CMD_ARG_HOST usershare delete Test1 */
+ argv[0] = NET_CMD_PATH;
+ argv[1] = (char *)"-S";
+ argv[2] = NET_CMD_ARG_HOST;
+ argv[3] = (char *)"usershare";
+ argv[4] = (char *)"delete";
+ argv[5] = strdup(sharename);
+ argv[6] = NULL;
+
+ rc = libzfs_run_process(argv[0], argv, 0);
+ if (rc < 0)
+ return (SA_SYSTEM_ERR);
+ else
+ return (SA_OK);
+}
+
+/*
+ * Disables SMB sharing for the specified share.
+ */
+static int
+smb_disable_share(sa_share_impl_t impl_share)
+{
+ smb_share_t *shares = smb_shares;
+
+ if (!smb_available()) {
+ /*
+ * The share can't possibly be active, so nothing
+ * needs to be done to disable it.
+ */
+ return (SA_OK);
+ }
+
+ while (shares != NULL) {
+ if (strcmp(impl_share->sa_mountpoint, shares->path) == 0)
+ return (smb_disable_share_one(shares->name));
+
+ shares = shares->next;
+ }
+
+ return (SA_OK);
+}
+
+/*
+ * Checks whether the specified SMB share options are syntactically correct.
+ */
+static int
+smb_validate_shareopts(const char *shareopts)
+{
+ /* TODO: Accept 'name' and sec/acl (?) */
+ if ((strcmp(shareopts, "off") == 0) || (strcmp(shareopts, "on") == 0))
+ return (SA_OK);
+
+ return (SA_SYNTAX_ERR);
+}
+
+/*
+ * Checks whether a share is currently active.
+ */
+static boolean_t
+smb_is_share_active(sa_share_impl_t impl_share)
+{
+ smb_share_t *iter = smb_shares;
+
+ if (!smb_available())
+ return (B_FALSE);
+
+ /* Retrieve the list of (possible) active shares */
+ smb_retrieve_shares();
+
+ while (iter != NULL) {
+ if (strcmp(impl_share->sa_mountpoint, iter->path) == 0)
+ return (B_TRUE);
+
+ iter = iter->next;
+ }
+
+ return (B_FALSE);
+}
+
+/*
+ * Called to update a share's options. A share's options might be out of
+ * date if the share was loaded from disk and the "sharesmb" dataset
+ * property has changed in the meantime. This function also takes care
+ * of re-enabling the share if necessary.
+ */
+static int
+smb_update_shareopts(sa_share_impl_t impl_share, const char *shareopts)
+{
+ if (!impl_share)
+ return (SA_SYSTEM_ERR);
+
+ FSINFO(impl_share, smb_fstype)->shareopts = (char *)shareopts;
+ return (SA_OK);
+}
+
+static int
+smb_update_shares(void)
+{
+ /* Not implemented */
+ return (0);
+}
+
+/*
+ * Clears a share's SMB options. Used by libshare to
+ * clean up shares that are about to be free()'d.
+ */
+static void
+smb_clear_shareopts(sa_share_impl_t impl_share)
+{
+ FSINFO(impl_share, smb_fstype)->shareopts = NULL;
+}
+
+static const sa_share_ops_t smb_shareops = {
+ .enable_share = smb_enable_share,
+ .disable_share = smb_disable_share,
+ .is_shared = smb_is_share_active,
+
+ .validate_shareopts = smb_validate_shareopts,
+ .update_shareopts = smb_update_shareopts,
+ .clear_shareopts = smb_clear_shareopts,
+ .commit_shares = smb_update_shares,
+};
+
+/*
+ * Provides a convenient wrapper for determining SMB availability
+ */
+static boolean_t
+smb_available(void)
+{
+ struct stat statbuf;
+
+ if (lstat(SHARE_DIR, &statbuf) != 0 ||
+ !S_ISDIR(statbuf.st_mode))
+ return (B_FALSE);
+
+ if (access(NET_CMD_PATH, F_OK) != 0)
+ return (B_FALSE);
+
+ return (B_TRUE);
+}
+
+/*
+ * Initializes the SMB functionality of libshare.
+ */
+void
+libshare_smb_init(void)
+{
+ smb_fstype = register_fstype("smb", &smb_shareops);
+}
diff --git a/lib/libshare/smb.h b/lib/libshare/smb.h
new file mode 100644
index 000000000000..8ea44677f9ae
--- /dev/null
+++ b/lib/libshare/smb.h
@@ -0,0 +1,49 @@
+/*
+ * 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) 2011 Turbo Fredriksson <turbo@bayour.com>.
+ */
+
+/*
+ * The maximum SMB share name seems to be 254 characters, though good
+ * references are hard to find.
+ */
+
+#define SMB_NAME_MAX 255
+#define SMB_COMMENT_MAX 255
+
+#define SHARE_DIR "/var/lib/samba/usershares"
+#define NET_CMD_PATH "/usr/bin/net"
+#define NET_CMD_ARG_HOST "127.0.0.1"
+
+typedef struct smb_share_s {
+ char name[SMB_NAME_MAX]; /* Share name */
+ char path[PATH_MAX]; /* Share path */
+ char comment[SMB_COMMENT_MAX]; /* Share's comment */
+ boolean_t guest_ok; /* 'y' or 'n' */
+
+ struct smb_share_s *next;
+} smb_share_t;
+
+extern smb_share_t *smb_shares;
+
+void libshare_smb_init(void);
diff --git a/lib/libspl/Makefile.am b/lib/libspl/Makefile.am
new file mode 100644
index 000000000000..f576d69248fa
--- /dev/null
+++ b/lib/libspl/Makefile.am
@@ -0,0 +1,56 @@
+include $(top_srcdir)/config/Rules.am
+
+if TARGET_CPU_I386
+TARGET_CPU_ATOMIC_SOURCE = asm-i386/atomic.S
+else
+if TARGET_CPU_X86_64
+TARGET_CPU_ATOMIC_SOURCE = asm-x86_64/atomic.S
+else
+TARGET_CPU_ATOMIC_SOURCE = asm-generic/atomic.c
+endif
+endif
+
+SUBDIRS = include
+
+AM_CCASFLAGS = \
+ $(CFLAGS)
+
+noinst_LTLIBRARIES = libspl_assert.la libspl.la
+
+libspl_assert_la_SOURCES = \
+ assert.c
+
+USER_C = \
+ list.c \
+ mkdirp.c \
+ page.c \
+ strlcat.c \
+ strlcpy.c \
+ timestamp.c \
+ zone.c \
+ include/sys/list.h \
+ include/sys/list_impl.h
+
+if BUILD_LINUX
+USER_C += \
+ os/linux/getexecname.c \
+ os/linux/gethostid.c \
+ os/linux/getmntany.c
+endif
+
+if BUILD_FREEBSD
+USER_C += \
+ os/freebsd/getexecname.c \
+ os/freebsd/gethostid.c \
+ os/freebsd/getmntany.c \
+ os/freebsd/mnttab.c
+endif
+
+libspl_la_SOURCES = \
+ $(USER_C) \
+ $(TARGET_CPU_ATOMIC_SOURCE)
+
+libspl_la_LIBADD = \
+ libspl_assert.la
+
+libspl_la_LIBADD += $(LIBCLOCK_GETTIME)
diff --git a/lib/libspl/asm-generic/.gitignore b/lib/libspl/asm-generic/.gitignore
new file mode 100644
index 000000000000..2792cf7b4551
--- /dev/null
+++ b/lib/libspl/asm-generic/.gitignore
@@ -0,0 +1 @@
+/atomic.S
diff --git a/lib/libspl/asm-generic/atomic.c b/lib/libspl/asm-generic/atomic.c
new file mode 100644
index 000000000000..35535ea49c79
--- /dev/null
+++ b/lib/libspl/asm-generic/atomic.c
@@ -0,0 +1,450 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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) 2009 by Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#include <atomic.h>
+#include <assert.h>
+#include <pthread.h>
+
+/*
+ * All operations are implemented by serializing them through a global
+ * pthread mutex. This provides a correct generic implementation.
+ * However all supported architectures are encouraged to provide a
+ * native implementation is assembly for performance reasons.
+ */
+pthread_mutex_t atomic_lock = PTHREAD_MUTEX_INITIALIZER;
+
+/*
+ * These are the void returning variants
+ */
+/* BEGIN CSTYLED */
+#define ATOMIC_INC(name, type) \
+ void atomic_inc_##name(volatile type *target) \
+ { \
+ VERIFY3S(pthread_mutex_lock(&atomic_lock), ==, 0); \
+ (*target)++; \
+ VERIFY3S(pthread_mutex_unlock(&atomic_lock), ==, 0); \
+ }
+
+ATOMIC_INC(8, uint8_t)
+ATOMIC_INC(uchar, uchar_t)
+ATOMIC_INC(16, uint16_t)
+ATOMIC_INC(ushort, ushort_t)
+ATOMIC_INC(32, uint32_t)
+ATOMIC_INC(uint, uint_t)
+ATOMIC_INC(ulong, ulong_t)
+ATOMIC_INC(64, uint64_t)
+
+
+#define ATOMIC_DEC(name, type) \
+ void atomic_dec_##name(volatile type *target) \
+ { \
+ VERIFY3S(pthread_mutex_lock(&atomic_lock), ==, 0); \
+ (*target)--; \
+ VERIFY3S(pthread_mutex_unlock(&atomic_lock), ==, 0); \
+ }
+
+ATOMIC_DEC(8, uint8_t)
+ATOMIC_DEC(uchar, uchar_t)
+ATOMIC_DEC(16, uint16_t)
+ATOMIC_DEC(ushort, ushort_t)
+ATOMIC_DEC(32, uint32_t)
+ATOMIC_DEC(uint, uint_t)
+ATOMIC_DEC(ulong, ulong_t)
+ATOMIC_DEC(64, uint64_t)
+
+
+#define ATOMIC_ADD(name, type1, type2) \
+ void atomic_add_##name(volatile type1 *target, type2 bits) \
+ { \
+ VERIFY3S(pthread_mutex_lock(&atomic_lock), ==, 0); \
+ *target += bits; \
+ VERIFY3S(pthread_mutex_unlock(&atomic_lock), ==, 0); \
+ }
+
+ATOMIC_ADD(8, uint8_t, int8_t)
+ATOMIC_ADD(char, uchar_t, signed char)
+ATOMIC_ADD(16, uint16_t, int16_t)
+ATOMIC_ADD(short, ushort_t, short)
+ATOMIC_ADD(32, uint32_t, int32_t)
+ATOMIC_ADD(int, uint_t, int)
+ATOMIC_ADD(long, ulong_t, long)
+ATOMIC_ADD(64, uint64_t, int64_t)
+
+void
+atomic_add_ptr(volatile void *target, ssize_t bits)
+{
+ VERIFY3S(pthread_mutex_lock(&atomic_lock), ==, 0);
+ *(caddr_t *)target += bits;
+ VERIFY3S(pthread_mutex_unlock(&atomic_lock), ==, 0);
+}
+
+
+#define ATOMIC_SUB(name, type1, type2) \
+ void atomic_sub_##name(volatile type1 *target, type2 bits) \
+ { \
+ VERIFY3S(pthread_mutex_lock(&atomic_lock), ==, 0); \
+ *target -= bits; \
+ VERIFY3S(pthread_mutex_unlock(&atomic_lock), ==, 0); \
+ }
+
+ATOMIC_SUB(8, uint8_t, int8_t)
+ATOMIC_SUB(char, uchar_t, signed char)
+ATOMIC_SUB(16, uint16_t, int16_t)
+ATOMIC_SUB(short, ushort_t, short)
+ATOMIC_SUB(32, uint32_t, int32_t)
+ATOMIC_SUB(int, uint_t, int)
+ATOMIC_SUB(long, ulong_t, long)
+ATOMIC_SUB(64, uint64_t, int64_t)
+
+void
+atomic_sub_ptr(volatile void *target, ssize_t bits)
+{
+ VERIFY3S(pthread_mutex_lock(&atomic_lock), ==, 0);
+ *(caddr_t *)target -= bits;
+ VERIFY3S(pthread_mutex_unlock(&atomic_lock), ==, 0);
+}
+
+
+#define ATOMIC_OR(name, type) \
+ void atomic_or_##name(volatile type *target, type bits) \
+ { \
+ VERIFY3S(pthread_mutex_lock(&atomic_lock), ==, 0); \
+ *target |= bits; \
+ VERIFY3S(pthread_mutex_unlock(&atomic_lock), ==, 0); \
+ }
+
+ATOMIC_OR(8, uint8_t)
+ATOMIC_OR(uchar, uchar_t)
+ATOMIC_OR(16, uint16_t)
+ATOMIC_OR(ushort, ushort_t)
+ATOMIC_OR(32, uint32_t)
+ATOMIC_OR(uint, uint_t)
+ATOMIC_OR(ulong, ulong_t)
+ATOMIC_OR(64, uint64_t)
+
+
+#define ATOMIC_AND(name, type) \
+ void atomic_and_##name(volatile type *target, type bits) \
+ { \
+ VERIFY3S(pthread_mutex_lock(&atomic_lock), ==, 0); \
+ *target &= bits; \
+ VERIFY3S(pthread_mutex_unlock(&atomic_lock), ==, 0); \
+ }
+
+ATOMIC_AND(8, uint8_t)
+ATOMIC_AND(uchar, uchar_t)
+ATOMIC_AND(16, uint16_t)
+ATOMIC_AND(ushort, ushort_t)
+ATOMIC_AND(32, uint32_t)
+ATOMIC_AND(uint, uint_t)
+ATOMIC_AND(ulong, ulong_t)
+ATOMIC_AND(64, uint64_t)
+
+
+/*
+ * New value returning variants
+ */
+
+#define ATOMIC_INC_NV(name, type) \
+ type atomic_inc_##name##_nv(volatile type *target) \
+ { \
+ type rc; \
+ VERIFY3S(pthread_mutex_lock(&atomic_lock), ==, 0); \
+ rc = (++(*target)); \
+ VERIFY3S(pthread_mutex_unlock(&atomic_lock), ==, 0); \
+ return (rc); \
+ }
+
+ATOMIC_INC_NV(8, uint8_t)
+ATOMIC_INC_NV(uchar, uchar_t)
+ATOMIC_INC_NV(16, uint16_t)
+ATOMIC_INC_NV(ushort, ushort_t)
+ATOMIC_INC_NV(32, uint32_t)
+ATOMIC_INC_NV(uint, uint_t)
+ATOMIC_INC_NV(ulong, ulong_t)
+ATOMIC_INC_NV(64, uint64_t)
+
+
+#define ATOMIC_DEC_NV(name, type) \
+ type atomic_dec_##name##_nv(volatile type *target) \
+ { \
+ type rc; \
+ VERIFY3S(pthread_mutex_lock(&atomic_lock), ==, 0); \
+ rc = (--(*target)); \
+ VERIFY3S(pthread_mutex_unlock(&atomic_lock), ==, 0); \
+ return (rc); \
+ }
+
+ATOMIC_DEC_NV(8, uint8_t)
+ATOMIC_DEC_NV(uchar, uchar_t)
+ATOMIC_DEC_NV(16, uint16_t)
+ATOMIC_DEC_NV(ushort, ushort_t)
+ATOMIC_DEC_NV(32, uint32_t)
+ATOMIC_DEC_NV(uint, uint_t)
+ATOMIC_DEC_NV(ulong, ulong_t)
+ATOMIC_DEC_NV(64, uint64_t)
+
+
+#define ATOMIC_ADD_NV(name, type1, type2) \
+ type1 atomic_add_##name##_nv(volatile type1 *target, type2 bits)\
+ { \
+ type1 rc; \
+ VERIFY3S(pthread_mutex_lock(&atomic_lock), ==, 0); \
+ rc = (*target += bits); \
+ VERIFY3S(pthread_mutex_unlock(&atomic_lock), ==, 0); \
+ return (rc); \
+ }
+
+ATOMIC_ADD_NV(8, uint8_t, int8_t)
+ATOMIC_ADD_NV(char, uchar_t, signed char)
+ATOMIC_ADD_NV(16, uint16_t, int16_t)
+ATOMIC_ADD_NV(short, ushort_t, short)
+ATOMIC_ADD_NV(32, uint32_t, int32_t)
+ATOMIC_ADD_NV(int, uint_t, int)
+ATOMIC_ADD_NV(long, ulong_t, long)
+ATOMIC_ADD_NV(64, uint64_t, int64_t)
+
+void *
+atomic_add_ptr_nv(volatile void *target, ssize_t bits)
+{
+ void *ptr;
+
+ VERIFY3S(pthread_mutex_lock(&atomic_lock), ==, 0);
+ ptr = (*(caddr_t *)target += bits);
+ VERIFY3S(pthread_mutex_unlock(&atomic_lock), ==, 0);
+
+ return (ptr);
+}
+
+
+#define ATOMIC_SUB_NV(name, type1, type2) \
+ type1 atomic_sub_##name##_nv(volatile type1 *target, type2 bits)\
+ { \
+ type1 rc; \
+ VERIFY3S(pthread_mutex_lock(&atomic_lock), ==, 0); \
+ rc = (*target -= bits); \
+ VERIFY3S(pthread_mutex_unlock(&atomic_lock), ==, 0); \
+ return (rc); \
+ }
+
+ATOMIC_SUB_NV(8, uint8_t, int8_t)
+ATOMIC_SUB_NV(char, uchar_t, signed char)
+ATOMIC_SUB_NV(16, uint16_t, int16_t)
+ATOMIC_SUB_NV(short, ushort_t, short)
+ATOMIC_SUB_NV(32, uint32_t, int32_t)
+ATOMIC_SUB_NV(int, uint_t, int)
+ATOMIC_SUB_NV(long, ulong_t, long)
+ATOMIC_SUB_NV(64, uint64_t, int64_t)
+
+void *
+atomic_sub_ptr_nv(volatile void *target, ssize_t bits)
+{
+ void *ptr;
+
+ VERIFY3S(pthread_mutex_lock(&atomic_lock), ==, 0);
+ ptr = (*(caddr_t *)target -= bits);
+ VERIFY3S(pthread_mutex_unlock(&atomic_lock), ==, 0);
+
+ return (ptr);
+}
+
+
+#define ATOMIC_OR_NV(name, type) \
+ type atomic_or_##name##_nv(volatile type *target, type bits) \
+ { \
+ type rc; \
+ VERIFY3S(pthread_mutex_lock(&atomic_lock), ==, 0); \
+ rc = (*target |= bits); \
+ VERIFY3S(pthread_mutex_unlock(&atomic_lock), ==, 0); \
+ return (rc); \
+ }
+
+ATOMIC_OR_NV(8, uint8_t)
+ATOMIC_OR_NV(uchar, uchar_t)
+ATOMIC_OR_NV(16, uint16_t)
+ATOMIC_OR_NV(ushort, ushort_t)
+ATOMIC_OR_NV(32, uint32_t)
+ATOMIC_OR_NV(uint, uint_t)
+ATOMIC_OR_NV(ulong, ulong_t)
+ATOMIC_OR_NV(64, uint64_t)
+
+
+#define ATOMIC_AND_NV(name, type) \
+ type atomic_and_##name##_nv(volatile type *target, type bits) \
+ { \
+ type rc; \
+ VERIFY3S(pthread_mutex_lock(&atomic_lock), ==, 0); \
+ rc = (*target &= bits); \
+ VERIFY3S(pthread_mutex_unlock(&atomic_lock), ==, 0); \
+ return (rc); \
+ }
+
+ATOMIC_AND_NV(8, uint8_t)
+ATOMIC_AND_NV(uchar, uchar_t)
+ATOMIC_AND_NV(16, uint16_t)
+ATOMIC_AND_NV(ushort, ushort_t)
+ATOMIC_AND_NV(32, uint32_t)
+ATOMIC_AND_NV(uint, uint_t)
+ATOMIC_AND_NV(ulong, ulong_t)
+ATOMIC_AND_NV(64, uint64_t)
+
+
+/*
+ * If *arg1 == arg2, set *arg1 = arg3; return old value
+ */
+
+#define ATOMIC_CAS(name, type) \
+ type atomic_cas_##name(volatile type *target, type arg1, type arg2) \
+ { \
+ type old; \
+ VERIFY3S(pthread_mutex_lock(&atomic_lock), ==, 0); \
+ old = *target; \
+ if (old == arg1) \
+ *target = arg2; \
+ VERIFY3S(pthread_mutex_unlock(&atomic_lock), ==, 0); \
+ return (old); \
+ }
+
+ATOMIC_CAS(8, uint8_t)
+ATOMIC_CAS(uchar, uchar_t)
+ATOMIC_CAS(16, uint16_t)
+ATOMIC_CAS(ushort, ushort_t)
+ATOMIC_CAS(32, uint32_t)
+ATOMIC_CAS(uint, uint_t)
+ATOMIC_CAS(ulong, ulong_t)
+ATOMIC_CAS(64, uint64_t)
+
+void *
+atomic_cas_ptr(volatile void *target, void *arg1, void *arg2)
+{
+ void *old;
+
+ VERIFY3S(pthread_mutex_lock(&atomic_lock), ==, 0);
+ old = *(void **)target;
+ if (old == arg1)
+ *(void **)target = arg2;
+ VERIFY3S(pthread_mutex_unlock(&atomic_lock), ==, 0);
+
+ return (old);
+}
+
+
+/*
+ * Swap target and return old value
+ */
+
+#define ATOMIC_SWAP(name, type) \
+ type atomic_swap_##name(volatile type *target, type bits) \
+ { \
+ type old; \
+ VERIFY3S(pthread_mutex_lock(&atomic_lock), ==, 0); \
+ old = *target; \
+ *target = bits; \
+ VERIFY3S(pthread_mutex_unlock(&atomic_lock), ==, 0); \
+ return (old); \
+ }
+
+ATOMIC_SWAP(8, uint8_t)
+ATOMIC_SWAP(uchar, uchar_t)
+ATOMIC_SWAP(16, uint16_t)
+ATOMIC_SWAP(ushort, ushort_t)
+ATOMIC_SWAP(32, uint32_t)
+ATOMIC_SWAP(uint, uint_t)
+ATOMIC_SWAP(ulong, ulong_t)
+ATOMIC_SWAP(64, uint64_t)
+/* END CSTYLED */
+
+void *
+atomic_swap_ptr(volatile void *target, void *bits)
+{
+ void *old;
+
+ VERIFY3S(pthread_mutex_lock(&atomic_lock), ==, 0);
+ old = *(void **)target;
+ *(void **)target = bits;
+ VERIFY3S(pthread_mutex_unlock(&atomic_lock), ==, 0);
+
+ return (old);
+}
+
+
+int
+atomic_set_long_excl(volatile ulong_t *target, uint_t value)
+{
+ ulong_t bit;
+
+ VERIFY3S(pthread_mutex_lock(&atomic_lock), ==, 0);
+ bit = (1UL << value);
+ if ((*target & bit) != 0) {
+ VERIFY3S(pthread_mutex_unlock(&atomic_lock), ==, 0);
+ return (-1);
+ }
+ *target |= bit;
+ VERIFY3S(pthread_mutex_unlock(&atomic_lock), ==, 0);
+
+ return (0);
+}
+
+int
+atomic_clear_long_excl(volatile ulong_t *target, uint_t value)
+{
+ ulong_t bit;
+
+ VERIFY3S(pthread_mutex_lock(&atomic_lock), ==, 0);
+ bit = (1UL << value);
+ if ((*target & bit) == 0) {
+ VERIFY3S(pthread_mutex_unlock(&atomic_lock), ==, 0);
+ return (-1);
+ }
+ *target &= ~bit;
+ VERIFY3S(pthread_mutex_unlock(&atomic_lock), ==, 0);
+
+ return (0);
+}
+
+void
+membar_enter(void)
+{
+ /* XXX - Implement me */
+}
+
+void
+membar_exit(void)
+{
+ /* XXX - Implement me */
+}
+
+void
+membar_producer(void)
+{
+ /* XXX - Implement me */
+}
+
+void
+membar_consumer(void)
+{
+ /* XXX - Implement me */
+}
diff --git a/lib/libspl/asm-i386/atomic.S b/lib/libspl/asm-i386/atomic.S
new file mode 100644
index 000000000000..7a574b0d1729
--- /dev/null
+++ b/lib/libspl/asm-i386/atomic.S
@@ -0,0 +1,840 @@
+/*
+ * 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 2007 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+ .ident "%Z%%M% %I% %E% SMI"
+
+ .file "%M%"
+
+#define _ASM
+#ifdef __linux__
+#include <ia32/sys/asm_linkage.h>
+#elif __FreeBSD__
+#include <machine/asmacros.h>
+#define SET_SIZE(x)
+#endif
+ ENTRY(atomic_inc_8)
+ ALTENTRY(atomic_inc_uchar)
+ movl 4(%esp), %eax
+ lock
+ incb (%eax)
+ ret
+ SET_SIZE(atomic_inc_uchar)
+ SET_SIZE(atomic_inc_8)
+
+ ENTRY(atomic_inc_16)
+ ALTENTRY(atomic_inc_ushort)
+ movl 4(%esp), %eax
+ lock
+ incw (%eax)
+ ret
+ SET_SIZE(atomic_inc_ushort)
+ SET_SIZE(atomic_inc_16)
+
+ ENTRY(atomic_inc_32)
+ ALTENTRY(atomic_inc_uint)
+ ALTENTRY(atomic_inc_ulong)
+ movl 4(%esp), %eax
+ lock
+ incl (%eax)
+ ret
+ SET_SIZE(atomic_inc_ulong)
+ SET_SIZE(atomic_inc_uint)
+ SET_SIZE(atomic_inc_32)
+
+ ENTRY(atomic_inc_8_nv)
+ ALTENTRY(atomic_inc_uchar_nv)
+ movl 4(%esp), %edx
+ movb (%edx), %al
+1:
+ leal 1(%eax), %ecx
+ lock
+ cmpxchgb %cl, (%edx)
+ jne 1b
+ movzbl %cl, %eax
+ ret
+ SET_SIZE(atomic_inc_uchar_nv)
+ SET_SIZE(atomic_inc_8_nv)
+
+ ENTRY(atomic_inc_16_nv)
+ ALTENTRY(atomic_inc_ushort_nv)
+ movl 4(%esp), %edx
+ movw (%edx), %ax
+1:
+ leal 1(%eax), %ecx
+ lock
+ cmpxchgw %cx, (%edx)
+ jne 1b
+ movzwl %cx, %eax
+ ret
+ SET_SIZE(atomic_inc_ushort_nv)
+ SET_SIZE(atomic_inc_16_nv)
+
+ ENTRY(atomic_inc_32_nv)
+ ALTENTRY(atomic_inc_uint_nv)
+ ALTENTRY(atomic_inc_ulong_nv)
+ movl 4(%esp), %edx
+ movl (%edx), %eax
+1:
+ leal 1(%eax), %ecx
+ lock
+ cmpxchgl %ecx, (%edx)
+ jne 1b
+ movl %ecx, %eax
+ ret
+ SET_SIZE(atomic_inc_ulong_nv)
+ SET_SIZE(atomic_inc_uint_nv)
+ SET_SIZE(atomic_inc_32_nv)
+
+ /*
+ * NOTE: If atomic_inc_64 and atomic_inc_64_nv are ever
+ * separated, you need to also edit the libc i386 platform
+ * specific mapfile and remove the NODYNSORT attribute
+ * from atomic_inc_64_nv.
+ */
+ ENTRY(atomic_inc_64)
+ ALTENTRY(atomic_inc_64_nv)
+ pushl %edi
+ pushl %ebx
+ movl 12(%esp), %edi
+ movl (%edi), %eax
+ movl 4(%edi), %edx
+1:
+ xorl %ebx, %ebx
+ xorl %ecx, %ecx
+ incl %ebx
+ addl %eax, %ebx
+ adcl %edx, %ecx
+ lock
+ cmpxchg8b (%edi)
+ jne 1b
+ movl %ebx, %eax
+ movl %ecx, %edx
+ popl %ebx
+ popl %edi
+ ret
+ SET_SIZE(atomic_inc_64_nv)
+ SET_SIZE(atomic_inc_64)
+
+ ENTRY(atomic_dec_8)
+ ALTENTRY(atomic_dec_uchar)
+ movl 4(%esp), %eax
+ lock
+ decb (%eax)
+ ret
+ SET_SIZE(atomic_dec_uchar)
+ SET_SIZE(atomic_dec_8)
+
+ ENTRY(atomic_dec_16)
+ ALTENTRY(atomic_dec_ushort)
+ movl 4(%esp), %eax
+ lock
+ decw (%eax)
+ ret
+ SET_SIZE(atomic_dec_ushort)
+ SET_SIZE(atomic_dec_16)
+
+ ENTRY(atomic_dec_32)
+ ALTENTRY(atomic_dec_uint)
+ ALTENTRY(atomic_dec_ulong)
+ movl 4(%esp), %eax
+ lock
+ decl (%eax)
+ ret
+ SET_SIZE(atomic_dec_ulong)
+ SET_SIZE(atomic_dec_uint)
+ SET_SIZE(atomic_dec_32)
+
+ ENTRY(atomic_dec_8_nv)
+ ALTENTRY(atomic_dec_uchar_nv)
+ movl 4(%esp), %edx
+ movb (%edx), %al
+1:
+ leal -1(%eax), %ecx
+ lock
+ cmpxchgb %cl, (%edx)
+ jne 1b
+ movzbl %cl, %eax
+ ret
+ SET_SIZE(atomic_dec_uchar_nv)
+ SET_SIZE(atomic_dec_8_nv)
+
+ ENTRY(atomic_dec_16_nv)
+ ALTENTRY(atomic_dec_ushort_nv)
+ movl 4(%esp), %edx
+ movw (%edx), %ax
+1:
+ leal -1(%eax), %ecx
+ lock
+ cmpxchgw %cx, (%edx)
+ jne 1b
+ movzwl %cx, %eax
+ ret
+ SET_SIZE(atomic_dec_ushort_nv)
+ SET_SIZE(atomic_dec_16_nv)
+
+ ENTRY(atomic_dec_32_nv)
+ ALTENTRY(atomic_dec_uint_nv)
+ ALTENTRY(atomic_dec_ulong_nv)
+ movl 4(%esp), %edx
+ movl (%edx), %eax
+1:
+ leal -1(%eax), %ecx
+ lock
+ cmpxchgl %ecx, (%edx)
+ jne 1b
+ movl %ecx, %eax
+ ret
+ SET_SIZE(atomic_dec_ulong_nv)
+ SET_SIZE(atomic_dec_uint_nv)
+ SET_SIZE(atomic_dec_32_nv)
+
+ /*
+ * NOTE: If atomic_dec_64 and atomic_dec_64_nv are ever
+ * separated, it is important to edit the libc i386 platform
+ * specific mapfile and remove the NODYNSORT attribute
+ * from atomic_dec_64_nv.
+ */
+ ENTRY(atomic_dec_64)
+ ALTENTRY(atomic_dec_64_nv)
+ pushl %edi
+ pushl %ebx
+ movl 12(%esp), %edi
+ movl (%edi), %eax
+ movl 4(%edi), %edx
+1:
+ xorl %ebx, %ebx
+ xorl %ecx, %ecx
+ not %ecx
+ not %ebx
+ addl %eax, %ebx
+ adcl %edx, %ecx
+ lock
+ cmpxchg8b (%edi)
+ jne 1b
+ movl %ebx, %eax
+ movl %ecx, %edx
+ popl %ebx
+ popl %edi
+ ret
+ SET_SIZE(atomic_dec_64_nv)
+ SET_SIZE(atomic_dec_64)
+
+ ENTRY(atomic_add_8)
+ ALTENTRY(atomic_add_char)
+ movl 4(%esp), %eax
+ movl 8(%esp), %ecx
+ lock
+ addb %cl, (%eax)
+ ret
+ SET_SIZE(atomic_add_char)
+ SET_SIZE(atomic_add_8)
+
+ ENTRY(atomic_add_16)
+ ALTENTRY(atomic_add_short)
+ movl 4(%esp), %eax
+ movl 8(%esp), %ecx
+ lock
+ addw %cx, (%eax)
+ ret
+ SET_SIZE(atomic_add_short)
+ SET_SIZE(atomic_add_16)
+
+ ENTRY(atomic_add_32)
+ ALTENTRY(atomic_add_int)
+ ALTENTRY(atomic_add_ptr)
+ ALTENTRY(atomic_add_long)
+ movl 4(%esp), %eax
+ movl 8(%esp), %ecx
+ lock
+ addl %ecx, (%eax)
+ ret
+ SET_SIZE(atomic_add_long)
+ SET_SIZE(atomic_add_ptr)
+ SET_SIZE(atomic_add_int)
+ SET_SIZE(atomic_add_32)
+
+ ENTRY(atomic_sub_8)
+ ALTENTRY(atomic_sub_char)
+ movl 4(%esp), %eax
+ movl 8(%esp), %ecx
+ lock
+ subb %cl, (%eax)
+ ret
+ SET_SIZE(atomic_sub_char)
+ SET_SIZE(atomic_sub_8)
+
+ ENTRY(atomic_sub_16)
+ ALTENTRY(atomic_sub_short)
+ movl 4(%esp), %eax
+ movl 8(%esp), %ecx
+ lock
+ subw %cx, (%eax)
+ ret
+ SET_SIZE(atomic_sub_short)
+ SET_SIZE(atomic_sub_16)
+
+ ENTRY(atomic_sub_32)
+ ALTENTRY(atomic_sub_int)
+ ALTENTRY(atomic_sub_ptr)
+ ALTENTRY(atomic_sub_long)
+ movl 4(%esp), %eax
+ movl 8(%esp), %ecx
+ lock
+ subl %ecx, (%eax)
+ ret
+ SET_SIZE(atomic_sub_long)
+ SET_SIZE(atomic_sub_ptr)
+ SET_SIZE(atomic_sub_int)
+ SET_SIZE(atomic_sub_32)
+
+ ENTRY(atomic_or_8)
+ ALTENTRY(atomic_or_uchar)
+ movl 4(%esp), %eax
+ movb 8(%esp), %cl
+ lock
+ orb %cl, (%eax)
+ ret
+ SET_SIZE(atomic_or_uchar)
+ SET_SIZE(atomic_or_8)
+
+ ENTRY(atomic_or_16)
+ ALTENTRY(atomic_or_ushort)
+ movl 4(%esp), %eax
+ movw 8(%esp), %cx
+ lock
+ orw %cx, (%eax)
+ ret
+ SET_SIZE(atomic_or_ushort)
+ SET_SIZE(atomic_or_16)
+
+ ENTRY(atomic_or_32)
+ ALTENTRY(atomic_or_uint)
+ ALTENTRY(atomic_or_ulong)
+ movl 4(%esp), %eax
+ movl 8(%esp), %ecx
+ lock
+ orl %ecx, (%eax)
+ ret
+ SET_SIZE(atomic_or_ulong)
+ SET_SIZE(atomic_or_uint)
+ SET_SIZE(atomic_or_32)
+
+ ENTRY(atomic_and_8)
+ ALTENTRY(atomic_and_uchar)
+ movl 4(%esp), %eax
+ movb 8(%esp), %cl
+ lock
+ andb %cl, (%eax)
+ ret
+ SET_SIZE(atomic_and_uchar)
+ SET_SIZE(atomic_and_8)
+
+ ENTRY(atomic_and_16)
+ ALTENTRY(atomic_and_ushort)
+ movl 4(%esp), %eax
+ movw 8(%esp), %cx
+ lock
+ andw %cx, (%eax)
+ ret
+ SET_SIZE(atomic_and_ushort)
+ SET_SIZE(atomic_and_16)
+
+ ENTRY(atomic_and_32)
+ ALTENTRY(atomic_and_uint)
+ ALTENTRY(atomic_and_ulong)
+ movl 4(%esp), %eax
+ movl 8(%esp), %ecx
+ lock
+ andl %ecx, (%eax)
+ ret
+ SET_SIZE(atomic_and_ulong)
+ SET_SIZE(atomic_and_uint)
+ SET_SIZE(atomic_and_32)
+
+ ENTRY(atomic_add_8_nv)
+ ALTENTRY(atomic_add_char_nv)
+ movl 4(%esp), %edx
+ movb (%edx), %al
+1:
+ movl 8(%esp), %ecx
+ addb %al, %cl
+ lock
+ cmpxchgb %cl, (%edx)
+ jne 1b
+ movzbl %cl, %eax
+ ret
+ SET_SIZE(atomic_add_char_nv)
+ SET_SIZE(atomic_add_8_nv)
+
+ ENTRY(atomic_add_16_nv)
+ ALTENTRY(atomic_add_short_nv)
+ movl 4(%esp), %edx
+ movw (%edx), %ax
+1:
+ movl 8(%esp), %ecx
+ addw %ax, %cx
+ lock
+ cmpxchgw %cx, (%edx)
+ jne 1b
+ movzwl %cx, %eax
+ ret
+ SET_SIZE(atomic_add_short_nv)
+ SET_SIZE(atomic_add_16_nv)
+
+ ENTRY(atomic_add_32_nv)
+ ALTENTRY(atomic_add_int_nv)
+ ALTENTRY(atomic_add_ptr_nv)
+ ALTENTRY(atomic_add_long_nv)
+ movl 4(%esp), %edx
+ movl (%edx), %eax
+1:
+ movl 8(%esp), %ecx
+ addl %eax, %ecx
+ lock
+ cmpxchgl %ecx, (%edx)
+ jne 1b
+ movl %ecx, %eax
+ ret
+ SET_SIZE(atomic_add_long_nv)
+ SET_SIZE(atomic_add_ptr_nv)
+ SET_SIZE(atomic_add_int_nv)
+ SET_SIZE(atomic_add_32_nv)
+
+ ENTRY(atomic_sub_8_nv)
+ ALTENTRY(atomic_sub_char_nv)
+ movl 4(%esp), %edx
+ movb (%edx), %al
+1:
+ movl 8(%esp), %ecx
+ subb %al, %cl
+ lock
+ cmpxchgb %cl, (%edx)
+ jne 1b
+ movzbl %cl, %eax
+ ret
+ SET_SIZE(atomic_sub_char_nv)
+ SET_SIZE(atomic_sub_8_nv)
+
+ ENTRY(atomic_sub_16_nv)
+ ALTENTRY(atomic_sub_short_nv)
+ movl 4(%esp), %edx
+ movw (%edx), %ax
+1:
+ movl 8(%esp), %ecx
+ subw %ax, %cx
+ lock
+ cmpxchgw %cx, (%edx)
+ jne 1b
+ movzwl %cx, %eax
+ ret
+ SET_SIZE(atomic_sub_short_nv)
+ SET_SIZE(atomic_sub_16_nv)
+
+ ENTRY(atomic_sub_32_nv)
+ ALTENTRY(atomic_sub_int_nv)
+ ALTENTRY(atomic_sub_ptr_nv)
+ ALTENTRY(atomic_sub_long_nv)
+ movl 4(%esp), %edx
+ movl (%edx), %eax
+1:
+ movl 8(%esp), %ecx
+ subl %eax, %ecx
+ lock
+ cmpxchgl %ecx, (%edx)
+ jne 1b
+ movl %ecx, %eax
+ ret
+ SET_SIZE(atomic_sub_long_nv)
+ SET_SIZE(atomic_sub_ptr_nv)
+ SET_SIZE(atomic_sub_int_nv)
+ SET_SIZE(atomic_sub_32_nv)
+
+ /*
+ * NOTE: If atomic_add_64 and atomic_add_64_nv are ever
+ * separated, it is important to edit the libc i386 platform
+ * specific mapfile and remove the NODYNSORT attribute
+ * from atomic_add_64_nv.
+ */
+ ENTRY(atomic_add_64)
+ ALTENTRY(atomic_add_64_nv)
+ pushl %edi
+ pushl %ebx
+ movl 12(%esp), %edi
+ movl (%edi), %eax
+ movl 4(%edi), %edx
+1:
+ movl 16(%esp), %ebx
+ movl 20(%esp), %ecx
+ addl %eax, %ebx
+ adcl %edx, %ecx
+ lock
+ cmpxchg8b (%edi)
+ jne 1b
+ movl %ebx, %eax
+ movl %ecx, %edx
+ popl %ebx
+ popl %edi
+ ret
+ SET_SIZE(atomic_add_64_nv)
+ SET_SIZE(atomic_add_64)
+
+ ENTRY(atomic_sub_64)
+ ALTENTRY(atomic_sub_64_nv)
+ pushl %edi
+ pushl %ebx
+ movl 12(%esp), %edi
+ movl (%edi), %eax
+ movl 4(%edi), %edx
+1:
+ movl 16(%esp), %ebx
+ movl 20(%esp), %ecx
+ subl %eax, %ebx
+ sbbl %edx, %ecx
+ lock
+ cmpxchg8b (%edi)
+ jne 1b
+ movl %ebx, %eax
+ movl %ecx, %edx
+ popl %ebx
+ popl %edi
+ ret
+ SET_SIZE(atomic_sub_64_nv)
+ SET_SIZE(atomic_sub_64)
+
+ ENTRY(atomic_or_8_nv)
+ ALTENTRY(atomic_or_uchar_nv)
+ movl 4(%esp), %edx
+ movb (%edx), %al
+1:
+ movl 8(%esp), %ecx
+ orb %al, %cl
+ lock
+ cmpxchgb %cl, (%edx)
+ jne 1b
+ movzbl %cl, %eax
+ ret
+ SET_SIZE(atomic_or_uchar_nv)
+ SET_SIZE(atomic_or_8_nv)
+
+ ENTRY(atomic_or_16_nv)
+ ALTENTRY(atomic_or_ushort_nv)
+ movl 4(%esp), %edx
+ movw (%edx), %ax
+1:
+ movl 8(%esp), %ecx
+ orw %ax, %cx
+ lock
+ cmpxchgw %cx, (%edx)
+ jne 1b
+ movzwl %cx, %eax
+ ret
+ SET_SIZE(atomic_or_ushort_nv)
+ SET_SIZE(atomic_or_16_nv)
+
+ ENTRY(atomic_or_32_nv)
+ ALTENTRY(atomic_or_uint_nv)
+ ALTENTRY(atomic_or_ulong_nv)
+ movl 4(%esp), %edx
+ movl (%edx), %eax
+1:
+ movl 8(%esp), %ecx
+ orl %eax, %ecx
+ lock
+ cmpxchgl %ecx, (%edx)
+ jne 1b
+ movl %ecx, %eax
+ ret
+ SET_SIZE(atomic_or_ulong_nv)
+ SET_SIZE(atomic_or_uint_nv)
+ SET_SIZE(atomic_or_32_nv)
+
+ /*
+ * NOTE: If atomic_or_64 and atomic_or_64_nv are ever
+ * separated, it is important to edit the libc i386 platform
+ * specific mapfile and remove the NODYNSORT attribute
+ * from atomic_or_64_nv.
+ */
+ ENTRY(atomic_or_64)
+ ALTENTRY(atomic_or_64_nv)
+ pushl %edi
+ pushl %ebx
+ movl 12(%esp), %edi
+ movl (%edi), %eax
+ movl 4(%edi), %edx
+1:
+ movl 16(%esp), %ebx
+ movl 20(%esp), %ecx
+ orl %eax, %ebx
+ orl %edx, %ecx
+ lock
+ cmpxchg8b (%edi)
+ jne 1b
+ movl %ebx, %eax
+ movl %ecx, %edx
+ popl %ebx
+ popl %edi
+ ret
+ SET_SIZE(atomic_or_64_nv)
+ SET_SIZE(atomic_or_64)
+
+ ENTRY(atomic_and_8_nv)
+ ALTENTRY(atomic_and_uchar_nv)
+ movl 4(%esp), %edx
+ movb (%edx), %al
+1:
+ movl 8(%esp), %ecx
+ andb %al, %cl
+ lock
+ cmpxchgb %cl, (%edx)
+ jne 1b
+ movzbl %cl, %eax
+ ret
+ SET_SIZE(atomic_and_uchar_nv)
+ SET_SIZE(atomic_and_8_nv)
+
+ ENTRY(atomic_and_16_nv)
+ ALTENTRY(atomic_and_ushort_nv)
+ movl 4(%esp), %edx
+ movw (%edx), %ax
+1:
+ movl 8(%esp), %ecx
+ andw %ax, %cx
+ lock
+ cmpxchgw %cx, (%edx)
+ jne 1b
+ movzwl %cx, %eax
+ ret
+ SET_SIZE(atomic_and_ushort_nv)
+ SET_SIZE(atomic_and_16_nv)
+
+ ENTRY(atomic_and_32_nv)
+ ALTENTRY(atomic_and_uint_nv)
+ ALTENTRY(atomic_and_ulong_nv)
+ movl 4(%esp), %edx
+ movl (%edx), %eax
+1:
+ movl 8(%esp), %ecx
+ andl %eax, %ecx
+ lock
+ cmpxchgl %ecx, (%edx)
+ jne 1b
+ movl %ecx, %eax
+ ret
+ SET_SIZE(atomic_and_ulong_nv)
+ SET_SIZE(atomic_and_uint_nv)
+ SET_SIZE(atomic_and_32_nv)
+
+ /*
+ * NOTE: If atomic_and_64 and atomic_and_64_nv are ever
+ * separated, it is important to edit the libc i386 platform
+ * specific mapfile and remove the NODYNSORT attribute
+ * from atomic_and_64_nv.
+ */
+ ENTRY(atomic_and_64)
+ ALTENTRY(atomic_and_64_nv)
+ pushl %edi
+ pushl %ebx
+ movl 12(%esp), %edi
+ movl (%edi), %eax
+ movl 4(%edi), %edx
+1:
+ movl 16(%esp), %ebx
+ movl 20(%esp), %ecx
+ andl %eax, %ebx
+ andl %edx, %ecx
+ lock
+ cmpxchg8b (%edi)
+ jne 1b
+ movl %ebx, %eax
+ movl %ecx, %edx
+ popl %ebx
+ popl %edi
+ ret
+ SET_SIZE(atomic_and_64_nv)
+ SET_SIZE(atomic_and_64)
+
+ ENTRY(atomic_cas_8)
+ ALTENTRY(atomic_cas_uchar)
+ movl 4(%esp), %edx
+ movzbl 8(%esp), %eax
+ movb 12(%esp), %cl
+ lock
+ cmpxchgb %cl, (%edx)
+ ret
+ SET_SIZE(atomic_cas_uchar)
+ SET_SIZE(atomic_cas_8)
+
+ ENTRY(atomic_cas_16)
+ ALTENTRY(atomic_cas_ushort)
+ movl 4(%esp), %edx
+ movzwl 8(%esp), %eax
+ movw 12(%esp), %cx
+ lock
+ cmpxchgw %cx, (%edx)
+ ret
+ SET_SIZE(atomic_cas_ushort)
+ SET_SIZE(atomic_cas_16)
+
+ ENTRY(atomic_cas_32)
+ ALTENTRY(atomic_cas_uint)
+ ALTENTRY(atomic_cas_ulong)
+ ALTENTRY(atomic_cas_ptr)
+ movl 4(%esp), %edx
+ movl 8(%esp), %eax
+ movl 12(%esp), %ecx
+ lock
+ cmpxchgl %ecx, (%edx)
+ ret
+ SET_SIZE(atomic_cas_ptr)
+ SET_SIZE(atomic_cas_ulong)
+ SET_SIZE(atomic_cas_uint)
+ SET_SIZE(atomic_cas_32)
+
+ ENTRY(atomic_cas_64)
+ pushl %ebx
+ pushl %esi
+ movl 12(%esp), %esi
+ movl 16(%esp), %eax
+ movl 20(%esp), %edx
+ movl 24(%esp), %ebx
+ movl 28(%esp), %ecx
+ lock
+ cmpxchg8b (%esi)
+ popl %esi
+ popl %ebx
+ ret
+ SET_SIZE(atomic_cas_64)
+
+ ENTRY(atomic_swap_8)
+ ALTENTRY(atomic_swap_uchar)
+ movl 4(%esp), %edx
+ movzbl 8(%esp), %eax
+ lock
+ xchgb %al, (%edx)
+ ret
+ SET_SIZE(atomic_swap_uchar)
+ SET_SIZE(atomic_swap_8)
+
+ ENTRY(atomic_swap_16)
+ ALTENTRY(atomic_swap_ushort)
+ movl 4(%esp), %edx
+ movzwl 8(%esp), %eax
+ lock
+ xchgw %ax, (%edx)
+ ret
+ SET_SIZE(atomic_swap_ushort)
+ SET_SIZE(atomic_swap_16)
+
+ ENTRY(atomic_swap_32)
+ ALTENTRY(atomic_swap_uint)
+ ALTENTRY(atomic_swap_ptr)
+ ALTENTRY(atomic_swap_ulong)
+ movl 4(%esp), %edx
+ movl 8(%esp), %eax
+ lock
+ xchgl %eax, (%edx)
+ ret
+ SET_SIZE(atomic_swap_ulong)
+ SET_SIZE(atomic_swap_ptr)
+ SET_SIZE(atomic_swap_uint)
+ SET_SIZE(atomic_swap_32)
+
+ ENTRY(atomic_swap_64)
+ pushl %esi
+ pushl %ebx
+ movl 12(%esp), %esi
+ movl 16(%esp), %ebx
+ movl 20(%esp), %ecx
+ movl (%esi), %eax
+ movl 4(%esi), %edx
+1:
+ lock
+ cmpxchg8b (%esi)
+ jne 1b
+ popl %ebx
+ popl %esi
+ ret
+ SET_SIZE(atomic_swap_64)
+
+ ENTRY(atomic_set_long_excl)
+ movl 4(%esp), %edx
+ movl 8(%esp), %ecx
+ xorl %eax, %eax
+ lock
+ btsl %ecx, (%edx)
+ jnc 1f
+ decl %eax
+1:
+ ret
+ SET_SIZE(atomic_set_long_excl)
+
+ ENTRY(atomic_clear_long_excl)
+ movl 4(%esp), %edx
+ movl 8(%esp), %ecx
+ xorl %eax, %eax
+ lock
+ btrl %ecx, (%edx)
+ jc 1f
+ decl %eax
+1:
+ ret
+ SET_SIZE(atomic_clear_long_excl)
+
+ /*
+ * NOTE: membar_enter, membar_exit, membar_producer, and
+ * membar_consumer are all identical routines. We define them
+ * separately, instead of using ALTENTRY definitions to alias them
+ * together, so that DTrace and debuggers will see a unique address
+ * for them, allowing more accurate tracing.
+ */
+
+
+ ENTRY(membar_enter)
+ lock
+ xorl $0, (%esp)
+ ret
+ SET_SIZE(membar_enter)
+
+ ENTRY(membar_exit)
+ lock
+ xorl $0, (%esp)
+ ret
+ SET_SIZE(membar_exit)
+
+ ENTRY(membar_producer)
+ lock
+ xorl $0, (%esp)
+ ret
+ SET_SIZE(membar_producer)
+
+ ENTRY(membar_consumer)
+ lock
+ xorl $0, (%esp)
+ ret
+ SET_SIZE(membar_consumer)
+
+#ifdef __ELF__
+.section .note.GNU-stack,"",%progbits
+#endif
diff --git a/lib/libspl/asm-x86_64/atomic.S b/lib/libspl/asm-x86_64/atomic.S
new file mode 100644
index 000000000000..50edfe878cc9
--- /dev/null
+++ b/lib/libspl/asm-x86_64/atomic.S
@@ -0,0 +1,691 @@
+/*
+ * 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 2007 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+ .ident "%Z%%M% %I% %E% SMI"
+
+ .file "%M%"
+
+#define _ASM
+#ifdef __linux__
+#include <ia32/sys/asm_linkage.h>
+#elif __FreeBSD__
+#include <machine/asmacros.h>
+#define SET_SIZE(x)
+#endif
+ ENTRY(atomic_inc_8)
+ ALTENTRY(atomic_inc_uchar)
+ lock
+ incb (%rdi)
+ ret
+ SET_SIZE(atomic_inc_uchar)
+ SET_SIZE(atomic_inc_8)
+
+ ENTRY(atomic_inc_16)
+ ALTENTRY(atomic_inc_ushort)
+ lock
+ incw (%rdi)
+ ret
+ SET_SIZE(atomic_inc_ushort)
+ SET_SIZE(atomic_inc_16)
+
+ ENTRY(atomic_inc_32)
+ ALTENTRY(atomic_inc_uint)
+ lock
+ incl (%rdi)
+ ret
+ SET_SIZE(atomic_inc_uint)
+ SET_SIZE(atomic_inc_32)
+
+ ENTRY(atomic_inc_64)
+ ALTENTRY(atomic_inc_ulong)
+ lock
+ incq (%rdi)
+ ret
+ SET_SIZE(atomic_inc_ulong)
+ SET_SIZE(atomic_inc_64)
+
+ ENTRY(atomic_inc_8_nv)
+ ALTENTRY(atomic_inc_uchar_nv)
+ movb (%rdi), %al
+1:
+ leaq 1(%rax), %rcx
+ lock
+ cmpxchgb %cl, (%rdi)
+ jne 1b
+ movzbl %cl, %eax
+ ret
+ SET_SIZE(atomic_inc_uchar_nv)
+ SET_SIZE(atomic_inc_8_nv)
+
+ ENTRY(atomic_inc_16_nv)
+ ALTENTRY(atomic_inc_ushort_nv)
+ movw (%rdi), %ax
+1:
+ leaq 1(%rax), %rcx
+ lock
+ cmpxchgw %cx, (%rdi)
+ jne 1b
+ movzwl %cx, %eax
+ ret
+ SET_SIZE(atomic_inc_ushort_nv)
+ SET_SIZE(atomic_inc_16_nv)
+
+ ENTRY(atomic_inc_32_nv)
+ ALTENTRY(atomic_inc_uint_nv)
+ movl (%rdi), %eax
+1:
+ leaq 1(%rax), %rcx
+ lock
+ cmpxchgl %ecx, (%rdi)
+ jne 1b
+ movl %ecx, %eax
+ ret
+ SET_SIZE(atomic_inc_uint_nv)
+ SET_SIZE(atomic_inc_32_nv)
+
+ ENTRY(atomic_inc_64_nv)
+ ALTENTRY(atomic_inc_ulong_nv)
+ movq (%rdi), %rax
+1:
+ leaq 1(%rax), %rcx
+ lock
+ cmpxchgq %rcx, (%rdi)
+ jne 1b
+ movq %rcx, %rax
+ ret
+ SET_SIZE(atomic_inc_ulong_nv)
+ SET_SIZE(atomic_inc_64_nv)
+
+ ENTRY(atomic_dec_8)
+ ALTENTRY(atomic_dec_uchar)
+ lock
+ decb (%rdi)
+ ret
+ SET_SIZE(atomic_dec_uchar)
+ SET_SIZE(atomic_dec_8)
+
+ ENTRY(atomic_dec_16)
+ ALTENTRY(atomic_dec_ushort)
+ lock
+ decw (%rdi)
+ ret
+ SET_SIZE(atomic_dec_ushort)
+ SET_SIZE(atomic_dec_16)
+
+ ENTRY(atomic_dec_32)
+ ALTENTRY(atomic_dec_uint)
+ lock
+ decl (%rdi)
+ ret
+ SET_SIZE(atomic_dec_uint)
+ SET_SIZE(atomic_dec_32)
+
+ ENTRY(atomic_dec_64)
+ ALTENTRY(atomic_dec_ulong)
+ lock
+ decq (%rdi)
+ ret
+ SET_SIZE(atomic_dec_ulong)
+ SET_SIZE(atomic_dec_64)
+
+ ENTRY(atomic_dec_8_nv)
+ ALTENTRY(atomic_dec_uchar_nv)
+ movb (%rdi), %al
+1:
+ leaq -1(%rax), %rcx
+ lock
+ cmpxchgb %cl, (%rdi)
+ jne 1b
+ movzbl %cl, %eax
+ ret
+ SET_SIZE(atomic_dec_uchar_nv)
+ SET_SIZE(atomic_dec_8_nv)
+
+ ENTRY(atomic_dec_16_nv)
+ ALTENTRY(atomic_dec_ushort_nv)
+ movw (%rdi), %ax
+1:
+ leaq -1(%rax), %rcx
+ lock
+ cmpxchgw %cx, (%rdi)
+ jne 1b
+ movzwl %cx, %eax
+ ret
+ SET_SIZE(atomic_dec_ushort_nv)
+ SET_SIZE(atomic_dec_16_nv)
+
+ ENTRY(atomic_dec_32_nv)
+ ALTENTRY(atomic_dec_uint_nv)
+ movl (%rdi), %eax
+1:
+ leaq -1(%rax), %rcx
+ lock
+ cmpxchgl %ecx, (%rdi)
+ jne 1b
+ movl %ecx, %eax
+ ret
+ SET_SIZE(atomic_dec_uint_nv)
+ SET_SIZE(atomic_dec_32_nv)
+
+ ENTRY(atomic_dec_64_nv)
+ ALTENTRY(atomic_dec_ulong_nv)
+ movq (%rdi), %rax
+1:
+ leaq -1(%rax), %rcx
+ lock
+ cmpxchgq %rcx, (%rdi)
+ jne 1b
+ movq %rcx, %rax
+ ret
+ SET_SIZE(atomic_dec_ulong_nv)
+ SET_SIZE(atomic_dec_64_nv)
+
+ ENTRY(atomic_add_8)
+ ALTENTRY(atomic_add_char)
+ lock
+ addb %sil, (%rdi)
+ ret
+ SET_SIZE(atomic_add_char)
+ SET_SIZE(atomic_add_8)
+
+ ENTRY(atomic_add_16)
+ ALTENTRY(atomic_add_short)
+ lock
+ addw %si, (%rdi)
+ ret
+ SET_SIZE(atomic_add_short)
+ SET_SIZE(atomic_add_16)
+
+ ENTRY(atomic_add_32)
+ ALTENTRY(atomic_add_int)
+ lock
+ addl %esi, (%rdi)
+ ret
+ SET_SIZE(atomic_add_int)
+ SET_SIZE(atomic_add_32)
+
+ ENTRY(atomic_add_64)
+ ALTENTRY(atomic_add_ptr)
+ ALTENTRY(atomic_add_long)
+ lock
+ addq %rsi, (%rdi)
+ ret
+ SET_SIZE(atomic_add_long)
+ SET_SIZE(atomic_add_ptr)
+ SET_SIZE(atomic_add_64)
+
+ ENTRY(atomic_sub_8)
+ ALTENTRY(atomic_sub_char)
+ lock
+ subb %sil, (%rdi)
+ ret
+ SET_SIZE(atomic_sub_char)
+ SET_SIZE(atomic_sub_8)
+
+ ENTRY(atomic_sub_16)
+ ALTENTRY(atomic_sub_short)
+ lock
+ subw %si, (%rdi)
+ ret
+ SET_SIZE(atomic_sub_short)
+ SET_SIZE(atomic_sub_16)
+
+ ENTRY(atomic_sub_32)
+ ALTENTRY(atomic_sub_int)
+ lock
+ subl %esi, (%rdi)
+ ret
+ SET_SIZE(atomic_sub_int)
+ SET_SIZE(atomic_sub_32)
+
+ ENTRY(atomic_sub_64)
+ ALTENTRY(atomic_sub_ptr)
+ ALTENTRY(atomic_sub_long)
+ lock
+ subq %rsi, (%rdi)
+ ret
+ SET_SIZE(atomic_sub_long)
+ SET_SIZE(atomic_sub_ptr)
+ SET_SIZE(atomic_sub_64)
+
+ ENTRY(atomic_or_8)
+ ALTENTRY(atomic_or_uchar)
+ lock
+ orb %sil, (%rdi)
+ ret
+ SET_SIZE(atomic_or_uchar)
+ SET_SIZE(atomic_or_8)
+
+ ENTRY(atomic_or_16)
+ ALTENTRY(atomic_or_ushort)
+ lock
+ orw %si, (%rdi)
+ ret
+ SET_SIZE(atomic_or_ushort)
+ SET_SIZE(atomic_or_16)
+
+ ENTRY(atomic_or_32)
+ ALTENTRY(atomic_or_uint)
+ lock
+ orl %esi, (%rdi)
+ ret
+ SET_SIZE(atomic_or_uint)
+ SET_SIZE(atomic_or_32)
+
+ ENTRY(atomic_or_64)
+ ALTENTRY(atomic_or_ulong)
+ lock
+ orq %rsi, (%rdi)
+ ret
+ SET_SIZE(atomic_or_ulong)
+ SET_SIZE(atomic_or_64)
+
+ ENTRY(atomic_and_8)
+ ALTENTRY(atomic_and_uchar)
+ lock
+ andb %sil, (%rdi)
+ ret
+ SET_SIZE(atomic_and_uchar)
+ SET_SIZE(atomic_and_8)
+
+ ENTRY(atomic_and_16)
+ ALTENTRY(atomic_and_ushort)
+ lock
+ andw %si, (%rdi)
+ ret
+ SET_SIZE(atomic_and_ushort)
+ SET_SIZE(atomic_and_16)
+
+ ENTRY(atomic_and_32)
+ ALTENTRY(atomic_and_uint)
+ lock
+ andl %esi, (%rdi)
+ ret
+ SET_SIZE(atomic_and_uint)
+ SET_SIZE(atomic_and_32)
+
+ ENTRY(atomic_and_64)
+ ALTENTRY(atomic_and_ulong)
+ lock
+ andq %rsi, (%rdi)
+ ret
+ SET_SIZE(atomic_and_ulong)
+ SET_SIZE(atomic_and_64)
+
+ ENTRY(atomic_add_8_nv)
+ ALTENTRY(atomic_add_char_nv)
+ movb (%rdi), %al
+1:
+ movb %sil, %cl
+ addb %al, %cl
+ lock
+ cmpxchgb %cl, (%rdi)
+ jne 1b
+ movzbl %cl, %eax
+ ret
+ SET_SIZE(atomic_add_char_nv)
+ SET_SIZE(atomic_add_8_nv)
+
+ ENTRY(atomic_add_16_nv)
+ ALTENTRY(atomic_add_short_nv)
+ movw (%rdi), %ax
+1:
+ movw %si, %cx
+ addw %ax, %cx
+ lock
+ cmpxchgw %cx, (%rdi)
+ jne 1b
+ movzwl %cx, %eax
+ ret
+ SET_SIZE(atomic_add_short_nv)
+ SET_SIZE(atomic_add_16_nv)
+
+ ENTRY(atomic_add_32_nv)
+ ALTENTRY(atomic_add_int_nv)
+ movl (%rdi), %eax
+1:
+ movl %esi, %ecx
+ addl %eax, %ecx
+ lock
+ cmpxchgl %ecx, (%rdi)
+ jne 1b
+ movl %ecx, %eax
+ ret
+ SET_SIZE(atomic_add_int_nv)
+ SET_SIZE(atomic_add_32_nv)
+
+ ENTRY(atomic_add_64_nv)
+ ALTENTRY(atomic_add_ptr_nv)
+ ALTENTRY(atomic_add_long_nv)
+ movq (%rdi), %rax
+1:
+ movq %rsi, %rcx
+ addq %rax, %rcx
+ lock
+ cmpxchgq %rcx, (%rdi)
+ jne 1b
+ movq %rcx, %rax
+ ret
+ SET_SIZE(atomic_add_long_nv)
+ SET_SIZE(atomic_add_ptr_nv)
+ SET_SIZE(atomic_add_64_nv)
+
+ ENTRY(atomic_sub_8_nv)
+ ALTENTRY(atomic_sub_char_nv)
+ movb (%rdi), %al
+1:
+ movb %sil, %cl
+ subb %al, %cl
+ lock
+ cmpxchgb %cl, (%rdi)
+ jne 1b
+ movzbl %cl, %eax
+ ret
+ SET_SIZE(atomic_sub_char_nv)
+ SET_SIZE(atomic_sub_8_nv)
+
+ ENTRY(atomic_sub_16_nv)
+ ALTENTRY(atomic_sub_short_nv)
+ movw (%rdi), %ax
+1:
+ movw %si, %cx
+ subw %ax, %cx
+ lock
+ cmpxchgw %cx, (%rdi)
+ jne 1b
+ movzwl %cx, %eax
+ ret
+ SET_SIZE(atomic_sub_short_nv)
+ SET_SIZE(atomic_sub_16_nv)
+
+ ENTRY(atomic_sub_32_nv)
+ ALTENTRY(atomic_sub_int_nv)
+ movl (%rdi), %eax
+1:
+ movl %esi, %ecx
+ subl %eax, %ecx
+ lock
+ cmpxchgl %ecx, (%rdi)
+ jne 1b
+ movl %ecx, %eax
+ ret
+ SET_SIZE(atomic_sub_int_nv)
+ SET_SIZE(atomic_sub_32_nv)
+
+ ENTRY(atomic_sub_64_nv)
+ ALTENTRY(atomic_sub_ptr_nv)
+ ALTENTRY(atomic_sub_long_nv)
+ movq (%rdi), %rax
+1:
+ movq %rsi, %rcx
+ subq %rax, %rcx
+ lock
+ cmpxchgq %rcx, (%rdi)
+ jne 1b
+ movq %rcx, %rax
+ ret
+ SET_SIZE(atomic_sub_long_nv)
+ SET_SIZE(atomic_sub_ptr_nv)
+ SET_SIZE(atomic_sub_64_nv)
+
+ ENTRY(atomic_and_8_nv)
+ ALTENTRY(atomic_and_uchar_nv)
+ movb (%rdi), %al
+1:
+ movb %sil, %cl
+ andb %al, %cl
+ lock
+ cmpxchgb %cl, (%rdi)
+ jne 1b
+ movzbl %cl, %eax
+ ret
+ SET_SIZE(atomic_and_uchar_nv)
+ SET_SIZE(atomic_and_8_nv)
+
+ ENTRY(atomic_and_16_nv)
+ ALTENTRY(atomic_and_ushort_nv)
+ movw (%rdi), %ax
+1:
+ movw %si, %cx
+ andw %ax, %cx
+ lock
+ cmpxchgw %cx, (%rdi)
+ jne 1b
+ movzwl %cx, %eax
+ ret
+ SET_SIZE(atomic_and_ushort_nv)
+ SET_SIZE(atomic_and_16_nv)
+
+ ENTRY(atomic_and_32_nv)
+ ALTENTRY(atomic_and_uint_nv)
+ movl (%rdi), %eax
+1:
+ movl %esi, %ecx
+ andl %eax, %ecx
+ lock
+ cmpxchgl %ecx, (%rdi)
+ jne 1b
+ movl %ecx, %eax
+ ret
+ SET_SIZE(atomic_and_uint_nv)
+ SET_SIZE(atomic_and_32_nv)
+
+ ENTRY(atomic_and_64_nv)
+ ALTENTRY(atomic_and_ulong_nv)
+ movq (%rdi), %rax
+1:
+ movq %rsi, %rcx
+ andq %rax, %rcx
+ lock
+ cmpxchgq %rcx, (%rdi)
+ jne 1b
+ movq %rcx, %rax
+ ret
+ SET_SIZE(atomic_and_ulong_nv)
+ SET_SIZE(atomic_and_64_nv)
+
+ ENTRY(atomic_or_8_nv)
+ ALTENTRY(atomic_or_uchar_nv)
+ movb (%rdi), %al
+1:
+ movb %sil, %cl
+ orb %al, %cl
+ lock
+ cmpxchgb %cl, (%rdi)
+ jne 1b
+ movzbl %cl, %eax
+ ret
+ SET_SIZE(atomic_and_uchar_nv)
+ SET_SIZE(atomic_and_8_nv)
+
+ ENTRY(atomic_or_16_nv)
+ ALTENTRY(atomic_or_ushort_nv)
+ movw (%rdi), %ax
+1:
+ movw %si, %cx
+ orw %ax, %cx
+ lock
+ cmpxchgw %cx, (%rdi)
+ jne 1b
+ movzwl %cx, %eax
+ ret
+ SET_SIZE(atomic_or_ushort_nv)
+ SET_SIZE(atomic_or_16_nv)
+
+ ENTRY(atomic_or_32_nv)
+ ALTENTRY(atomic_or_uint_nv)
+ movl (%rdi), %eax
+1:
+ movl %esi, %ecx
+ orl %eax, %ecx
+ lock
+ cmpxchgl %ecx, (%rdi)
+ jne 1b
+ movl %ecx, %eax
+ ret
+ SET_SIZE(atomic_or_uint_nv)
+ SET_SIZE(atomic_or_32_nv)
+
+ ENTRY(atomic_or_64_nv)
+ ALTENTRY(atomic_or_ulong_nv)
+ movq (%rdi), %rax
+1:
+ movq %rsi, %rcx
+ orq %rax, %rcx
+ lock
+ cmpxchgq %rcx, (%rdi)
+ jne 1b
+ movq %rcx, %rax
+ ret
+ SET_SIZE(atomic_or_ulong_nv)
+ SET_SIZE(atomic_or_64_nv)
+
+ ENTRY(atomic_cas_8)
+ ALTENTRY(atomic_cas_uchar)
+ movzbl %sil, %eax
+ lock
+ cmpxchgb %dl, (%rdi)
+ ret
+ SET_SIZE(atomic_cas_uchar)
+ SET_SIZE(atomic_cas_8)
+
+ ENTRY(atomic_cas_16)
+ ALTENTRY(atomic_cas_ushort)
+ movzwl %si, %eax
+ lock
+ cmpxchgw %dx, (%rdi)
+ ret
+ SET_SIZE(atomic_cas_ushort)
+ SET_SIZE(atomic_cas_16)
+
+ ENTRY(atomic_cas_32)
+ ALTENTRY(atomic_cas_uint)
+ movl %esi, %eax
+ lock
+ cmpxchgl %edx, (%rdi)
+ ret
+ SET_SIZE(atomic_cas_uint)
+ SET_SIZE(atomic_cas_32)
+
+ ENTRY(atomic_cas_64)
+ ALTENTRY(atomic_cas_ulong)
+ ALTENTRY(atomic_cas_ptr)
+ movq %rsi, %rax
+ lock
+ cmpxchgq %rdx, (%rdi)
+ ret
+ SET_SIZE(atomic_cas_ptr)
+ SET_SIZE(atomic_cas_ulong)
+ SET_SIZE(atomic_cas_64)
+
+ ENTRY(atomic_swap_8)
+ ALTENTRY(atomic_swap_uchar)
+ movzbl %sil, %eax
+ lock
+ xchgb %al, (%rdi)
+ ret
+ SET_SIZE(atomic_swap_uchar)
+ SET_SIZE(atomic_swap_8)
+
+ ENTRY(atomic_swap_16)
+ ALTENTRY(atomic_swap_ushort)
+ movzwl %si, %eax
+ lock
+ xchgw %ax, (%rdi)
+ ret
+ SET_SIZE(atomic_swap_ushort)
+ SET_SIZE(atomic_swap_16)
+
+ ENTRY(atomic_swap_32)
+ ALTENTRY(atomic_swap_uint)
+ movl %esi, %eax
+ lock
+ xchgl %eax, (%rdi)
+ ret
+ SET_SIZE(atomic_swap_uint)
+ SET_SIZE(atomic_swap_32)
+
+ ENTRY(atomic_swap_64)
+ ALTENTRY(atomic_swap_ulong)
+ ALTENTRY(atomic_swap_ptr)
+ movq %rsi, %rax
+ lock
+ xchgq %rax, (%rdi)
+ ret
+ SET_SIZE(atomic_swap_ptr)
+ SET_SIZE(atomic_swap_ulong)
+ SET_SIZE(atomic_swap_64)
+
+ ENTRY(atomic_set_long_excl)
+ xorl %eax, %eax
+ lock
+ btsq %rsi, (%rdi)
+ jnc 1f
+ decl %eax
+1:
+ ret
+ SET_SIZE(atomic_set_long_excl)
+
+ ENTRY(atomic_clear_long_excl)
+ xorl %eax, %eax
+ lock
+ btrq %rsi, (%rdi)
+ jc 1f
+ decl %eax
+1:
+ ret
+ SET_SIZE(atomic_clear_long_excl)
+
+ /*
+ * NOTE: membar_enter, and membar_exit are identical routines.
+ * We define them separately, instead of using an ALTENTRY
+ * definitions to alias them together, so that DTrace and
+ * debuggers will see a unique address for them, allowing
+ * more accurate tracing.
+ */
+
+ ENTRY(membar_enter)
+ mfence
+ ret
+ SET_SIZE(membar_enter)
+
+ ENTRY(membar_exit)
+ mfence
+ ret
+ SET_SIZE(membar_exit)
+
+ ENTRY(membar_producer)
+ sfence
+ ret
+ SET_SIZE(membar_producer)
+
+ ENTRY(membar_consumer)
+ lfence
+ ret
+ SET_SIZE(membar_consumer)
+
+#ifdef __ELF__
+.section .note.GNU-stack,"",%progbits
+#endif
diff --git a/lib/libspl/assert.c b/lib/libspl/assert.c
new file mode 100644
index 000000000000..94290ae13e09
--- /dev/null
+++ b/lib/libspl/assert.c
@@ -0,0 +1,46 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#include <assert.h>
+
+int aok = 0;
+
+/* printf version of libspl_assert */
+void
+libspl_assertf(const char *file, const char *func, int line,
+ const char *format, ...)
+{
+ va_list args;
+
+ va_start(args, format);
+ vfprintf(stderr, format, args);
+ fprintf(stderr, "\n");
+ fprintf(stderr, "ASSERT at %s:%d:%s()", file, line, func);
+ va_end(args);
+ if (aok) {
+ return;
+ }
+ abort();
+}
diff --git a/lib/libspl/include/Makefile.am b/lib/libspl/include/Makefile.am
new file mode 100644
index 000000000000..9ca08b2bc0f7
--- /dev/null
+++ b/lib/libspl/include/Makefile.am
@@ -0,0 +1,22 @@
+SUBDIRS = ia32 rpc sys util os
+
+libspldir = $(includedir)/libspl
+libspl_HEADERS = \
+ assert.h \
+ atomic.h \
+ libdevinfo.h \
+ libgen.h \
+ libshare.h \
+ limits.h \
+ locale.h \
+ statcommon.h \
+ stdio.h \
+ stdlib.h \
+ string.h \
+ stropts.h \
+ thread.h \
+ tzfile.h \
+ ucred.h \
+ umem.h \
+ unistd.h \
+ zone.h
diff --git a/lib/libspl/include/assert.h b/lib/libspl/include/assert.h
new file mode 100644
index 000000000000..0503ce492181
--- /dev/null
+++ b/lib/libspl/include/assert.h
@@ -0,0 +1,151 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2007 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#include_next <assert.h>
+
+#ifndef _LIBSPL_ASSERT_H
+#define _LIBSPL_ASSERT_H
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+
+/* Set to non-zero to avoid abort()ing on an assertion failure */
+extern int aok;
+
+/* printf version of libspl_assert */
+extern void libspl_assertf(const char *file, const char *func, int line,
+ const char *format, ...);
+
+static inline int
+libspl_assert(const char *buf, const char *file, const char *func, int line)
+{
+ libspl_assertf(file, func, line, "%s", buf);
+ return (0);
+}
+
+#ifdef verify
+#undef verify
+#endif
+
+#define VERIFY(cond) \
+ (void) ((!(cond)) && \
+ libspl_assert(#cond, __FILE__, __FUNCTION__, __LINE__))
+#define verify(cond) \
+ (void) ((!(cond)) && \
+ libspl_assert(#cond, __FILE__, __FUNCTION__, __LINE__))
+
+#define VERIFY3B(LEFT, OP, RIGHT) \
+do { \
+ const boolean_t __left = (boolean_t)(LEFT); \
+ const boolean_t __right = (boolean_t)(RIGHT); \
+ if (!(__left OP __right)) \
+ libspl_assertf(__FILE__, __FUNCTION__, __LINE__, \
+ "%s %s %s (0x%llx %s 0x%llx)", #LEFT, #OP, #RIGHT, \
+ (u_longlong_t)__left, #OP, (u_longlong_t)__right); \
+} while (0)
+
+#define VERIFY3S(LEFT, OP, RIGHT) \
+do { \
+ const int64_t __left = (int64_t)(LEFT); \
+ const int64_t __right = (int64_t)(RIGHT); \
+ if (!(__left OP __right)) \
+ libspl_assertf(__FILE__, __FUNCTION__, __LINE__, \
+ "%s %s %s (0x%llx %s 0x%llx)", #LEFT, #OP, #RIGHT, \
+ (u_longlong_t)__left, #OP, (u_longlong_t)__right); \
+} while (0)
+
+#define VERIFY3U(LEFT, OP, RIGHT) \
+do { \
+ const uint64_t __left = (uint64_t)(LEFT); \
+ const uint64_t __right = (uint64_t)(RIGHT); \
+ if (!(__left OP __right)) \
+ libspl_assertf(__FILE__, __FUNCTION__, __LINE__, \
+ "%s %s %s (0x%llx %s 0x%llx)", #LEFT, #OP, #RIGHT, \
+ (u_longlong_t)__left, #OP, (u_longlong_t)__right); \
+} while (0)
+
+#define VERIFY3P(LEFT, OP, RIGHT) \
+do { \
+ const uintptr_t __left = (uintptr_t)(LEFT); \
+ const uintptr_t __right = (uintptr_t)(RIGHT); \
+ if (!(__left OP __right)) \
+ libspl_assertf(__FILE__, __FUNCTION__, __LINE__, \
+ "%s %s %s (0x%llx %s 0x%llx)", #LEFT, #OP, #RIGHT, \
+ (u_longlong_t)__left, #OP, (u_longlong_t)__right); \
+} while (0)
+
+#define VERIFY0(LEFT) \
+do { \
+ const uint64_t __left = (uint64_t)(LEFT); \
+ if (!(__left == 0)) \
+ libspl_assertf(__FILE__, __FUNCTION__, __LINE__, \
+ "%s == 0 (0x%llx == 0)", #LEFT, \
+ (u_longlong_t)__left); \
+} while (0)
+
+#ifdef assert
+#undef assert
+#endif
+
+/* Compile time assert */
+#define CTASSERT_GLOBAL(x) _CTASSERT(x, __LINE__)
+#define CTASSERT(x) { _CTASSERT(x, __LINE__); }
+#define _CTASSERT(x, y) __CTASSERT(x, y)
+#define __CTASSERT(x, y) \
+ typedef char __attribute__((unused)) \
+ __compile_time_assertion__ ## y[(x) ? 1 : -1]
+
+#ifdef NDEBUG
+#define ASSERT3B(x, y, z) ((void)0)
+#define ASSERT3S(x, y, z) ((void)0)
+#define ASSERT3U(x, y, z) ((void)0)
+#define ASSERT3P(x, y, z) ((void)0)
+#define ASSERT0(x) ((void)0)
+#define ASSERT(x) ((void)0)
+#define assert(x) ((void)0)
+#define IMPLY(A, B) ((void)0)
+#define EQUIV(A, B) ((void)0)
+#else
+#define ASSERT3B VERIFY3B
+#define ASSERT3S VERIFY3S
+#define ASSERT3U VERIFY3U
+#define ASSERT3P VERIFY3P
+#define ASSERT0 VERIFY0
+#define ASSERT VERIFY
+#define assert VERIFY
+#define IMPLY(A, B) \
+ ((void)(((!(A)) || (B)) || \
+ libspl_assert("(" #A ") implies (" #B ")", \
+ __FILE__, __FUNCTION__, __LINE__)))
+#define EQUIV(A, B) \
+ ((void)((!!(A) == !!(B)) || \
+ libspl_assert("(" #A ") is equivalent to (" #B ")", \
+ __FILE__, __FUNCTION__, __LINE__)))
+
+#endif /* NDEBUG */
+
+#endif /* _LIBSPL_ASSERT_H */
diff --git a/lib/libspl/include/atomic.h b/lib/libspl/include/atomic.h
new file mode 100644
index 000000000000..f8c257f9696b
--- /dev/null
+++ b/lib/libspl/include/atomic.h
@@ -0,0 +1,296 @@
+/*
+ * 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 2005 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _SYS_ATOMIC_H
+#define _SYS_ATOMIC_H
+
+#include <sys/types.h>
+#include <sys/inttypes.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if defined(__STDC__)
+/*
+ * Increment target.
+ */
+extern void atomic_inc_8(volatile uint8_t *);
+extern void atomic_inc_uchar(volatile uchar_t *);
+extern void atomic_inc_16(volatile uint16_t *);
+extern void atomic_inc_ushort(volatile ushort_t *);
+extern void atomic_inc_32(volatile uint32_t *);
+extern void atomic_inc_uint(volatile uint_t *);
+extern void atomic_inc_ulong(volatile ulong_t *);
+#if defined(_INT64_TYPE)
+extern void atomic_inc_64(volatile uint64_t *);
+#endif
+
+/*
+ * Decrement target
+ */
+extern void atomic_dec_8(volatile uint8_t *);
+extern void atomic_dec_uchar(volatile uchar_t *);
+extern void atomic_dec_16(volatile uint16_t *);
+extern void atomic_dec_ushort(volatile ushort_t *);
+extern void atomic_dec_32(volatile uint32_t *);
+extern void atomic_dec_uint(volatile uint_t *);
+extern void atomic_dec_ulong(volatile ulong_t *);
+#if defined(_INT64_TYPE)
+extern void atomic_dec_64(volatile uint64_t *);
+#endif
+
+/*
+ * Add delta to target
+ */
+extern void atomic_add_8(volatile uint8_t *, int8_t);
+extern void atomic_add_char(volatile uchar_t *, signed char);
+extern void atomic_add_16(volatile uint16_t *, int16_t);
+extern void atomic_add_short(volatile ushort_t *, short);
+extern void atomic_add_32(volatile uint32_t *, int32_t);
+extern void atomic_add_int(volatile uint_t *, int);
+extern void atomic_add_ptr(volatile void *, ssize_t);
+extern void atomic_add_long(volatile ulong_t *, long);
+#if defined(_INT64_TYPE)
+extern void atomic_add_64(volatile uint64_t *, int64_t);
+#endif
+
+/*
+ * Subtract delta from target
+ */
+extern void atomic_sub_8(volatile uint8_t *, int8_t);
+extern void atomic_sub_char(volatile uchar_t *, signed char);
+extern void atomic_sub_16(volatile uint16_t *, int16_t);
+extern void atomic_sub_short(volatile ushort_t *, short);
+extern void atomic_sub_32(volatile uint32_t *, int32_t);
+extern void atomic_sub_int(volatile uint_t *, int);
+extern void atomic_sub_ptr(volatile void *, ssize_t);
+extern void atomic_sub_long(volatile ulong_t *, long);
+#if defined(_INT64_TYPE)
+extern void atomic_sub_64(volatile uint64_t *, int64_t);
+#endif
+
+/*
+ * logical OR bits with target
+ */
+extern void atomic_or_8(volatile uint8_t *, uint8_t);
+extern void atomic_or_uchar(volatile uchar_t *, uchar_t);
+extern void atomic_or_16(volatile uint16_t *, uint16_t);
+extern void atomic_or_ushort(volatile ushort_t *, ushort_t);
+extern void atomic_or_32(volatile uint32_t *, uint32_t);
+extern void atomic_or_uint(volatile uint_t *, uint_t);
+extern void atomic_or_ulong(volatile ulong_t *, ulong_t);
+#if defined(_INT64_TYPE)
+extern void atomic_or_64(volatile uint64_t *, uint64_t);
+#endif
+
+/*
+ * logical AND bits with target
+ */
+extern void atomic_and_8(volatile uint8_t *, uint8_t);
+extern void atomic_and_uchar(volatile uchar_t *, uchar_t);
+extern void atomic_and_16(volatile uint16_t *, uint16_t);
+extern void atomic_and_ushort(volatile ushort_t *, ushort_t);
+extern void atomic_and_32(volatile uint32_t *, uint32_t);
+extern void atomic_and_uint(volatile uint_t *, uint_t);
+extern void atomic_and_ulong(volatile ulong_t *, ulong_t);
+#if defined(_INT64_TYPE)
+extern void atomic_and_64(volatile uint64_t *, uint64_t);
+#endif
+
+/*
+ * As above, but return the new value. Note that these _nv() variants are
+ * substantially more expensive on some platforms than the no-return-value
+ * versions above, so don't use them unless you really need to know the
+ * new value *atomically* (e.g. when decrementing a reference count and
+ * checking whether it went to zero).
+ */
+
+/*
+ * Increment target and return new value.
+ */
+extern uint8_t atomic_inc_8_nv(volatile uint8_t *);
+extern uchar_t atomic_inc_uchar_nv(volatile uchar_t *);
+extern uint16_t atomic_inc_16_nv(volatile uint16_t *);
+extern ushort_t atomic_inc_ushort_nv(volatile ushort_t *);
+extern uint32_t atomic_inc_32_nv(volatile uint32_t *);
+extern uint_t atomic_inc_uint_nv(volatile uint_t *);
+extern ulong_t atomic_inc_ulong_nv(volatile ulong_t *);
+#if defined(_INT64_TYPE)
+extern uint64_t atomic_inc_64_nv(volatile uint64_t *);
+#endif
+
+/*
+ * Decrement target and return new value.
+ */
+extern uint8_t atomic_dec_8_nv(volatile uint8_t *);
+extern uchar_t atomic_dec_uchar_nv(volatile uchar_t *);
+extern uint16_t atomic_dec_16_nv(volatile uint16_t *);
+extern ushort_t atomic_dec_ushort_nv(volatile ushort_t *);
+extern uint32_t atomic_dec_32_nv(volatile uint32_t *);
+extern uint_t atomic_dec_uint_nv(volatile uint_t *);
+extern ulong_t atomic_dec_ulong_nv(volatile ulong_t *);
+#if defined(_INT64_TYPE)
+extern uint64_t atomic_dec_64_nv(volatile uint64_t *);
+#endif
+
+/*
+ * Add delta to target
+ */
+extern uint8_t atomic_add_8_nv(volatile uint8_t *, int8_t);
+extern uchar_t atomic_add_char_nv(volatile uchar_t *, signed char);
+extern uint16_t atomic_add_16_nv(volatile uint16_t *, int16_t);
+extern ushort_t atomic_add_short_nv(volatile ushort_t *, short);
+extern uint32_t atomic_add_32_nv(volatile uint32_t *, int32_t);
+extern uint_t atomic_add_int_nv(volatile uint_t *, int);
+extern void *atomic_add_ptr_nv(volatile void *, ssize_t);
+extern ulong_t atomic_add_long_nv(volatile ulong_t *, long);
+#if defined(_INT64_TYPE)
+extern uint64_t atomic_add_64_nv(volatile uint64_t *, int64_t);
+#endif
+
+/*
+ * Subtract delta from target
+ */
+extern uint8_t atomic_sub_8_nv(volatile uint8_t *, int8_t);
+extern uchar_t atomic_sub_char_nv(volatile uchar_t *, signed char);
+extern uint16_t atomic_sub_16_nv(volatile uint16_t *, int16_t);
+extern ushort_t atomic_sub_short_nv(volatile ushort_t *, short);
+extern uint32_t atomic_sub_32_nv(volatile uint32_t *, int32_t);
+extern uint_t atomic_sub_int_nv(volatile uint_t *, int);
+extern void *atomic_sub_ptr_nv(volatile void *, ssize_t);
+extern ulong_t atomic_sub_long_nv(volatile ulong_t *, long);
+#if defined(_INT64_TYPE)
+extern uint64_t atomic_sub_64_nv(volatile uint64_t *, int64_t);
+#endif
+
+/*
+ * logical OR bits with target and return new value.
+ */
+extern uint8_t atomic_or_8_nv(volatile uint8_t *, uint8_t);
+extern uchar_t atomic_or_uchar_nv(volatile uchar_t *, uchar_t);
+extern uint16_t atomic_or_16_nv(volatile uint16_t *, uint16_t);
+extern ushort_t atomic_or_ushort_nv(volatile ushort_t *, ushort_t);
+extern uint32_t atomic_or_32_nv(volatile uint32_t *, uint32_t);
+extern uint_t atomic_or_uint_nv(volatile uint_t *, uint_t);
+extern ulong_t atomic_or_ulong_nv(volatile ulong_t *, ulong_t);
+#if defined(_INT64_TYPE)
+extern uint64_t atomic_or_64_nv(volatile uint64_t *, uint64_t);
+#endif
+
+/*
+ * logical AND bits with target and return new value.
+ */
+extern uint8_t atomic_and_8_nv(volatile uint8_t *, uint8_t);
+extern uchar_t atomic_and_uchar_nv(volatile uchar_t *, uchar_t);
+extern uint16_t atomic_and_16_nv(volatile uint16_t *, uint16_t);
+extern ushort_t atomic_and_ushort_nv(volatile ushort_t *, ushort_t);
+extern uint32_t atomic_and_32_nv(volatile uint32_t *, uint32_t);
+extern uint_t atomic_and_uint_nv(volatile uint_t *, uint_t);
+extern ulong_t atomic_and_ulong_nv(volatile ulong_t *, ulong_t);
+#if defined(_INT64_TYPE)
+extern uint64_t atomic_and_64_nv(volatile uint64_t *, uint64_t);
+#endif
+
+/*
+ * If *arg1 == arg2, set *arg1 = arg3; return old value
+ */
+extern uint8_t atomic_cas_8(volatile uint8_t *, uint8_t, uint8_t);
+extern uchar_t atomic_cas_uchar(volatile uchar_t *, uchar_t, uchar_t);
+extern uint16_t atomic_cas_16(volatile uint16_t *, uint16_t, uint16_t);
+extern ushort_t atomic_cas_ushort(volatile ushort_t *, ushort_t, ushort_t);
+extern uint32_t atomic_cas_32(volatile uint32_t *, uint32_t, uint32_t);
+extern uint_t atomic_cas_uint(volatile uint_t *, uint_t, uint_t);
+extern void *atomic_cas_ptr(volatile void *, void *, void *);
+extern ulong_t atomic_cas_ulong(volatile ulong_t *, ulong_t, ulong_t);
+#if defined(_INT64_TYPE)
+extern uint64_t atomic_cas_64(volatile uint64_t *, uint64_t, uint64_t);
+#endif
+
+/*
+ * Swap target and return old value
+ */
+extern uint8_t atomic_swap_8(volatile uint8_t *, uint8_t);
+extern uchar_t atomic_swap_uchar(volatile uchar_t *, uchar_t);
+extern uint16_t atomic_swap_16(volatile uint16_t *, uint16_t);
+extern ushort_t atomic_swap_ushort(volatile ushort_t *, ushort_t);
+extern uint32_t atomic_swap_32(volatile uint32_t *, uint32_t);
+extern uint_t atomic_swap_uint(volatile uint_t *, uint_t);
+extern void *atomic_swap_ptr(volatile void *, void *);
+extern ulong_t atomic_swap_ulong(volatile ulong_t *, ulong_t);
+#if defined(_INT64_TYPE)
+extern uint64_t atomic_swap_64(volatile uint64_t *, uint64_t);
+#endif
+
+/*
+ * Perform an exclusive atomic bit set/clear on a target.
+ * Returns 0 if bit was successfully set/cleared, or -1
+ * if the bit was already set/cleared.
+ */
+extern int atomic_set_long_excl(volatile ulong_t *, uint_t);
+extern int atomic_clear_long_excl(volatile ulong_t *, uint_t);
+
+/*
+ * Generic memory barrier used during lock entry, placed after the
+ * memory operation that acquires the lock to guarantee that the lock
+ * protects its data. No stores from after the memory barrier will
+ * reach visibility, and no loads from after the barrier will be
+ * resolved, before the lock acquisition reaches global visibility.
+ */
+extern void membar_enter(void);
+
+/*
+ * Generic memory barrier used during lock exit, placed before the
+ * memory operation that releases the lock to guarantee that the lock
+ * protects its data. All loads and stores issued before the barrier
+ * will be resolved before the subsequent lock update reaches visibility.
+ */
+extern void membar_exit(void);
+
+/*
+ * Arrange that all stores issued before this point in the code reach
+ * global visibility before any stores that follow; useful in producer
+ * modules that update a data item, then set a flag that it is available.
+ * The memory barrier guarantees that the available flag is not visible
+ * earlier than the updated data, i.e. it imposes store ordering.
+ */
+extern void membar_producer(void);
+
+/*
+ * Arrange that all loads issued before this point in the code are
+ * completed before any subsequent loads; useful in consumer modules
+ * that check to see if data is available and read the data.
+ * The memory barrier guarantees that the data is not sampled until
+ * after the available flag has been seen, i.e. it imposes load ordering.
+ */
+extern void membar_consumer(void);
+#endif /* __STDC__ */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _SYS_ATOMIC_H */
diff --git a/lib/libspl/include/ia32/Makefile.am b/lib/libspl/include/ia32/Makefile.am
new file mode 100644
index 000000000000..081839c48c8f
--- /dev/null
+++ b/lib/libspl/include/ia32/Makefile.am
@@ -0,0 +1 @@
+SUBDIRS = sys
diff --git a/lib/libspl/include/ia32/sys/Makefile.am b/lib/libspl/include/ia32/sys/Makefile.am
new file mode 100644
index 000000000000..683288460cdf
--- /dev/null
+++ b/lib/libspl/include/ia32/sys/Makefile.am
@@ -0,0 +1,3 @@
+libspldir = $(includedir)/libspl/ia32/sys
+libspl_HEADERS = \
+ asm_linkage.h
diff --git a/lib/libspl/include/ia32/sys/asm_linkage.h b/lib/libspl/include/ia32/sys/asm_linkage.h
new file mode 100644
index 000000000000..61c4d1a26977
--- /dev/null
+++ b/lib/libspl/include/ia32/sys/asm_linkage.h
@@ -0,0 +1,302 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2004 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _IA32_SYS_ASM_LINKAGE_H
+#define _IA32_SYS_ASM_LINKAGE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef _ASM /* The remainder of this file is only for assembly files */
+
+/*
+ * make annoying differences in assembler syntax go away
+ */
+
+/*
+ * D16 and A16 are used to insert instructions prefixes; the
+ * macros help the assembler code be slightly more portable.
+ */
+#if !defined(__GNUC_AS__)
+/*
+ * /usr/ccs/bin/as prefixes are parsed as separate instructions
+ */
+#define D16 data16;
+#define A16 addr16;
+
+/*
+ * (There are some weird constructs in constant expressions)
+ */
+#define _CONST(const) [const]
+#define _BITNOT(const) -1!_CONST(const)
+#define _MUL(a, b) _CONST(a \* b)
+
+#else
+/*
+ * Why not use the 'data16' and 'addr16' prefixes .. well, the
+ * assembler doesn't quite believe in real mode, and thus argues with
+ * us about what we're trying to do.
+ */
+#define D16 .byte 0x66;
+#define A16 .byte 0x67;
+
+#define _CONST(const) (const)
+#define _BITNOT(const) ~_CONST(const)
+#define _MUL(a, b) _CONST(a * b)
+
+#endif
+
+/*
+ * C pointers are different sizes between i386 and amd64.
+ * These constants can be used to compute offsets into pointer arrays.
+ */
+#if defined(__amd64)
+#define CLONGSHIFT 3
+#define CLONGSIZE 8
+#define CLONGMASK 7
+#elif defined(__i386)
+#define CLONGSHIFT 2
+#define CLONGSIZE 4
+#define CLONGMASK 3
+#endif
+
+/*
+ * Since we know we're either ILP32 or LP64 ..
+ */
+#define CPTRSHIFT CLONGSHIFT
+#define CPTRSIZE CLONGSIZE
+#define CPTRMASK CLONGMASK
+
+#if CPTRSIZE != (1 << CPTRSHIFT) || CLONGSIZE != (1 << CLONGSHIFT)
+#error "inconsistent shift constants"
+#endif
+
+#if CPTRMASK != (CPTRSIZE - 1) || CLONGMASK != (CLONGSIZE - 1)
+#error "inconsistent mask constants"
+#endif
+
+#define ASM_ENTRY_ALIGN 16
+
+/*
+ * SSE register alignment and save areas
+ */
+
+#define XMM_SIZE 16
+#define XMM_ALIGN 16
+
+#if defined(__amd64)
+
+#define SAVE_XMM_PROLOG(sreg, nreg) \
+ subq $_CONST(_MUL(XMM_SIZE, nreg)), %rsp; \
+ movq %rsp, sreg
+
+#define RSTOR_XMM_EPILOG(sreg, nreg) \
+ addq $_CONST(_MUL(XMM_SIZE, nreg)), %rsp
+
+#elif defined(__i386)
+
+#define SAVE_XMM_PROLOG(sreg, nreg) \
+ subl $_CONST(_MUL(XMM_SIZE, nreg) + XMM_ALIGN), %esp; \
+ movl %esp, sreg; \
+ addl $XMM_ALIGN, sreg; \
+ andl $_BITNOT(XMM_ALIGN-1), sreg
+
+#define RSTOR_XMM_EPILOG(sreg, nreg) \
+ addl $_CONST(_MUL(XMM_SIZE, nreg) + XMM_ALIGN), %esp;
+
+#endif /* __i386 */
+
+/*
+ * profiling causes definitions of the MCOUNT and RTMCOUNT
+ * particular to the type
+ */
+#ifdef GPROF
+
+#define MCOUNT(x) \
+ pushl %ebp; \
+ movl %esp, %ebp; \
+ call _mcount; \
+ popl %ebp
+
+#endif /* GPROF */
+
+#ifdef PROF
+
+#define MCOUNT(x) \
+/* CSTYLED */ \
+ .lcomm .L_/**/x/**/1, 4, 4; \
+ pushl %ebp; \
+ movl %esp, %ebp; \
+/* CSTYLED */ \
+ movl $.L_/**/x/**/1, %edx; \
+ call _mcount; \
+ popl %ebp
+
+#endif /* PROF */
+
+/*
+ * if we are not profiling, MCOUNT should be defined to nothing
+ */
+#if !defined(PROF) && !defined(GPROF)
+#define MCOUNT(x)
+#endif /* !defined(PROF) && !defined(GPROF) */
+
+#define RTMCOUNT(x) MCOUNT(x)
+
+/*
+ * Macro to define weak symbol aliases. These are similar to the ANSI-C
+ * #pragma weak name = _name
+ * except a compiler can determine type. The assembler must be told. Hence,
+ * the second parameter must be the type of the symbol (i.e.: function,...)
+ */
+#define ANSI_PRAGMA_WEAK(sym, stype) \
+ .weak sym; \
+ .type sym, @stype; \
+/* CSTYLED */ \
+sym = _/**/sym
+
+/*
+ * Like ANSI_PRAGMA_WEAK(), but for unrelated names, as in:
+ * #pragma weak sym1 = sym2
+ */
+#define ANSI_PRAGMA_WEAK2(sym1, sym2, stype) \
+ .weak sym1; \
+ .type sym1, @stype; \
+sym1 = sym2
+
+/*
+ * ENTRY provides the standard procedure entry code and an easy way to
+ * insert the calls to mcount for profiling. ENTRY_NP is identical, but
+ * never calls mcount.
+ */
+#define ENTRY(x) \
+ .text; \
+ .align ASM_ENTRY_ALIGN; \
+ .globl x; \
+ .type x, @function; \
+x: MCOUNT(x)
+
+#define ENTRY_NP(x) \
+ .text; \
+ .align ASM_ENTRY_ALIGN; \
+ .globl x; \
+ .type x, @function; \
+x:
+
+#define RTENTRY(x) \
+ .text; \
+ .align ASM_ENTRY_ALIGN; \
+ .globl x; \
+ .type x, @function; \
+x: RTMCOUNT(x)
+
+/*
+ * ENTRY2 is identical to ENTRY but provides two labels for the entry point.
+ */
+#define ENTRY2(x, y) \
+ .text; \
+ .align ASM_ENTRY_ALIGN; \
+ .globl x, y; \
+ .type x, @function; \
+ .type y, @function; \
+/* CSTYLED */ \
+x: ; \
+y: MCOUNT(x)
+
+#define ENTRY_NP2(x, y) \
+ .text; \
+ .align ASM_ENTRY_ALIGN; \
+ .globl x, y; \
+ .type x, @function; \
+ .type y, @function; \
+/* CSTYLED */ \
+x: ; \
+y:
+
+
+/*
+ * ALTENTRY provides for additional entry points.
+ */
+#define ALTENTRY(x) \
+ .globl x; \
+ .type x, @function; \
+x:
+
+/*
+ * DGDEF and DGDEF2 provide global data declarations.
+ *
+ * DGDEF provides a word aligned word of storage.
+ *
+ * DGDEF2 allocates "sz" bytes of storage with **NO** alignment. This
+ * implies this macro is best used for byte arrays.
+ *
+ * DGDEF3 allocates "sz" bytes of storage with "algn" alignment.
+ */
+#define DGDEF2(name, sz) \
+ .data; \
+ .globl name; \
+ .type name, @object; \
+ .size name, sz; \
+name:
+
+#define DGDEF3(name, sz, algn) \
+ .data; \
+ .align algn; \
+ .globl name; \
+ .type name, @object; \
+ .size name, sz; \
+name:
+
+#define DGDEF(name) DGDEF3(name, 4, 4)
+
+/*
+ * SET_SIZE trails a function and set the size for the ELF symbol table.
+ */
+#define SET_SIZE(x) \
+ .size x, [.-x]
+
+/*
+ * NWORD provides native word value.
+ */
+#if defined(__amd64)
+
+/*CSTYLED*/
+#define NWORD quad
+
+#elif defined(__i386)
+
+#define NWORD long
+
+#endif /* __i386 */
+
+#endif /* _ASM */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _IA32_SYS_ASM_LINKAGE_H */
diff --git a/lib/libspl/include/libdevinfo.h b/lib/libspl/include/libdevinfo.h
new file mode 100644
index 000000000000..be1d291f4051
--- /dev/null
+++ b/lib/libspl/include/libdevinfo.h
@@ -0,0 +1,30 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _LIBSPL_LIBDEVINFO_H
+#define _LIBSPL_LIBDEVINFO_H
+
+#endif /* _LIBSPL_LIBDEVINFO_H */
diff --git a/lib/libspl/include/libgen.h b/lib/libspl/include/libgen.h
new file mode 100644
index 000000000000..c46d7454e49f
--- /dev/null
+++ b/lib/libspl/include/libgen.h
@@ -0,0 +1,35 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2003 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _LIBSPL_LIBGEN_H
+#define _LIBSPL_LIBGEN_H
+
+#include <sys/types.h>
+#include_next <libgen.h>
+
+extern int mkdirp(const char *, mode_t);
+
+#endif /* _LIBSPL_LIBGEN_H */
diff --git a/lib/libspl/include/libshare.h b/lib/libspl/include/libshare.h
new file mode 100644
index 000000000000..ea53f8c15089
--- /dev/null
+++ b/lib/libspl/include/libshare.h
@@ -0,0 +1,86 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ * Copyright (c) 2019, 2020 by Delphix. All rights reserved.
+ */
+#ifndef _LIBSPL_LIBSHARE_H
+#define _LIBSPL_LIBSHARE_H
+
+/* API Initialization */
+#define SA_INIT_SHARE_API 0x0001 /* init share specific interface */
+#define SA_INIT_CONTROL_API 0x0002 /* init control specific interface */
+
+/*
+ * defined error values
+ */
+
+#define SA_OK 0
+#define SA_NO_SUCH_PATH 1 /* provided path doesn't exist */
+#define SA_NO_MEMORY 2 /* no memory for data structures */
+#define SA_DUPLICATE_NAME 3 /* object name is already in use */
+#define SA_BAD_PATH 4 /* not a full path */
+#define SA_NO_SUCH_GROUP 5 /* group is not defined */
+#define SA_CONFIG_ERR 6 /* system configuration error */
+#define SA_SYSTEM_ERR 7 /* system error, use errno */
+#define SA_SYNTAX_ERR 8 /* syntax error on command line */
+#define SA_NO_PERMISSION 9 /* no permission for operation */
+#define SA_BUSY 10 /* resource is busy */
+#define SA_NO_SUCH_PROP 11 /* property doesn't exist */
+#define SA_INVALID_NAME 12 /* name of object is invalid */
+#define SA_INVALID_PROTOCOL 13 /* specified protocol not valid */
+#define SA_NOT_ALLOWED 14 /* operation not allowed */
+#define SA_BAD_VALUE 15 /* bad value for property */
+#define SA_INVALID_SECURITY 16 /* invalid security type */
+#define SA_NO_SUCH_SECURITY 17 /* security set not found */
+#define SA_VALUE_CONFLICT 18 /* property value conflict */
+#define SA_NOT_IMPLEMENTED 19 /* plugin interface not implemented */
+#define SA_INVALID_PATH 20 /* path is sub-dir of existing share */
+#define SA_NOT_SUPPORTED 21 /* operation not supported for proto */
+#define SA_PROP_SHARE_ONLY 22 /* property valid on share only */
+#define SA_NOT_SHARED 23 /* path is not shared */
+#define SA_NO_SUCH_RESOURCE 24 /* resource not found */
+#define SA_RESOURCE_REQUIRED 25 /* resource name is required */
+#define SA_MULTIPLE_ERROR 26 /* multiple protocols reported error */
+#define SA_PATH_IS_SUBDIR 27 /* check_path found path is subdir */
+#define SA_PATH_IS_PARENTDIR 28 /* check_path found path is parent */
+#define SA_NO_SECTION 29 /* protocol requires section info */
+#define SA_NO_SUCH_SECTION 30 /* no section found */
+#define SA_NO_PROPERTIES 31 /* no properties found */
+#define SA_PASSWORD_ENC 32 /* passwords must be encrypted */
+#define SA_SHARE_EXISTS 33 /* path or file is already shared */
+
+/* initialization */
+extern char *sa_errorstr(int);
+
+/* share control */
+extern int sa_enable_share(const char *, const char *, const char *,
+ char *);
+extern int sa_disable_share(const char *, char *);
+extern boolean_t sa_is_shared(const char *, char *);
+extern void sa_commit_shares(const char *);
+
+/* protocol specific interfaces */
+extern int sa_validate_shareopts(char *, char *);
+
+#endif /* _LIBSPL_LIBSHARE_H */
diff --git a/lib/libspl/include/limits.h b/lib/libspl/include/limits.h
new file mode 100644
index 000000000000..1a42cfec469c
--- /dev/null
+++ b/lib/libspl/include/limits.h
@@ -0,0 +1,40 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#include_next <limits.h>
+
+#ifndef _LIBSPL_LIMITS_H
+#define _LIBSPL_LIMITS_H
+
+#define DBL_DIG 15
+#define DBL_MAX 1.7976931348623157081452E+308
+#define DBL_MIN 2.2250738585072013830903E-308
+
+#define FLT_DIG 6
+#define FLT_MAX 3.4028234663852885981170E+38F
+#define FLT_MIN 1.1754943508222875079688E-38F
+
+#endif /* _LIBSPL_LIMITS_H */
diff --git a/lib/libspl/include/locale.h b/lib/libspl/include/locale.h
new file mode 100644
index 000000000000..6c74df72072e
--- /dev/null
+++ b/lib/libspl/include/locale.h
@@ -0,0 +1,35 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#include_next <locale.h>
+
+#ifndef _LIBSPL_LOCALE_H
+#define _LIBSPL_LOCALE_H
+
+#include <time.h>
+#include <sys/time.h>
+
+#endif
diff --git a/lib/libspl/include/os/Makefile.am b/lib/libspl/include/os/Makefile.am
new file mode 100644
index 000000000000..7b362e02ad59
--- /dev/null
+++ b/lib/libspl/include/os/Makefile.am
@@ -0,0 +1,7 @@
+if BUILD_FREEBSD
+SUBDIRS = freebsd
+endif
+
+if BUILD_LINUX
+SUBDIRS = linux
+endif
diff --git a/lib/libspl/include/os/freebsd/Makefile.am b/lib/libspl/include/os/freebsd/Makefile.am
new file mode 100644
index 000000000000..081839c48c8f
--- /dev/null
+++ b/lib/libspl/include/os/freebsd/Makefile.am
@@ -0,0 +1 @@
+SUBDIRS = sys
diff --git a/lib/libspl/include/os/freebsd/sys/Makefile.am b/lib/libspl/include/os/freebsd/sys/Makefile.am
new file mode 100644
index 000000000000..6775522f5d72
--- /dev/null
+++ b/lib/libspl/include/os/freebsd/sys/Makefile.am
@@ -0,0 +1,11 @@
+libspldir = $(includedir)/libspl/sys
+libspl_HEADERS = \
+ byteorder.h \
+ file.h \
+ mnttab.h \
+ mount.h \
+ param.h \
+ stat.h \
+ sysmacros.h \
+ vfs.h \
+ zfs_context_os.h
diff --git a/lib/libspl/include/os/freebsd/sys/byteorder.h b/lib/libspl/include/os/freebsd/sys/byteorder.h
new file mode 100644
index 000000000000..cd692d3616e0
--- /dev/null
+++ b/lib/libspl/include/os/freebsd/sys/byteorder.h
@@ -0,0 +1,192 @@
+/*
+ * 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 2007 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+/* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */
+/* All Rights Reserved */
+
+/*
+ * University Copyright- Copyright (c) 1982, 1986, 1988
+ * The Regents of the University of California
+ * All Rights Reserved
+ *
+ * University Acknowledgment- Portions of this document are derived from
+ * software developed by the University of California, Berkeley, and its
+ * contributors.
+ */
+
+#ifndef _SYS_BYTEORDER_H
+#define _SYS_BYTEORDER_H
+
+#include <sys/endian.h>
+#include <netinet/in.h>
+#include <sys/isa_defs.h>
+#include <sys/int_types.h>
+
+#if defined(__GNUC__) && defined(_ASM_INLINES) && \
+ (defined(__i386) || defined(__amd64))
+#include <asm/byteorder.h>
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * macros for conversion between host and (internet) network byte order
+ */
+#if !defined(_XPG4_2) || defined(__EXTENSIONS__)
+
+/*
+ * Macros to reverse byte order
+ */
+#define BSWAP_8(x) ((x) & 0xff)
+#define BSWAP_16(x) ((BSWAP_8(x) << 8) | BSWAP_8((x) >> 8))
+#define BSWAP_32(x) ((BSWAP_16(x) << 16) | BSWAP_16((x) >> 16))
+#define BSWAP_64(x) ((BSWAP_32(x) << 32) | BSWAP_32((x) >> 32))
+
+#define BMASK_8(x) ((x) & 0xff)
+#define BMASK_16(x) ((x) & 0xffff)
+#define BMASK_32(x) ((x) & 0xffffffff)
+#define BMASK_64(x) (x)
+
+/*
+ * Macros to convert from a specific byte order to/from native byte order
+ */
+#ifdef _ZFS_BIG_ENDIAN
+#define BE_8(x) BMASK_8(x)
+#define BE_16(x) BMASK_16(x)
+#define BE_32(x) BMASK_32(x)
+#define BE_64(x) BMASK_64(x)
+#define LE_8(x) BSWAP_8(x)
+#define LE_16(x) BSWAP_16(x)
+#define LE_32(x) BSWAP_32(x)
+#define LE_64(x) BSWAP_64(x)
+#else
+#define LE_8(x) BMASK_8(x)
+#define LE_16(x) BMASK_16(x)
+#define LE_32(x) BMASK_32(x)
+#define LE_64(x) BMASK_64(x)
+#define BE_8(x) BSWAP_8(x)
+#define BE_16(x) BSWAP_16(x)
+#define BE_32(x) BSWAP_32(x)
+#define BE_64(x) BSWAP_64(x)
+#endif
+
+#ifdef _ZFS_BIG_ENDIAN
+static __inline__ uint64_t
+htonll(uint64_t n)
+{
+ return (n);
+}
+
+static __inline__ uint64_t
+ntohll(uint64_t n)
+{
+ return (n);
+}
+#else
+static __inline__ uint64_t
+htonll(uint64_t n)
+{
+ return ((((uint64_t)htonl(n)) << 32) + htonl(n >> 32));
+}
+
+static __inline__ uint64_t
+ntohll(uint64_t n)
+{
+ return ((((uint64_t)ntohl(n)) << 32) + ntohl(n >> 32));
+}
+#endif
+
+/*
+ * Macros to read unaligned values from a specific byte order to
+ * native byte order
+ */
+
+#define BE_IN8(xa) \
+ *((uint8_t *)(xa))
+
+#define BE_IN16(xa) \
+ (((uint16_t)BE_IN8(xa) << 8) | BE_IN8((uint8_t *)(xa)+1))
+
+#define BE_IN32(xa) \
+ (((uint32_t)BE_IN16(xa) << 16) | BE_IN16((uint8_t *)(xa)+2))
+
+#define BE_IN64(xa) \
+ (((uint64_t)BE_IN32(xa) << 32) | BE_IN32((uint8_t *)(xa)+4))
+
+#define LE_IN8(xa) \
+ *((uint8_t *)(xa))
+
+#define LE_IN16(xa) \
+ (((uint16_t)LE_IN8((uint8_t *)(xa) + 1) << 8) | LE_IN8(xa))
+
+#define LE_IN32(xa) \
+ (((uint32_t)LE_IN16((uint8_t *)(xa) + 2) << 16) | LE_IN16(xa))
+
+#define LE_IN64(xa) \
+ (((uint64_t)LE_IN32((uint8_t *)(xa) + 4) << 32) | LE_IN32(xa))
+
+/*
+ * Macros to write unaligned values from native byte order to a specific byte
+ * order.
+ */
+
+#define BE_OUT8(xa, yv) *((uint8_t *)(xa)) = (uint8_t)(yv);
+
+#define BE_OUT16(xa, yv) \
+ BE_OUT8((uint8_t *)(xa) + 1, yv); \
+ BE_OUT8((uint8_t *)(xa), (yv) >> 8);
+
+#define BE_OUT32(xa, yv) \
+ BE_OUT16((uint8_t *)(xa) + 2, yv); \
+ BE_OUT16((uint8_t *)(xa), (yv) >> 16);
+
+#define BE_OUT64(xa, yv) \
+ BE_OUT32((uint8_t *)(xa) + 4, yv); \
+ BE_OUT32((uint8_t *)(xa), (yv) >> 32);
+
+#define LE_OUT8(xa, yv) *((uint8_t *)(xa)) = (uint8_t)(yv);
+
+#define LE_OUT16(xa, yv) \
+ LE_OUT8((uint8_t *)(xa), yv); \
+ LE_OUT8((uint8_t *)(xa) + 1, (yv) >> 8);
+
+#define LE_OUT32(xa, yv) \
+ LE_OUT16((uint8_t *)(xa), yv); \
+ LE_OUT16((uint8_t *)(xa) + 2, (yv) >> 16);
+
+#define LE_OUT64(xa, yv) \
+ LE_OUT32((uint8_t *)(xa), yv); \
+ LE_OUT32((uint8_t *)(xa) + 4, (yv) >> 32);
+
+#endif /* !defined(_XPG4_2) || defined(__EXTENSIONS__) */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _SYS_BYTEORDER_H */
diff --git a/lib/libspl/include/os/freebsd/sys/file.h b/lib/libspl/include/os/freebsd/sys/file.h
new file mode 100644
index 000000000000..27fd2888f326
--- /dev/null
+++ b/lib/libspl/include/os/freebsd/sys/file.h
@@ -0,0 +1,42 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2006 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _LIBSPL_SYS_FILE_H
+#define _LIBSPL_SYS_FILE_H
+
+#include_next <sys/file.h>
+
+#define FCREAT O_CREAT
+#define FTRUNC O_TRUNC
+#define FSYNC O_SYNC
+#define FDSYNC O_DSYNC
+#define FEXCL O_EXCL
+
+#define FNODSYNC 0x10000 /* fsync pseudo flag */
+#define FNOFOLLOW 0x20000 /* don't follow symlinks */
+#define FIGNORECASE 0x80000 /* request case-insensitive lookups */
+
+#endif
diff --git a/lib/libspl/include/os/freebsd/sys/mnttab.h b/lib/libspl/include/os/freebsd/sys/mnttab.h
new file mode 100644
index 000000000000..c08349bdf9bd
--- /dev/null
+++ b/lib/libspl/include/os/freebsd/sys/mnttab.h
@@ -0,0 +1,85 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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) 1984, 1986, 1987, 1988, 1989 AT&T */
+/* All Rights Reserved */
+/*
+ * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+/* Copyright 2006 Ricardo Correia */
+
+#ifndef _SYS_MNTTAB_H
+#define _SYS_MNTTAB_H
+
+#include <stdio.h>
+#include <sys/types.h>
+
+#ifdef MNTTAB
+#undef MNTTAB
+#endif /* MNTTAB */
+
+#include <paths.h>
+#include <sys/mount.h>
+#define MNTTAB _PATH_DEVZERO
+#define MS_NOMNTTAB 0x0
+#define MS_RDONLY 0x1
+#define umount2(p, f) unmount(p, f)
+#define MNT_LINE_MAX 4108
+
+#define MNT_TOOLONG 1 /* entry exceeds MNT_LINE_MAX */
+#define MNT_TOOMANY 2 /* too many fields in line */
+#define MNT_TOOFEW 3 /* too few fields in line */
+
+struct mnttab {
+ char *mnt_special;
+ char *mnt_mountp;
+ char *mnt_fstype;
+ char *mnt_mntopts;
+};
+
+/*
+ * NOTE: fields in extmnttab should match struct mnttab till new fields
+ * are encountered, this allows hasmntopt to work properly when its arg is
+ * a pointer to an extmnttab struct cast to a mnttab struct pointer.
+ */
+
+struct extmnttab {
+ char *mnt_special;
+ char *mnt_mountp;
+ char *mnt_fstype;
+ char *mnt_mntopts;
+ uint_t mnt_major;
+ uint_t mnt_minor;
+};
+
+struct stat64;
+struct statfs;
+
+extern int getmntany(FILE *fp, struct mnttab *mp, struct mnttab *mpref);
+extern int _sol_getmntent(FILE *fp, struct mnttab *mp);
+extern int getextmntent(const char *path, struct extmnttab *entry,
+ struct stat64 *statbuf);
+extern void statfs2mnttab(struct statfs *sfs, struct mnttab *mp);
+char *hasmntopt(struct mnttab *mnt, char *opt);
+int getmntent(FILE *fp, struct mnttab *mp);
+
+#endif
diff --git a/lib/libspl/include/os/freebsd/sys/mount.h b/lib/libspl/include/os/freebsd/sys/mount.h
new file mode 100644
index 000000000000..b4023910005b
--- /dev/null
+++ b/lib/libspl/include/os/freebsd/sys/mount.h
@@ -0,0 +1,108 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2006 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+
+#ifndef _LIBSPL_SYS_MOUNT_H
+#define _LIBSPL_SYS_MOUNT_H
+
+#undef _SYS_MOUNT_H_
+#include_next <sys/mount.h>
+
+#include <assert.h>
+#include <string.h>
+#include <stdlib.h>
+
+/*
+ * Some old glibc headers don't define BLKGETSIZE64
+ * and we don't want to require the kernel headers
+ */
+#if !defined(BLKGETSIZE64)
+#define BLKGETSIZE64 _IOR(0x12, 114, size_t)
+#endif
+
+/*
+ * Some old glibc headers don't correctly define MS_DIRSYNC and
+ * instead use the enum name S_WRITE. When using these older
+ * headers define MS_DIRSYNC to be S_WRITE.
+ */
+#if !defined(MS_DIRSYNC)
+#define MS_DIRSYNC S_WRITE
+#endif
+
+/*
+ * Some old glibc headers don't correctly define MS_POSIXACL and
+ * instead leave it undefined. When using these older headers define
+ * MS_POSIXACL to the reserved value of (1<<16).
+ */
+#if !defined(MS_POSIXACL)
+#define MS_POSIXACL (1<<16)
+#endif
+
+#define MS_NOSUID MNT_NOSUID
+#define MS_NOEXEC MNT_NOEXEC
+#define MS_NODEV 0
+#define S_WRITE 0
+#define MS_BIND 0
+#define MS_REMOUNT 0
+#define MS_SYNCHRONOUS MNT_SYNCHRONOUS
+
+#define MS_USERS (MS_NOEXEC|MS_NOSUID|MS_NODEV)
+#define MS_OWNER (MS_NOSUID|MS_NODEV)
+#define MS_GROUP (MS_NOSUID|MS_NODEV)
+#define MS_COMMENT 0
+
+/*
+ * Older glibc <sys/mount.h> headers did not define all the available
+ * umount2(2) flags. Both MNT_FORCE and MNT_DETACH are supported in the
+ * kernel back to 2.4.11 so we define them correctly if they are missing.
+ */
+#ifdef MNT_FORCE
+#define MS_FORCE MNT_FORCE
+#else
+#define MS_FORCE 0x00000001
+#endif /* MNT_FORCE */
+
+#ifdef MNT_DETACH
+#define MS_DETACH MNT_DETACH
+#else
+#define MS_DETACH 0x00000002
+#endif /* MNT_DETACH */
+
+/*
+ * Overlay mount is default in Linux, but for solaris/zfs
+ * compatibility, MS_OVERLAY is defined to explicitly have the user
+ * provide a flag (-O) to mount over a non empty directory.
+ */
+#define MS_OVERLAY 0x00000004
+
+/*
+ * MS_CRYPT indicates that encryption keys should be loaded if they are not
+ * already available. This is not defined in glibc, but it is never seen by
+ * the kernel so it will not cause any problems.
+ */
+#define MS_CRYPT 0x00000008
+
+#endif /* _LIBSPL_SYS_MOUNT_H */
diff --git a/lib/libspl/include/os/freebsd/sys/param.h b/lib/libspl/include/os/freebsd/sys/param.h
new file mode 100644
index 000000000000..7c23b670591a
--- /dev/null
+++ b/lib/libspl/include/os/freebsd/sys/param.h
@@ -0,0 +1,66 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2006 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _LIBSPL_SYS_PARAM_H
+#define _LIBSPL_SYS_PARAM_H
+
+#include_next <sys/param.h>
+#include <unistd.h>
+
+/*
+ * File system parameters and macros.
+ *
+ * The file system is made out of blocks of at most MAXBSIZE units,
+ * with smaller units (fragments) only in the last direct block.
+ * MAXBSIZE primarily determines the size of buffers in the buffer
+ * pool. It may be made larger without any effect on existing
+ * file systems; however making it smaller may make some file
+ * systems unmountable.
+ *
+ * Note that the blocked devices are assumed to have DEV_BSIZE
+ * "sectors" and that fragments must be some multiple of this size.
+ */
+#define MAXNAMELEN 256
+
+#ifndef IN_BASE
+#define UID_NOBODY 60001 /* user ID no body */
+#define GID_NOBODY UID_NOBODY
+#endif
+#define UID_NOACCESS 60002 /* user ID no access */
+
+#define MAXUID UINT32_MAX /* max user id */
+#define MAXPROJID MAXUID /* max project id */
+
+#ifdef PAGESIZE
+#undef PAGESIZE
+#endif /* PAGESIZE */
+
+extern size_t spl_pagesize(void);
+#define PAGESIZE (spl_pagesize())
+
+extern int execvpe(const char *name, char * const argv[], char * const envp[]);
+
+#endif
diff --git a/lib/libspl/include/os/freebsd/sys/stat.h b/lib/libspl/include/os/freebsd/sys/stat.h
new file mode 100644
index 000000000000..82c86262fff3
--- /dev/null
+++ b/lib/libspl/include/os/freebsd/sys/stat.h
@@ -0,0 +1,71 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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) 2010, Oracle and/or its affiliates. All rights reserved.
+ */
+
+#ifndef _LIBSPL_SYS_STAT_H
+#define _LIBSPL_SYS_STAT_H
+
+#include_next <sys/stat.h>
+
+#include <sys/mount.h> /* for BLKGETSIZE64 */
+
+#define stat64 stat
+
+#define MAXOFFSET_T OFF_MAX
+
+#ifndef _KERNEL
+#include <sys/disk.h>
+
+static __inline int
+fstat64(int fd, struct stat *sb)
+{
+ int ret;
+
+ ret = fstat(fd, sb);
+ if (ret == 0) {
+ if (S_ISCHR(sb->st_mode))
+ (void) ioctl(fd, DIOCGMEDIASIZE, &sb->st_size);
+ }
+ return (ret);
+}
+#endif
+
+/*
+ * Emulate Solaris' behavior of returning the block device size in fstat64().
+ */
+static inline int
+fstat64_blk(int fd, struct stat64 *st)
+{
+ if (fstat64(fd, st) == -1)
+ return (-1);
+
+ /* In Linux we need to use an ioctl to get the size of a block device */
+ if (S_ISBLK(st->st_mode)) {
+ if (ioctl(fd, BLKGETSIZE64, &st->st_size) != 0)
+ return (-1);
+ }
+
+ return (0);
+}
+#endif /* _LIBSPL_SYS_STAT_H */
diff --git a/lib/libspl/include/os/freebsd/sys/sysmacros.h b/lib/libspl/include/os/freebsd/sys/sysmacros.h
new file mode 100644
index 000000000000..d9639d27b60e
--- /dev/null
+++ b/lib/libspl/include/os/freebsd/sys/sysmacros.h
@@ -0,0 +1 @@
+/* keep me */
diff --git a/lib/libspl/include/os/freebsd/sys/vfs.h b/lib/libspl/include/os/freebsd/sys/vfs.h
new file mode 100644
index 000000000000..55eb3c23b22e
--- /dev/null
+++ b/lib/libspl/include/os/freebsd/sys/vfs.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2020 iXsystems, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef ZFS_SYS_VFS_H_
+#define ZFS_SYS_VFS_H_
+
+#include_next <sys/statvfs.h>
+
+int fsshare(const char *, const char *, const char *);
+int fsunshare(const char *, const char *);
+
+#endif /* !ZFS_SYS_VFS_H_ */
diff --git a/lib/libspl/include/os/freebsd/sys/zfs_context_os.h b/lib/libspl/include/os/freebsd/sys/zfs_context_os.h
new file mode 100644
index 000000000000..f5a136d22125
--- /dev/null
+++ b/lib/libspl/include/os/freebsd/sys/zfs_context_os.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2020 iXsystems, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef ZFS_CONTEXT_OS_H_
+#define ZFS_CONTEXT_OS_H_
+
+#define ZFS_EXPORTS_PATH "/etc/zfs/exports"
+
+#endif
diff --git a/lib/libspl/include/os/linux/Makefile.am b/lib/libspl/include/os/linux/Makefile.am
new file mode 100644
index 000000000000..081839c48c8f
--- /dev/null
+++ b/lib/libspl/include/os/linux/Makefile.am
@@ -0,0 +1 @@
+SUBDIRS = sys
diff --git a/lib/libspl/include/os/linux/sys/Makefile.am b/lib/libspl/include/os/linux/sys/Makefile.am
new file mode 100644
index 000000000000..1ec07a76d354
--- /dev/null
+++ b/lib/libspl/include/os/linux/sys/Makefile.am
@@ -0,0 +1,10 @@
+libspldir = $(includedir)/libspl/sys
+libspl_HEADERS = \
+ byteorder.h \
+ errno.h \
+ mnttab.h \
+ mount.h \
+ param.h \
+ stat.h \
+ sysmacros.h \
+ zfs_context_os.h
diff --git a/lib/libspl/include/os/linux/sys/byteorder.h b/lib/libspl/include/os/linux/sys/byteorder.h
new file mode 100644
index 000000000000..d5ee3e26f5a5
--- /dev/null
+++ b/lib/libspl/include/os/linux/sys/byteorder.h
@@ -0,0 +1,223 @@
+/*
+ * 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 2007 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+/* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */
+/* All Rights Reserved */
+
+/*
+ * University Copyright- Copyright (c) 1982, 1986, 1988
+ * The Regents of the University of California
+ * All Rights Reserved
+ *
+ * University Acknowledgment- Portions of this document are derived from
+ * software developed by the University of California, Berkeley, and its
+ * contributors.
+ */
+
+#ifndef _SYS_BYTEORDER_H
+#define _SYS_BYTEORDER_H
+
+#if defined(__GNUC__) && defined(_ASM_INLINES) && \
+ (defined(__i386) || defined(__amd64))
+#include <asm/byteorder.h>
+#endif
+
+#include <sys/isa_defs.h>
+#include <sys/int_types.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * macros for conversion between host and (internet) network byte order
+ */
+
+#if defined(_ZFS_BIG_ENDIAN) && !defined(ntohl) && !defined(__lint)
+/* big-endian */
+#define ntohl(x) (x)
+#define ntohs(x) (x)
+#define htonl(x) (x)
+#define htons(x) (x)
+
+#elif !defined(ntohl) /* little-endian */
+
+#ifndef _IN_PORT_T
+#define _IN_PORT_T
+typedef uint16_t in_port_t;
+#endif
+
+#ifndef _IN_ADDR_T
+#define _IN_ADDR_T
+typedef uint32_t in_addr_t;
+#endif
+
+#if !defined(_XPG4_2) || defined(__EXTENSIONS__) || defined(_XPG5)
+extern uint32_t htonl(uint32_t);
+extern uint16_t htons(uint16_t);
+extern uint32_t ntohl(uint32_t);
+extern uint16_t ntohs(uint16_t);
+#else
+extern in_addr_t htonl(in_addr_t);
+extern in_port_t htons(in_port_t);
+extern in_addr_t ntohl(in_addr_t);
+extern in_port_t ntohs(in_port_t);
+#endif /* !defined(_XPG4_2) || defined(__EXTENSIONS__) || defined(_XPG5) */
+#endif
+
+#if !defined(_XPG4_2) || defined(__EXTENSIONS__)
+
+/*
+ * Macros to reverse byte order
+ */
+#define BSWAP_8(x) ((x) & 0xff)
+#define BSWAP_16(x) ((BSWAP_8(x) << 8) | BSWAP_8((x) >> 8))
+#define BSWAP_32(x) ((BSWAP_16(x) << 16) | BSWAP_16((x) >> 16))
+#define BSWAP_64(x) ((BSWAP_32(x) << 32) | BSWAP_32((x) >> 32))
+
+#define BMASK_8(x) ((x) & 0xff)
+#define BMASK_16(x) ((x) & 0xffff)
+#define BMASK_32(x) ((x) & 0xffffffff)
+#define BMASK_64(x) (x)
+
+/*
+ * Macros to convert from a specific byte order to/from native byte order
+ */
+#ifdef _ZFS_BIG_ENDIAN
+#define BE_8(x) BMASK_8(x)
+#define BE_16(x) BMASK_16(x)
+#define BE_32(x) BMASK_32(x)
+#define BE_64(x) BMASK_64(x)
+#define LE_8(x) BSWAP_8(x)
+#define LE_16(x) BSWAP_16(x)
+#define LE_32(x) BSWAP_32(x)
+#define LE_64(x) BSWAP_64(x)
+#else
+#define LE_8(x) BMASK_8(x)
+#define LE_16(x) BMASK_16(x)
+#define LE_32(x) BMASK_32(x)
+#define LE_64(x) BMASK_64(x)
+#define BE_8(x) BSWAP_8(x)
+#define BE_16(x) BSWAP_16(x)
+#define BE_32(x) BSWAP_32(x)
+#define BE_64(x) BSWAP_64(x)
+#endif
+
+#ifdef _ZFS_BIG_ENDIAN
+static __inline__ uint64_t
+htonll(uint64_t n)
+{
+ return (n);
+}
+
+static __inline__ uint64_t
+ntohll(uint64_t n)
+{
+ return (n);
+}
+#else
+static __inline__ uint64_t
+htonll(uint64_t n)
+{
+ return ((((uint64_t)htonl(n)) << 32) + htonl(n >> 32));
+}
+
+static __inline__ uint64_t
+ntohll(uint64_t n)
+{
+ return ((((uint64_t)ntohl(n)) << 32) + ntohl(n >> 32));
+}
+#endif
+
+/*
+ * Macros to read unaligned values from a specific byte order to
+ * native byte order
+ */
+
+#define BE_IN8(xa) \
+ *((uint8_t *)(xa))
+
+#define BE_IN16(xa) \
+ (((uint16_t)BE_IN8(xa) << 8) | BE_IN8((uint8_t *)(xa)+1))
+
+#define BE_IN32(xa) \
+ (((uint32_t)BE_IN16(xa) << 16) | BE_IN16((uint8_t *)(xa)+2))
+
+#define BE_IN64(xa) \
+ (((uint64_t)BE_IN32(xa) << 32) | BE_IN32((uint8_t *)(xa)+4))
+
+#define LE_IN8(xa) \
+ *((uint8_t *)(xa))
+
+#define LE_IN16(xa) \
+ (((uint16_t)LE_IN8((uint8_t *)(xa) + 1) << 8) | LE_IN8(xa))
+
+#define LE_IN32(xa) \
+ (((uint32_t)LE_IN16((uint8_t *)(xa) + 2) << 16) | LE_IN16(xa))
+
+#define LE_IN64(xa) \
+ (((uint64_t)LE_IN32((uint8_t *)(xa) + 4) << 32) | LE_IN32(xa))
+
+/*
+ * Macros to write unaligned values from native byte order to a specific byte
+ * order.
+ */
+
+#define BE_OUT8(xa, yv) *((uint8_t *)(xa)) = (uint8_t)(yv);
+
+#define BE_OUT16(xa, yv) \
+ BE_OUT8((uint8_t *)(xa) + 1, yv); \
+ BE_OUT8((uint8_t *)(xa), (yv) >> 8);
+
+#define BE_OUT32(xa, yv) \
+ BE_OUT16((uint8_t *)(xa) + 2, yv); \
+ BE_OUT16((uint8_t *)(xa), (yv) >> 16);
+
+#define BE_OUT64(xa, yv) \
+ BE_OUT32((uint8_t *)(xa) + 4, yv); \
+ BE_OUT32((uint8_t *)(xa), (yv) >> 32);
+
+#define LE_OUT8(xa, yv) *((uint8_t *)(xa)) = (uint8_t)(yv);
+
+#define LE_OUT16(xa, yv) \
+ LE_OUT8((uint8_t *)(xa), yv); \
+ LE_OUT8((uint8_t *)(xa) + 1, (yv) >> 8);
+
+#define LE_OUT32(xa, yv) \
+ LE_OUT16((uint8_t *)(xa), yv); \
+ LE_OUT16((uint8_t *)(xa) + 2, (yv) >> 16);
+
+#define LE_OUT64(xa, yv) \
+ LE_OUT32((uint8_t *)(xa), yv); \
+ LE_OUT32((uint8_t *)(xa) + 4, (yv) >> 32);
+
+#endif /* !defined(_XPG4_2) || defined(__EXTENSIONS__) */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _SYS_BYTEORDER_H */
diff --git a/lib/libspl/include/os/linux/sys/errno.h b/lib/libspl/include/os/linux/sys/errno.h
new file mode 100644
index 000000000000..30d20ab895c5
--- /dev/null
+++ b/lib/libspl/include/os/linux/sys/errno.h
@@ -0,0 +1,46 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2017 Zettabyte Software, LLC. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+/*
+ * Compiling against musl correctly points out that including sys/errno.h is
+ * disallowed by the Single UNIX Specification when building in userspace, so
+ * we implement a dummy header to redirect the include to the proper header.
+ */
+#ifndef _LIBSPL_SYS_ERRNO_H
+#define _LIBSPL_SYS_ERRNO_H
+
+#include <errno.h>
+/*
+ * We'll take the unused errnos, 'EBADE' and 'EBADR' (from the Convergent
+ * graveyard) to indicate checksum errors and fragmentation.
+ */
+#define ECKSUM EBADE
+#define EFRAGS EBADR
+
+/* Similar for ENOACTIVE */
+#define ENOTACTIVE ENOANO
+
+#endif /* _LIBSPL_SYS_ERRNO_H */
diff --git a/lib/libspl/include/os/linux/sys/mnttab.h b/lib/libspl/include/os/linux/sys/mnttab.h
new file mode 100644
index 000000000000..1957293d5c69
--- /dev/null
+++ b/lib/libspl/include/os/linux/sys/mnttab.h
@@ -0,0 +1,89 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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) 1984, 1986, 1987, 1988, 1989 AT&T */
+/* All Rights Reserved */
+/*
+ * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+/* Copyright 2006 Ricardo Correia */
+
+#ifndef _SYS_MNTTAB_H
+#define _SYS_MNTTAB_H
+
+#include <stdio.h>
+#include <mntent.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#ifdef MNTTAB
+#undef MNTTAB
+#endif /* MNTTAB */
+
+#define MNTTAB "/proc/self/mounts"
+#define MNT_LINE_MAX 4108
+
+#define MNT_TOOLONG 1 /* entry exceeds MNT_LINE_MAX */
+#define MNT_TOOMANY 2 /* too many fields in line */
+#define MNT_TOOFEW 3 /* too few fields in line */
+
+struct mnttab {
+ char *mnt_special;
+ char *mnt_mountp;
+ char *mnt_fstype;
+ char *mnt_mntopts;
+};
+
+/*
+ * NOTE: fields in extmnttab should match struct mnttab till new fields
+ * are encountered, this allows hasmntopt to work properly when its arg is
+ * a pointer to an extmnttab struct cast to a mnttab struct pointer.
+ */
+
+struct extmnttab {
+ char *mnt_special;
+ char *mnt_mountp;
+ char *mnt_fstype;
+ char *mnt_mntopts;
+ uint_t mnt_major;
+ uint_t mnt_minor;
+};
+
+struct statfs;
+
+extern int getmntany(FILE *fp, struct mnttab *mp, struct mnttab *mpref);
+extern int _sol_getmntent(FILE *fp, struct mnttab *mp);
+extern int getextmntent(const char *path, struct extmnttab *mp,
+ struct stat64 *statbuf);
+static inline char *_sol_hasmntopt(struct mnttab *mnt, char *opt)
+{
+ struct mntent mnt_new;
+
+ mnt_new.mnt_opts = mnt->mnt_mntopts;
+
+ return (hasmntopt(&mnt_new, opt));
+}
+
+#define hasmntopt _sol_hasmntopt
+#define getmntent _sol_getmntent
+
+#endif
diff --git a/lib/libspl/include/os/linux/sys/mount.h b/lib/libspl/include/os/linux/sys/mount.h
new file mode 100644
index 000000000000..d7c6f750e23d
--- /dev/null
+++ b/lib/libspl/include/os/linux/sys/mount.h
@@ -0,0 +1,98 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2006 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#include_next <sys/mount.h>
+
+#ifndef _LIBSPL_SYS_MOUNT_H
+#define _LIBSPL_SYS_MOUNT_H
+
+#include <assert.h>
+#include <string.h>
+#include <stdlib.h>
+
+/*
+ * Some old glibc headers don't define BLKGETSIZE64
+ * and we don't want to require the kernel headers
+ */
+#if !defined(BLKGETSIZE64)
+#define BLKGETSIZE64 _IOR(0x12, 114, size_t)
+#endif
+
+/*
+ * Some old glibc headers don't correctly define MS_DIRSYNC and
+ * instead use the enum name S_WRITE. When using these older
+ * headers define MS_DIRSYNC to be S_WRITE.
+ */
+#if !defined(MS_DIRSYNC)
+#define MS_DIRSYNC S_WRITE
+#endif
+
+/*
+ * Some old glibc headers don't correctly define MS_POSIXACL and
+ * instead leave it undefined. When using these older headers define
+ * MS_POSIXACL to the reserved value of (1<<16).
+ */
+#if !defined(MS_POSIXACL)
+#define MS_POSIXACL (1<<16)
+#endif
+
+#define MS_USERS (MS_NOEXEC|MS_NOSUID|MS_NODEV)
+#define MS_OWNER (MS_NOSUID|MS_NODEV)
+#define MS_GROUP (MS_NOSUID|MS_NODEV)
+#define MS_COMMENT 0
+
+/*
+ * Older glibc <sys/mount.h> headers did not define all the available
+ * umount2(2) flags. Both MNT_FORCE and MNT_DETACH are supported in the
+ * kernel back to 2.4.11 so we define them correctly if they are missing.
+ */
+#ifdef MNT_FORCE
+#define MS_FORCE MNT_FORCE
+#else
+#define MS_FORCE 0x00000001
+#endif /* MNT_FORCE */
+
+#ifdef MNT_DETACH
+#define MS_DETACH MNT_DETACH
+#else
+#define MS_DETACH 0x00000002
+#endif /* MNT_DETACH */
+
+/*
+ * Overlay mount is default in Linux, but for solaris/zfs
+ * compatibility, MS_OVERLAY is defined to explicitly have the user
+ * provide a flag (-O) to mount over a non empty directory.
+ */
+#define MS_OVERLAY 0x00000004
+
+/*
+ * MS_CRYPT indicates that encryption keys should be loaded if they are not
+ * already available. This is not defined in glibc, but it is never seen by
+ * the kernel so it will not cause any problems.
+ */
+#define MS_CRYPT 0x00000008
+
+#endif /* _LIBSPL_SYS_MOUNT_H */
diff --git a/lib/libspl/include/os/linux/sys/param.h b/lib/libspl/include/os/linux/sys/param.h
new file mode 100644
index 000000000000..26335187fdca
--- /dev/null
+++ b/lib/libspl/include/os/linux/sys/param.h
@@ -0,0 +1,67 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2006 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _LIBSPL_SYS_PARAM_H
+#define _LIBSPL_SYS_PARAM_H
+
+#include_next <sys/param.h>
+#include <unistd.h>
+
+/*
+ * File system parameters and macros.
+ *
+ * The file system is made out of blocks of at most MAXBSIZE units,
+ * with smaller units (fragments) only in the last direct block.
+ * MAXBSIZE primarily determines the size of buffers in the buffer
+ * pool. It may be made larger without any effect on existing
+ * file systems; however making it smaller may make some file
+ * systems unmountable.
+ *
+ * Note that the blocked devices are assumed to have DEV_BSIZE
+ * "sectors" and that fragments must be some multiple of this size.
+ */
+#define MAXBSIZE 8192
+#define DEV_BSIZE 512
+#define DEV_BSHIFT 9 /* log2(DEV_BSIZE) */
+
+#define MAXNAMELEN 256
+#define MAXOFFSET_T LLONG_MAX
+
+#define UID_NOBODY 60001 /* user ID no body */
+#define GID_NOBODY UID_NOBODY
+#define UID_NOACCESS 60002 /* user ID no access */
+
+#define MAXUID UINT32_MAX /* max user id */
+#define MAXPROJID MAXUID /* max project id */
+
+#ifdef PAGESIZE
+#undef PAGESIZE
+#endif /* PAGESIZE */
+
+extern size_t spl_pagesize(void);
+#define PAGESIZE (spl_pagesize())
+
+#endif
diff --git a/lib/libspl/include/os/linux/sys/stat.h b/lib/libspl/include/os/linux/sys/stat.h
new file mode 100644
index 000000000000..3e8d27e4c19a
--- /dev/null
+++ b/lib/libspl/include/os/linux/sys/stat.h
@@ -0,0 +1,50 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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) 2010, Oracle and/or its affiliates. All rights reserved.
+ */
+
+#ifndef _LIBSPL_SYS_STAT_H
+#define _LIBSPL_SYS_STAT_H
+
+#include_next <sys/stat.h>
+
+#include <sys/mount.h> /* for BLKGETSIZE64 */
+
+/*
+ * Emulate Solaris' behavior of returning the block device size in fstat64().
+ */
+static inline int
+fstat64_blk(int fd, struct stat64 *st)
+{
+ if (fstat64(fd, st) == -1)
+ return (-1);
+
+ /* In Linux we need to use an ioctl to get the size of a block device */
+ if (S_ISBLK(st->st_mode)) {
+ if (ioctl(fd, BLKGETSIZE64, &st->st_size) != 0)
+ return (-1);
+ }
+
+ return (0);
+}
+#endif /* _LIBSPL_SYS_STAT_H */
diff --git a/lib/libspl/include/os/linux/sys/sysmacros.h b/lib/libspl/include/os/linux/sys/sysmacros.h
new file mode 100644
index 000000000000..22fcb04b94e0
--- /dev/null
+++ b/lib/libspl/include/os/linux/sys/sysmacros.h
@@ -0,0 +1,103 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2007 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _LIBSPL_SYS_SYSMACROS_H
+#define _LIBSPL_SYS_SYSMACROS_H
+
+#include_next <sys/sysmacros.h>
+
+/* common macros */
+#ifndef MIN
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+#endif
+#ifndef MAX
+#define MAX(a, b) ((a) < (b) ? (b) : (a))
+#endif
+#ifndef ABS
+#define ABS(a) ((a) < 0 ? -(a) : (a))
+#endif
+#ifndef ARRAY_SIZE
+#define ARRAY_SIZE(a) (sizeof (a) / sizeof (a[0]))
+#endif
+#ifndef DIV_ROUND_UP
+#define DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d))
+#endif
+
+#define makedevice(maj, min) makedev(maj, min)
+#define _sysconf(a) sysconf(a)
+
+/*
+ * Compatibility macros/typedefs needed for Solaris -> Linux port
+ */
+#define P2ALIGN(x, align) ((x) & -(align))
+#define P2CROSS(x, y, align) (((x) ^ (y)) > (align) - 1)
+#define P2ROUNDUP(x, align) ((((x) - 1) | ((align) - 1)) + 1)
+#define P2BOUNDARY(off, len, align) \
+ (((off) ^ ((off) + (len) - 1)) > (align) - 1)
+#define P2PHASE(x, align) ((x) & ((align) - 1))
+#define P2NPHASE(x, align) (-(x) & ((align) - 1))
+#define P2NPHASE_TYPED(x, align, type) \
+ (-(type)(x) & ((type)(align) - 1))
+#define ISP2(x) (((x) & ((x) - 1)) == 0)
+#define IS_P2ALIGNED(v, a) ((((uintptr_t)(v)) & ((uintptr_t)(a) - 1)) == 0)
+
+/*
+ * Typed version of the P2* macros. These macros should be used to ensure
+ * that the result is correctly calculated based on the data type of (x),
+ * which is passed in as the last argument, regardless of the data
+ * type of the alignment. For example, if (x) is of type uint64_t,
+ * and we want to round it up to a page boundary using "PAGESIZE" as
+ * the alignment, we can do either
+ * P2ROUNDUP(x, (uint64_t)PAGESIZE)
+ * or
+ * P2ROUNDUP_TYPED(x, PAGESIZE, uint64_t)
+ */
+#define P2ALIGN_TYPED(x, align, type) \
+ ((type)(x) & -(type)(align))
+#define P2PHASE_TYPED(x, align, type) \
+ ((type)(x) & ((type)(align) - 1))
+#define P2NPHASE_TYPED(x, align, type) \
+ (-(type)(x) & ((type)(align) - 1))
+#define P2ROUNDUP_TYPED(x, align, type) \
+ ((((type)(x) - 1) | ((type)(align) - 1)) + 1)
+#define P2END_TYPED(x, align, type) \
+ (-(~(type)(x) & -(type)(align)))
+#define P2PHASEUP_TYPED(x, align, phase, type) \
+ ((type)(phase) - (((type)(phase) - (type)(x)) & -(type)(align)))
+#define P2CROSS_TYPED(x, y, align, type) \
+ (((type)(x) ^ (type)(y)) > (type)(align) - 1)
+#define P2SAMEHIGHBIT_TYPED(x, y, type) \
+ (((type)(x) ^ (type)(y)) < ((type)(x) & (type)(y)))
+
+
+/* avoid any possibility of clashing with <stddef.h> version */
+#if defined(_KERNEL) && !defined(_KMEMUSER) && !defined(offsetof)
+#define offsetof(s, m) ((size_t)(&(((s *)0)->m)))
+#endif
+
+#define _NOTE(x)
+
+#endif /* _LIBSPL_SYS_SYSMACROS_H */
diff --git a/lib/libspl/include/os/linux/sys/zfs_context_os.h b/lib/libspl/include/os/linux/sys/zfs_context_os.h
new file mode 100644
index 000000000000..008e57df4eae
--- /dev/null
+++ b/lib/libspl/include/os/linux/sys/zfs_context_os.h
@@ -0,0 +1,25 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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
+ */
+
+#ifndef ZFS_CONTEXT_OS_H
+#define ZFS_CONTEXT_OS_H
+#endif
diff --git a/lib/libspl/include/rpc/Makefile.am b/lib/libspl/include/rpc/Makefile.am
new file mode 100644
index 000000000000..7fe1d7fea4d7
--- /dev/null
+++ b/lib/libspl/include/rpc/Makefile.am
@@ -0,0 +1,3 @@
+libspldir = $(includedir)/libspl/rpc
+libspl_HEADERS = \
+ xdr.h
diff --git a/lib/libspl/include/rpc/xdr.h b/lib/libspl/include/rpc/xdr.h
new file mode 100644
index 000000000000..51d71f693bbf
--- /dev/null
+++ b/lib/libspl/include/rpc/xdr.h
@@ -0,0 +1,68 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2005 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ *
+ * Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T
+ * All Rights Reserved
+ *
+ * Portions of this source code were derived from Berkeley 4.3 BSD
+ * under license from the Regents of the University of California.
+ */
+
+#ifndef LIBSPL_RPC_XDR_H
+#define LIBSPL_RPC_XDR_H
+
+#include_next <rpc/xdr.h>
+
+#ifdef xdr_control /* if e.g. using tirpc */
+#undef xdr_control
+#endif
+
+#define XDR_GET_BYTES_AVAIL 1
+
+#ifndef HAVE_XDR_BYTESREC
+struct xdr_bytesrec {
+ bool_t xc_is_last_record;
+ size_t xc_num_avail;
+};
+#endif
+typedef struct xdr_bytesrec xdr_bytesrec_t;
+
+/*
+ * This functionality is not required and is disabled in user space.
+ */
+static inline bool_t
+xdr_control(XDR *xdrs, int request, void *info)
+{
+ xdr_bytesrec_t *xptr;
+
+ ASSERT3U(request, ==, XDR_GET_BYTES_AVAIL);
+
+ xptr = (xdr_bytesrec_t *)info;
+ xptr->xc_is_last_record = TRUE;
+ xptr->xc_num_avail = xdrs->x_handy;
+
+ return (TRUE);
+}
+
+#endif /* LIBSPL_RPC_XDR_H */
diff --git a/lib/libspl/include/statcommon.h b/lib/libspl/include/statcommon.h
new file mode 100644
index 000000000000..1f376f5c7c24
--- /dev/null
+++ b/lib/libspl/include/statcommon.h
@@ -0,0 +1,41 @@
+/*
+ * 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 2009 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ *
+ * Common routines for acquiring snapshots of kstats for
+ * iostat, mpstat, and vmstat.
+ */
+
+#ifndef _STATCOMMON_H
+#define _STATCOMMON_H
+
+#include <sys/types.h>
+
+#define NODATE 0 /* Default: No time stamp */
+#define DDATE 1 /* Standard date format */
+#define UDATE 2 /* Internal representation of Unix time */
+
+/* Print a timestamp in either Unix or standard format. */
+void print_timestamp(uint_t);
+
+#endif /* _STATCOMMON_H */
diff --git a/lib/libspl/include/stdio.h b/lib/libspl/include/stdio.h
new file mode 100644
index 000000000000..6152b09f1a97
--- /dev/null
+++ b/lib/libspl/include/stdio.h
@@ -0,0 +1,34 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#include_next <stdio.h>
+
+#ifndef _LIBSPL_STDIO_H
+#define _LIBSPL_STDIO_H
+
+#define enable_extended_FILE_stdio(fd, sig) ((void) 0)
+
+#endif
diff --git a/lib/libspl/include/stdlib.h b/lib/libspl/include/stdlib.h
new file mode 100644
index 000000000000..a4ce4f781fc5
--- /dev/null
+++ b/lib/libspl/include/stdlib.h
@@ -0,0 +1,34 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2007 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#include_next <stdlib.h>
+
+#ifndef _LIBSPL_STDLIB_H
+#define _LIBSPL_STDLIB_H
+
+extern const char *getexecname(void);
+
+#endif
diff --git a/lib/libspl/include/string.h b/lib/libspl/include/string.h
new file mode 100644
index 000000000000..a7d40fa61943
--- /dev/null
+++ b/lib/libspl/include/string.h
@@ -0,0 +1,40 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2006 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _LIBSPL_STRING_H
+#define _LIBSPL_STRING_H
+
+#include_next <string.h>
+
+#ifndef HAVE_STRLCAT
+extern size_t strlcat(char *dst, const char *src, size_t dstsize);
+#endif
+
+#ifndef HAVE_STRLCPY
+extern size_t strlcpy(char *dst, const char *src, size_t len);
+#endif
+
+#endif
diff --git a/lib/libspl/include/stropts.h b/lib/libspl/include/stropts.h
new file mode 100644
index 000000000000..37acd4052b0b
--- /dev/null
+++ b/lib/libspl/include/stropts.h
@@ -0,0 +1,25 @@
+/*
+ * 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
+ */
+
+#ifndef _LIBSPL_STROPTS_H
+#define _LIBSPL_STROPTS_H
+
+#endif /* _LIBSPL_STROPTS_H */
diff --git a/lib/libspl/include/sys/Makefile.am b/lib/libspl/include/sys/Makefile.am
new file mode 100644
index 000000000000..53a36f6bfbca
--- /dev/null
+++ b/lib/libspl/include/sys/Makefile.am
@@ -0,0 +1,47 @@
+SUBDIRS = dktp
+
+libspldir = $(includedir)/libspl/sys
+libspl_HEADERS = \
+ acl.h \
+ acl_impl.h \
+ callb.h \
+ cmn_err.h \
+ cred.h \
+ debug.h \
+ dkio.h \
+ dklabel.h \
+ feature_tests.h \
+ int_limits.h \
+ int_types.h \
+ inttypes.h \
+ isa_defs.h \
+ kmem.h \
+ kstat.h \
+ list.h \
+ list_impl.h \
+ mhd.h \
+ mkdev.h \
+ policy.h \
+ poll.h \
+ priv.h \
+ processor.h \
+ sha2.h \
+ simd.h \
+ stack.h \
+ stdtypes.h \
+ strings.h \
+ stropts.h \
+ sunddi.h \
+ systeminfo.h \
+ time.h \
+ trace_spl.h \
+ trace_zfs.h \
+ types32.h \
+ types.h \
+ tzfile.h \
+ uio.h \
+ va_list.h \
+ varargs.h \
+ vnode.h \
+ vtoc.h \
+ zone.h
diff --git a/lib/libspl/include/sys/acl.h b/lib/libspl/include/sys/acl.h
new file mode 100644
index 000000000000..e6df864f850f
--- /dev/null
+++ b/lib/libspl/include/sys/acl.h
@@ -0,0 +1,287 @@
+/*
+ * 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 2007 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _SYS_ACL_H
+#define _SYS_ACL_H
+
+#include <sys/types.h>
+#include <sys/acl_impl.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define MAX_ACL_ENTRIES (1024) /* max entries of each type */
+typedef struct acl {
+ int a_type; /* the type of ACL entry */
+ uid_t a_id; /* the entry in -uid or gid */
+ o_mode_t a_perm; /* the permission field */
+} aclent_t;
+
+typedef struct ace {
+ uid_t a_who; /* uid or gid */
+ uint32_t a_access_mask; /* read,write,... */
+ uint16_t a_flags; /* see below */
+ uint16_t a_type; /* allow or deny */
+} ace_t;
+
+typedef struct acl_info acl_t;
+
+/*
+ * The following are Defined types for an aclent_t.
+ */
+#define USER_OBJ (0x01) /* object owner */
+#define USER (0x02) /* additional users */
+#define GROUP_OBJ (0x04) /* owning group of the object */
+#define GROUP (0x08) /* additional groups */
+#define CLASS_OBJ (0x10) /* file group class and mask entry */
+#define OTHER_OBJ (0x20) /* other entry for the object */
+#define ACL_DEFAULT (0x1000) /* default flag */
+/* default object owner */
+#define DEF_USER_OBJ (ACL_DEFAULT | USER_OBJ)
+/* default additional users */
+#define DEF_USER (ACL_DEFAULT | USER)
+/* default owning group */
+#define DEF_GROUP_OBJ (ACL_DEFAULT | GROUP_OBJ)
+/* default additional groups */
+#define DEF_GROUP (ACL_DEFAULT | GROUP)
+/* default mask entry */
+#define DEF_CLASS_OBJ (ACL_DEFAULT | CLASS_OBJ)
+/* default other entry */
+#define DEF_OTHER_OBJ (ACL_DEFAULT | OTHER_OBJ)
+
+/*
+ * The following are defined for ace_t.
+ */
+#define ACE_READ_DATA 0x00000001
+#define ACE_LIST_DIRECTORY 0x00000001
+#define ACE_WRITE_DATA 0x00000002
+#define ACE_ADD_FILE 0x00000002
+#define ACE_APPEND_DATA 0x00000004
+#define ACE_ADD_SUBDIRECTORY 0x00000004
+#define ACE_READ_NAMED_ATTRS 0x00000008
+#define ACE_WRITE_NAMED_ATTRS 0x00000010
+#define ACE_EXECUTE 0x00000020
+#define ACE_DELETE_CHILD 0x00000040
+#define ACE_READ_ATTRIBUTES 0x00000080
+#define ACE_WRITE_ATTRIBUTES 0x00000100
+#define ACE_DELETE 0x00010000
+#define ACE_READ_ACL 0x00020000
+#define ACE_WRITE_ACL 0x00040000
+#define ACE_WRITE_OWNER 0x00080000
+#define ACE_SYNCHRONIZE 0x00100000
+
+#define ACE_FILE_INHERIT_ACE 0x0001
+#define ACE_DIRECTORY_INHERIT_ACE 0x0002
+#define ACE_NO_PROPAGATE_INHERIT_ACE 0x0004
+#define ACE_INHERIT_ONLY_ACE 0x0008
+#define ACE_SUCCESSFUL_ACCESS_ACE_FLAG 0x0010
+#define ACE_FAILED_ACCESS_ACE_FLAG 0x0020
+#define ACE_IDENTIFIER_GROUP 0x0040
+#define ACE_INHERITED_ACE 0x0080
+#define ACE_OWNER 0x1000
+#define ACE_GROUP 0x2000
+#define ACE_EVERYONE 0x4000
+
+#define ACE_ACCESS_ALLOWED_ACE_TYPE 0x0000
+#define ACE_ACCESS_DENIED_ACE_TYPE 0x0001
+#define ACE_SYSTEM_AUDIT_ACE_TYPE 0x0002
+#define ACE_SYSTEM_ALARM_ACE_TYPE 0x0003
+
+#define ACL_AUTO_INHERIT 0x0001
+#define ACL_PROTECTED 0x0002
+#define ACL_DEFAULTED 0x0004
+#define ACL_FLAGS_ALL (ACL_AUTO_INHERIT|ACL_PROTECTED| \
+ ACL_DEFAULTED)
+
+#ifdef _KERNEL
+
+/*
+ * These are only applicable in a CIFS context.
+ */
+#define ACE_ACCESS_ALLOWED_COMPOUND_ACE_TYPE 0x04
+#define ACE_ACCESS_ALLOWED_OBJECT_ACE_TYPE 0x05
+#define ACE_ACCESS_DENIED_OBJECT_ACE_TYPE 0x06
+#define ACE_SYSTEM_AUDIT_OBJECT_ACE_TYPE 0x07
+#define ACE_SYSTEM_ALARM_OBJECT_ACE_TYPE 0x08
+#define ACE_ACCESS_ALLOWED_CALLBACK_ACE_TYPE 0x09
+#define ACE_ACCESS_DENIED_CALLBACK_ACE_TYPE 0x0A
+#define ACE_ACCESS_ALLOWED_CALLBACK_OBJECT_ACE_TYPE 0x0B
+#define ACE_ACCESS_DENIED_CALLBACK_OBJECT_ACE_TYPE 0x0C
+#define ACE_SYSTEM_AUDIT_CALLBACK_ACE_TYPE 0x0D
+#define ACE_SYSTEM_ALARM_CALLBACK_ACE_TYPE 0x0E
+#define ACE_SYSTEM_AUDIT_CALLBACK_OBJECT_ACE_TYPE 0x0F
+#define ACE_SYSTEM_ALARM_CALLBACK_OBJECT_ACE_TYPE 0x10
+
+#define ACE_ALL_TYPES 0x001F
+
+typedef struct ace_object {
+ uid_t a_who; /* uid or gid */
+ uint32_t a_access_mask; /* read,write,... */
+ uint16_t a_flags; /* see below */
+ uint16_t a_type; /* allow or deny */
+ uint8_t a_obj_type[16]; /* obj type */
+ uint8_t a_inherit_obj_type[16]; /* inherit obj */
+} ace_object_t;
+
+#endif
+
+#define ACE_ALL_PERMS (ACE_READ_DATA|ACE_LIST_DIRECTORY|ACE_WRITE_DATA| \
+ ACE_ADD_FILE|ACE_APPEND_DATA|ACE_ADD_SUBDIRECTORY|ACE_READ_NAMED_ATTRS| \
+ ACE_WRITE_NAMED_ATTRS|ACE_EXECUTE|ACE_DELETE_CHILD|ACE_READ_ATTRIBUTES| \
+ ACE_WRITE_ATTRIBUTES|ACE_DELETE|ACE_READ_ACL|ACE_WRITE_ACL| \
+ ACE_WRITE_OWNER|ACE_SYNCHRONIZE)
+
+/*
+ * The following flags are supported by both NFSv4 ACLs and ace_t.
+ */
+#define ACE_NFSV4_SUP_FLAGS (ACE_FILE_INHERIT_ACE | \
+ ACE_DIRECTORY_INHERIT_ACE | \
+ ACE_NO_PROPAGATE_INHERIT_ACE | \
+ ACE_INHERIT_ONLY_ACE | \
+ ACE_IDENTIFIER_GROUP)
+
+#define ACE_TYPE_FLAGS (ACE_OWNER|ACE_GROUP|ACE_EVERYONE| \
+ ACE_IDENTIFIER_GROUP)
+#define ACE_INHERIT_FLAGS (ACE_FILE_INHERIT_ACE| \
+ ACE_DIRECTORY_INHERIT_ACE|ACE_NO_PROPAGATE_INHERIT_ACE|ACE_INHERIT_ONLY_ACE)
+
+/* cmd args to acl(2) for aclent_t */
+#define GETACL 1
+#define SETACL 2
+#define GETACLCNT 3
+
+/* cmd's to manipulate ace acls. */
+#define ACE_GETACL 4
+#define ACE_SETACL 5
+#define ACE_GETACLCNT 6
+
+/* minimal acl entries from GETACLCNT */
+#define MIN_ACL_ENTRIES 4
+
+#if !defined(_KERNEL)
+
+/* acl check errors */
+#define GRP_ERROR 1
+#define USER_ERROR 2
+#define OTHER_ERROR 3
+#define CLASS_ERROR 4
+#define DUPLICATE_ERROR 5
+#define MISS_ERROR 6
+#define MEM_ERROR 7
+#define ENTRY_ERROR 8
+
+
+/*
+ * similar to ufs_acl.h: changed to char type for user commands (tar, cpio)
+ * Attribute types
+ */
+#define UFSD_FREE ('0') /* Free entry */
+#define UFSD_ACL ('1') /* Access Control Lists */
+#define UFSD_DFACL ('2') /* reserved for future use */
+#define ACE_ACL ('3') /* ace_t style acls */
+
+/*
+ * flag to [f]acl_get()
+ * controls whether a trivial acl should be returned.
+ */
+#define ACL_NO_TRIVIAL 0x2
+
+
+/*
+ * Flags to control acl_totext()
+ */
+
+#define ACL_APPEND_ID 0x1 /* append uid/gid to user/group entries */
+#define ACL_COMPACT_FMT 0x2 /* build ACL in ls -V format */
+#define ACL_NORESOLVE 0x4 /* don't do name service lookups */
+
+/*
+ * Legacy aclcheck errors for aclent_t ACLs
+ */
+#define EACL_GRP_ERROR GRP_ERROR
+#define EACL_USER_ERROR USER_ERROR
+#define EACL_OTHER_ERROR OTHER_ERROR
+#define EACL_CLASS_ERROR CLASS_ERROR
+#define EACL_DUPLICATE_ERROR DUPLICATE_ERROR
+#define EACL_MISS_ERROR MISS_ERROR
+#define EACL_MEM_ERROR MEM_ERROR
+#define EACL_ENTRY_ERROR ENTRY_ERROR
+
+#define EACL_INHERIT_ERROR 9 /* invalid inherit flags */
+#define EACL_FLAGS_ERROR 10 /* unknown flag value */
+#define EACL_PERM_MASK_ERROR 11 /* unknown permission */
+#define EACL_COUNT_ERROR 12 /* invalid acl count */
+
+#define EACL_INVALID_SLOT 13 /* invalid acl slot */
+#define EACL_NO_ACL_ENTRY 14 /* Entry doesn't exist */
+#define EACL_DIFF_TYPE 15 /* acls aren't same type */
+
+#define EACL_INVALID_USER_GROUP 16 /* need user/group name */
+#define EACL_INVALID_STR 17 /* invalid acl string */
+#define EACL_FIELD_NOT_BLANK 18 /* can't have blank field */
+#define EACL_INVALID_ACCESS_TYPE 19 /* invalid access type */
+#define EACL_UNKNOWN_DATA 20 /* Unrecognized data in ACL */
+#define EACL_MISSING_FIELDS 21 /* missing fields in acl */
+
+#define EACL_INHERIT_NOTDIR 22 /* Need dir for inheritance */
+
+extern int aclcheck(aclent_t *, int, int *);
+extern int acltomode(aclent_t *, int, mode_t *);
+extern int aclfrommode(aclent_t *, int, mode_t *);
+extern int aclsort(int, int, aclent_t *);
+extern char *acltotext(aclent_t *, int);
+extern aclent_t *aclfromtext(char *, int *);
+extern void acl_free(acl_t *);
+extern int acl_get(const char *, int, acl_t **);
+extern int facl_get(int, int, acl_t **);
+extern int acl_set(const char *, acl_t *acl);
+extern int facl_set(int, acl_t *acl);
+extern int acl_strip(const char *, uid_t, gid_t, mode_t);
+extern int acl_trivial(const char *);
+extern char *acl_totext(acl_t *, int);
+extern int acl_fromtext(const char *, acl_t **);
+extern int acl_check(acl_t *, int);
+
+#else /* !defined(_KERNEL) */
+
+extern void ksort(caddr_t, int, int, int (*)(void *, void *));
+extern int cmp2acls(void *, void *);
+
+#endif /* !defined(_KERNEL) */
+
+#if defined(__STDC__)
+extern int acl(const char *path, int cmd, int cnt, void *buf);
+extern int facl(int fd, int cmd, int cnt, void *buf);
+#else /* !__STDC__ */
+extern int acl();
+extern int facl();
+#endif /* defined(__STDC__) */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _SYS_ACL_H */
diff --git a/lib/libspl/include/sys/acl_impl.h b/lib/libspl/include/sys/acl_impl.h
new file mode 100644
index 000000000000..717334906415
--- /dev/null
+++ b/lib/libspl/include/sys/acl_impl.h
@@ -0,0 +1,59 @@
+/*
+ * 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 2007 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _SYS_ACL_IMPL_H
+#define _SYS_ACL_IMPL_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * acl flags
+ *
+ * ACL_AUTO_INHERIT, ACL_PROTECTED and ACL_DEFAULTED
+ * flags can also be stored in this field.
+ */
+#define ACL_IS_TRIVIAL 0x10000
+#define ACL_IS_DIR 0x20000
+
+typedef enum acl_type {
+ ACLENT_T = 0,
+ ACE_T = 1
+} acl_type_t;
+
+struct acl_info {
+ acl_type_t acl_type; /* style of acl */
+ int acl_cnt; /* number of acl entries */
+ int acl_entry_size; /* sizeof acl entry */
+ int acl_flags; /* special flags about acl */
+ void *acl_aclp; /* the acl */
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _SYS_ACL_IMPL_H */
diff --git a/lib/libspl/include/sys/callb.h b/lib/libspl/include/sys/callb.h
new file mode 100644
index 000000000000..8ffd18788865
--- /dev/null
+++ b/lib/libspl/include/sys/callb.h
@@ -0,0 +1,30 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _SYS_CALLB_H
+#define _SYS_CALLB_H
+
+#endif
diff --git a/lib/libspl/include/sys/cmn_err.h b/lib/libspl/include/sys/cmn_err.h
new file mode 100644
index 000000000000..63ff4eb29bc8
--- /dev/null
+++ b/lib/libspl/include/sys/cmn_err.h
@@ -0,0 +1,30 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2006 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _LIBSPL_SYS_CMN_ERR_H
+#define _LIBSPL_SYS_CMN_ERR_H
+
+#endif
diff --git a/lib/libspl/include/sys/cred.h b/lib/libspl/include/sys/cred.h
new file mode 100644
index 000000000000..463b3abfc977
--- /dev/null
+++ b/lib/libspl/include/sys/cred.h
@@ -0,0 +1,32 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2006 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _LIBSPL_SYS_CRED_H
+#define _LIBSPL_SYS_CRED_H
+
+typedef struct cred cred_t;
+
+#endif
diff --git a/lib/libspl/include/sys/debug.h b/lib/libspl/include/sys/debug.h
new file mode 100644
index 000000000000..af18da94804c
--- /dev/null
+++ b/lib/libspl/include/sys/debug.h
@@ -0,0 +1,40 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _LIBSPL_SYS_DEBUG_H
+#define _LIBSPL_SYS_DEBUG_H
+
+#include <assert.h>
+
+#ifndef __printflike
+#define __printflike(x, y) __attribute__((__format__(__printf__, x, y)))
+#endif
+
+#ifndef __maybe_unused
+#define __maybe_unused __attribute__((unused))
+#endif
+
+#endif
diff --git a/lib/libspl/include/sys/dkio.h b/lib/libspl/include/sys/dkio.h
new file mode 100644
index 000000000000..f3c641f669b7
--- /dev/null
+++ b/lib/libspl/include/sys/dkio.h
@@ -0,0 +1,483 @@
+/*
+ * 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 2007 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _SYS_DKIO_H
+#define _SYS_DKIO_H
+
+
+
+#include <sys/dklabel.h> /* Needed for NDKMAP define */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Structures and definitions for disk io control commands
+ */
+
+/*
+ * Structures used as data by ioctl calls.
+ */
+
+#define DK_DEVLEN 16 /* device name max length, including */
+ /* unit # & NULL (ie - "xyc1") */
+
+/*
+ * Used for controller info
+ */
+struct dk_cinfo {
+ char dki_cname[DK_DEVLEN]; /* controller name (no unit #) */
+ ushort_t dki_ctype; /* controller type */
+ ushort_t dki_flags; /* flags */
+ ushort_t dki_cnum; /* controller number */
+ uint_t dki_addr; /* controller address */
+ uint_t dki_space; /* controller bus type */
+ uint_t dki_prio; /* interrupt priority */
+ uint_t dki_vec; /* interrupt vector */
+ char dki_dname[DK_DEVLEN]; /* drive name (no unit #) */
+ uint_t dki_unit; /* unit number */
+ ushort_t dki_partition; /* partition number */
+ ushort_t dki_maxtransfer; /* max. transfer size in DEV_BSIZE */
+};
+
+/*
+ * Controller types
+ */
+#define DKC_UNKNOWN 0
+#define DKC_CDROM 1 /* CD-ROM, SCSI or otherwise */
+#define DKC_WDC2880 2
+#define DKC_XXX_0 3 /* unassigned */
+#define DKC_XXX_1 4 /* unassigned */
+#define DKC_DSD5215 5
+#define DKC_ACB4000 7
+#define DKC_MD21 8
+#define DKC_XXX_2 9 /* unassigned */
+#define DKC_NCRFLOPPY 10
+#define DKC_SMSFLOPPY 12
+#define DKC_SCSI_CCS 13 /* SCSI CCS compatible */
+#define DKC_INTEL82072 14 /* native floppy chip */
+#define DKC_MD 16 /* meta-disk (virtual-disk) driver */
+#define DKC_INTEL82077 19 /* 82077 floppy disk controller */
+#define DKC_DIRECT 20 /* Intel direct attached device i.e. IDE */
+#define DKC_PCMCIA_MEM 21 /* PCMCIA memory disk-like type */
+#define DKC_PCMCIA_ATA 22 /* PCMCIA AT Attached type */
+#define DKC_VBD 23 /* virtual block device */
+
+/*
+ * Sun reserves up through 1023
+ */
+
+#define DKC_CUSTOMER_BASE 1024
+
+/*
+ * Flags
+ */
+#define DKI_BAD144 0x01 /* use DEC std 144 bad sector fwding */
+#define DKI_MAPTRK 0x02 /* controller does track mapping */
+#define DKI_FMTTRK 0x04 /* formats only full track at a time */
+#define DKI_FMTVOL 0x08 /* formats only full volume at a time */
+#define DKI_FMTCYL 0x10 /* formats only full cylinders at a time */
+#define DKI_HEXUNIT 0x20 /* unit number is printed as 3 hex digits */
+#define DKI_PCMCIA_PFD 0x40 /* PCMCIA pseudo-floppy memory card */
+
+/*
+ * Used for all partitions
+ */
+struct dk_allmap {
+ struct dk_map dka_map[NDKMAP];
+};
+
+#if defined(_SYSCALL32)
+struct dk_allmap32 {
+ struct dk_map32 dka_map[NDKMAP];
+};
+#endif /* _SYSCALL32 */
+
+/*
+ * Definition of a disk's geometry
+ */
+struct dk_geom {
+ unsigned short dkg_ncyl; /* # of data cylinders */
+ unsigned short dkg_acyl; /* # of alternate cylinders */
+ unsigned short dkg_bcyl; /* cyl offset (for fixed head area) */
+ unsigned short dkg_nhead; /* # of heads */
+ unsigned short dkg_obs1; /* obsolete */
+ unsigned short dkg_nsect; /* # of data sectors per track */
+ unsigned short dkg_intrlv; /* interleave factor */
+ unsigned short dkg_obs2; /* obsolete */
+ unsigned short dkg_obs3; /* obsolete */
+ unsigned short dkg_apc; /* alternates per cyl (SCSI only) */
+ unsigned short dkg_rpm; /* revolutions per minute */
+ unsigned short dkg_pcyl; /* # of physical cylinders */
+ unsigned short dkg_write_reinstruct; /* # sectors to skip, writes */
+ unsigned short dkg_read_reinstruct; /* # sectors to skip, reads */
+ unsigned short dkg_extra[7]; /* for compatible expansion */
+};
+
+/*
+ * These defines are for historic compatibility with old drivers.
+ */
+#define dkg_bhead dkg_obs1 /* used to be head offset */
+#define dkg_gap1 dkg_obs2 /* used to be gap1 */
+#define dkg_gap2 dkg_obs3 /* used to be gap2 */
+
+/*
+ * Disk io control commands
+ * Warning: some other ioctls with the DIOC prefix exist elsewhere.
+ * The Generic DKIOC numbers are from 0 - 50.
+ * The Floppy Driver uses 51 - 100.
+ * The Hard Disk (except SCSI) 101 - 106. (these are obsolete)
+ * The CDROM Driver 151 - 200.
+ * The USCSI ioctl 201 - 250.
+ */
+#define DKIOC (0x04 << 8)
+
+/*
+ * The following ioctls are generic in nature and need to be
+ * supported as appropriate by all disk drivers
+ */
+#define DKIOCGGEOM (DKIOC|1) /* Get geometry */
+#define DKIOCINFO (DKIOC|3) /* Get info */
+#define DKIOCEJECT (DKIOC|6) /* Generic 'eject' */
+#define DKIOCGVTOC (DKIOC|11) /* Get VTOC */
+#define DKIOCSVTOC (DKIOC|12) /* Set VTOC & Write to Disk */
+
+/*
+ * Disk Cache Controls. These ioctls should be supported by
+ * all disk drivers.
+ *
+ * DKIOCFLUSHWRITECACHE when used from user-mode ignores the ioctl
+ * argument, but it should be passed as NULL to allow for future
+ * reinterpretation. From user-mode, this ioctl request is synchronous.
+ *
+ * When invoked from within the kernel, the arg can be NULL to indicate
+ * a synchronous request or can be the address of a struct dk_callback
+ * to request an asynchronous callback when the flush request is complete.
+ * In this case, the flag to the ioctl must include FKIOCTL and the
+ * dkc_callback field of the pointed to struct must be non-null or the
+ * request is made synchronously.
+ *
+ * In the callback case: if the ioctl returns 0, a callback WILL be performed.
+ * If the ioctl returns non-zero, a callback will NOT be performed.
+ * NOTE: In some cases, the callback may be done BEFORE the ioctl call
+ * returns. The caller's locking strategy should be prepared for this case.
+ */
+#define DKIOCFLUSHWRITECACHE (DKIOC|34) /* flush cache to phys medium */
+
+struct dk_callback {
+ void (*dkc_callback)(void *dkc_cookie, int error);
+ void *dkc_cookie;
+ int dkc_flag;
+};
+
+/* bit flag definitions for dkc_flag */
+#define FLUSH_VOLATILE 0x1 /* Bit 0: if set, only flush */
+ /* volatile cache; otherwise, flush */
+ /* volatile and non-volatile cache */
+
+#define DKIOCGETWCE (DKIOC|36) /* Get current write cache */
+ /* enablement status */
+#define DKIOCSETWCE (DKIOC|37) /* Enable/Disable write cache */
+
+/*
+ * The following ioctls are used by Sun drivers to communicate
+ * with their associated format routines. Support of these ioctls
+ * is not required of foreign drivers
+ */
+#define DKIOCSGEOM (DKIOC|2) /* Set geometry */
+#define DKIOCSAPART (DKIOC|4) /* Set all partitions */
+#define DKIOCGAPART (DKIOC|5) /* Get all partitions */
+#define DKIOCG_PHYGEOM (DKIOC|32) /* get physical geometry */
+#define DKIOCG_VIRTGEOM (DKIOC|33) /* get virtual geometry */
+
+/*
+ * The following ioctl's are removable media support
+ */
+#define DKIOCLOCK (DKIOC|7) /* Generic 'lock' */
+#define DKIOCUNLOCK (DKIOC|8) /* Generic 'unlock' */
+#define DKIOCSTATE (DKIOC|13) /* Inquire insert/eject state */
+#define DKIOCREMOVABLE (DKIOC|16) /* is media removable */
+
+
+/*
+ * ioctl for hotpluggable devices
+ */
+#define DKIOCHOTPLUGGABLE (DKIOC|35) /* is hotpluggable */
+
+/*
+ * Ioctl to force driver to re-read the alternate partition and rebuild
+ * the internal defect map.
+ */
+#define DKIOCADDBAD (DKIOC|20) /* Re-read the alternate map (IDE) */
+#define DKIOCGETDEF (DKIOC|21) /* read defect list (IDE) */
+
+/*
+ * Used by applications to get disk defect information from IDE
+ * drives.
+ */
+#ifdef _SYSCALL32
+struct defect_header32 {
+ int head;
+ caddr32_t buffer;
+};
+#endif /* _SYSCALL32 */
+
+struct defect_header {
+ int head;
+ caddr_t buffer;
+};
+
+#define DKIOCPARTINFO (DKIOC|22) /* Get partition or slice parameters */
+
+/*
+ * Used by applications to get partition or slice information
+ */
+#ifdef _SYSCALL32
+struct part_info32 {
+ uint32_t p_start;
+ int p_length;
+};
+#endif /* _SYSCALL32 */
+
+struct part_info {
+ uint64_t p_start;
+ int p_length;
+};
+
+/* The following ioctls are for Optical Memory Device */
+#define DKIOC_EBP_ENABLE (DKIOC|40) /* enable by pass erase on write */
+#define DKIOC_EBP_DISABLE (DKIOC|41) /* disable by pass erase on write */
+
+/*
+ * This state enum is the argument passed to the DKIOCSTATE ioctl.
+ */
+enum dkio_state { DKIO_NONE, DKIO_EJECTED, DKIO_INSERTED, DKIO_DEV_GONE };
+
+#define DKIOCGMEDIAINFO (DKIOC|42) /* get information about the media */
+
+/*
+ * ioctls to read/write mboot info.
+ */
+#define DKIOCGMBOOT (DKIOC|43) /* get mboot info */
+#define DKIOCSMBOOT (DKIOC|44) /* set mboot info */
+
+/*
+ * ioctl to get the device temperature.
+ */
+#define DKIOCGTEMPERATURE (DKIOC|45) /* get temperature */
+
+/*
+ * Used for providing the temperature.
+ */
+
+struct dk_temperature {
+ uint_t dkt_flags; /* Flags */
+ short dkt_cur_temp; /* Current disk temperature */
+ short dkt_ref_temp; /* reference disk temperature */
+};
+
+#define DKT_BYPASS_PM 0x1
+#define DKT_INVALID_TEMP 0xFFFF
+
+
+/*
+ * Used for Media info or the current profile info
+ */
+struct dk_minfo {
+ uint_t dki_media_type; /* Media type or profile info */
+ uint_t dki_lbsize; /* Logical blocksize of media */
+ diskaddr_t dki_capacity; /* Capacity as # of dki_lbsize blks */
+};
+
+/*
+ * Media types or profiles known
+ */
+#define DK_UNKNOWN 0x00 /* Media inserted - type unknown */
+
+
+/*
+ * SFF 8090 Specification Version 3, media types 0x01 - 0xfffe are retained to
+ * maintain compatibility with SFF8090. The following define the
+ * optical media type.
+ */
+#define DK_REMOVABLE_DISK 0x02 /* Removable Disk */
+#define DK_MO_ERASABLE 0x03 /* MO Erasable */
+#define DK_MO_WRITEONCE 0x04 /* MO Write once */
+#define DK_AS_MO 0x05 /* AS MO */
+#define DK_CDROM 0x08 /* CDROM */
+#define DK_CDR 0x09 /* CD-R */
+#define DK_CDRW 0x0A /* CD-RW */
+#define DK_DVDROM 0x10 /* DVD-ROM */
+#define DK_DVDR 0x11 /* DVD-R */
+#define DK_DVDRAM 0x12 /* DVD_RAM or DVD-RW */
+
+/*
+ * Media types for other rewritable magnetic media
+ */
+#define DK_FIXED_DISK 0x10001 /* Fixed disk SCSI or otherwise */
+#define DK_FLOPPY 0x10002 /* Floppy media */
+#define DK_ZIP 0x10003 /* IOMEGA ZIP media */
+#define DK_JAZ 0x10004 /* IOMEGA JAZ media */
+
+#define DKIOCSETEFI (DKIOC|17) /* Set EFI info */
+#define DKIOCGETEFI (DKIOC|18) /* Get EFI info */
+
+#define DKIOCPARTITION (DKIOC|9) /* Get partition info */
+
+/*
+ * Ioctls to get/set volume capabilities related to Logical Volume Managers.
+ * They include the ability to get/set capabilities and to issue a read to a
+ * specific underlying device of a replicated device.
+ */
+
+#define DKIOCGETVOLCAP (DKIOC | 25) /* Get volume capabilities */
+#define DKIOCSETVOLCAP (DKIOC | 26) /* Set volume capabilities */
+#define DKIOCDMR (DKIOC | 27) /* Issue a directed read */
+
+typedef uint_t volcapinfo_t;
+
+typedef uint_t volcapset_t;
+
+#define DKV_ABR_CAP 0x00000001 /* Support Appl.Based Recovery */
+#define DKV_DMR_CAP 0x00000002 /* Support Directed Mirror Read */
+
+typedef struct volcap {
+ volcapinfo_t vc_info; /* Capabilities available */
+ volcapset_t vc_set; /* Capabilities set */
+} volcap_t;
+
+#define VOL_SIDENAME 256
+
+typedef struct vol_directed_rd {
+ int vdr_flags;
+ offset_t vdr_offset;
+ size_t vdr_nbytes;
+ size_t vdr_bytesread;
+ void *vdr_data;
+ int vdr_side;
+ char vdr_side_name[VOL_SIDENAME];
+} vol_directed_rd_t;
+
+#define DKV_SIDE_INIT (-1)
+#define DKV_DMR_NEXT_SIDE 0x00000001
+#define DKV_DMR_DONE 0x00000002
+#define DKV_DMR_ERROR 0x00000004
+#define DKV_DMR_SUCCESS 0x00000008
+#define DKV_DMR_SHORT 0x00000010
+
+#ifdef _MULTI_DATAMODEL
+#if _LONG_LONG_ALIGNMENT == 8 && _LONG_LONG_ALIGNMENT_32 == 4
+#pragma pack(4)
+#endif
+typedef struct vol_directed_rd32 {
+ int32_t vdr_flags;
+ offset_t vdr_offset; /* 64-bit element on 32-bit alignment */
+ size32_t vdr_nbytes;
+ size32_t vdr_bytesread;
+ caddr32_t vdr_data;
+ int32_t vdr_side;
+ char vdr_side_name[VOL_SIDENAME];
+} vol_directed_rd32_t;
+#if _LONG_LONG_ALIGNMENT == 8 && _LONG_LONG_ALIGNMENT_32 == 4
+#pragma pack()
+#endif
+#endif /* _MULTI_DATAMODEL */
+
+/*
+ * The ioctl is used to fetch disk's device type, vendor ID,
+ * model number/product ID, firmware revision and serial number together.
+ *
+ * Currently there are two device types - DKD_ATA_TYPE which means the
+ * disk is driven by cmdk/ata or dad/uata driver, and DKD_SCSI_TYPE
+ * which means the disk is driven by sd/scsi hba driver.
+ */
+#define DKIOC_GETDISKID (DKIOC|46)
+
+/* These two labels are for dkd_dtype of dk_disk_id_t */
+#define DKD_ATA_TYPE 0x01 /* ATA disk or legacy mode SATA disk */
+#define DKD_SCSI_TYPE 0x02 /* SCSI disk or native mode SATA disk */
+
+#define DKD_ATA_MODEL 40 /* model number length */
+#define DKD_ATA_FWVER 8 /* firmware revision length */
+#define DKD_ATA_SERIAL 20 /* serial number length */
+
+#define DKD_SCSI_VENDOR 8 /* vendor ID length */
+#define DKD_SCSI_PRODUCT 16 /* product ID length */
+#define DKD_SCSI_REVLEVEL 4 /* revision level length */
+#define DKD_SCSI_SERIAL 12 /* serial number length */
+
+/*
+ * The argument type for DKIOC_GETDISKID ioctl.
+ */
+typedef struct dk_disk_id {
+ uint_t dkd_dtype;
+ union {
+ struct {
+ char dkd_amodel[DKD_ATA_MODEL]; /* 40 bytes */
+ char dkd_afwver[DKD_ATA_FWVER]; /* 8 bytes */
+ char dkd_aserial[DKD_ATA_SERIAL]; /* 20 bytes */
+ } ata_disk_id;
+ struct {
+ char dkd_svendor[DKD_SCSI_VENDOR]; /* 8 bytes */
+ char dkd_sproduct[DKD_SCSI_PRODUCT]; /* 16 bytes */
+ char dkd_sfwver[DKD_SCSI_REVLEVEL]; /* 4 bytes */
+ char dkd_sserial[DKD_SCSI_SERIAL]; /* 12 bytes */
+ } scsi_disk_id;
+ } disk_id;
+} dk_disk_id_t;
+
+/*
+ * The ioctl is used to update the firmware of device.
+ */
+#define DKIOC_UPDATEFW (DKIOC|47)
+
+/* The argument type for DKIOC_UPDATEFW ioctl */
+typedef struct dk_updatefw {
+ caddr_t dku_ptrbuf; /* pointer to firmware buf */
+ uint_t dku_size; /* firmware buf length */
+ uint8_t dku_type; /* firmware update type */
+} dk_updatefw_t;
+
+#ifdef _SYSCALL32
+typedef struct dk_updatefw_32 {
+ caddr32_t dku_ptrbuf; /* pointer to firmware buf */
+ uint_t dku_size; /* firmware buf length */
+ uint8_t dku_type; /* firmware update type */
+} dk_updatefw_32_t;
+#endif /* _SYSCALL32 */
+
+/*
+ * firmware update type - temporary or permanent use
+ */
+#define FW_TYPE_TEMP 0x0 /* temporary use */
+#define FW_TYPE_PERM 0x1 /* permanent use */
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _SYS_DKIO_H */
diff --git a/lib/libspl/include/sys/dklabel.h b/lib/libspl/include/sys/dklabel.h
new file mode 100644
index 000000000000..95faf2bb4ab3
--- /dev/null
+++ b/lib/libspl/include/sys/dklabel.h
@@ -0,0 +1,268 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 1990-2002 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _SYS_DKLABEL_H
+#define _SYS_DKLABEL_H
+
+
+
+#include <sys/isa_defs.h>
+#include <sys/types32.h>
+#include <sys/isa_defs.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Miscellaneous defines
+ */
+#define DKL_MAGIC 0xDABE /* magic number */
+#define FKL_MAGIC 0xff /* magic number for DOS floppies */
+
+#if defined(_SUNOS_VTOC_16)
+#define NDKMAP 16 /* # of logical partitions */
+#define DK_LABEL_LOC 1 /* location of disk label */
+#elif defined(_SUNOS_VTOC_8)
+#define NDKMAP 8 /* # of logical partitions */
+#define DK_LABEL_LOC 0 /* location of disk label */
+#else
+#error "No VTOC format defined."
+#endif
+
+#define LEN_DKL_ASCII 128 /* length of dkl_asciilabel */
+#define LEN_DKL_VVOL 8 /* length of v_volume */
+#define DK_LABEL_SIZE 512 /* size of disk label */
+#define DK_MAX_BLOCKS 0x7fffffff /* max # of blocks handled */
+
+/*
+ * Reserve two cylinders on SCSI disks.
+ * One is for the backup disk label and the other is for the deviceid.
+ *
+ * IPI disks only reserve one cylinder, but they will go away soon.
+ * CDROMs do not reserve any cylinders.
+ */
+#define DK_ACYL 2
+
+/*
+ * Format of a Sun disk label.
+ * Resides in cylinder 0, head 0, sector 0.
+ *
+ * sizeof (struct dk_label) should be 512 (the current sector size),
+ * but should the sector size increase, this structure should remain
+ * at the beginning of the sector.
+ */
+
+/*
+ * partition headers: section 1
+ * Returned in struct dk_allmap by ioctl DKIOC[SG]APART (dkio(7I))
+ */
+struct dk_map {
+ uint64_t dkl_cylno; /* starting cylinder */
+ uint64_t dkl_nblk; /* number of blocks; if == 0, */
+ /* partition is undefined */
+};
+
+/*
+ * partition headers: section 1
+ * Fixed size for on-disk dk_label
+ */
+struct dk_map32 {
+ daddr32_t dkl_cylno; /* starting cylinder */
+ daddr32_t dkl_nblk; /* number of blocks; if == 0, */
+ /* partition is undefined */
+};
+
+/*
+ * partition headers: section 2,
+ * brought over from AT&T SVr4 vtoc structure.
+ */
+struct dk_map2 {
+ uint16_t p_tag; /* ID tag of partition */
+ uint16_t p_flag; /* permission flag */
+};
+
+struct dkl_partition {
+ uint16_t p_tag; /* ID tag of partition */
+ uint16_t p_flag; /* permission flags */
+ daddr32_t p_start; /* start sector no of partition */
+ int32_t p_size; /* # of blocks in partition */
+};
+
+
+/*
+ * VTOC inclusions from AT&T SVr4
+ * Fixed sized types for on-disk VTOC
+ */
+
+struct dk_vtoc {
+#if defined(_SUNOS_VTOC_16)
+ uint32_t v_bootinfo[3]; /* info for mboot (unsupported) */
+ uint32_t v_sanity; /* to verify vtoc sanity */
+ uint32_t v_version; /* layout version */
+ char v_volume[LEN_DKL_VVOL]; /* volume name */
+ uint16_t v_sectorsz; /* sector size in bytes */
+ uint16_t v_nparts; /* number of partitions */
+ uint32_t v_reserved[10]; /* free space */
+ struct dkl_partition v_part[NDKMAP]; /* partition headers */
+ time32_t timestamp[NDKMAP]; /* partition timestamp (unsupported) */
+ char v_asciilabel[LEN_DKL_ASCII]; /* for compatibility */
+#elif defined(_SUNOS_VTOC_8)
+ uint32_t v_version; /* layout version */
+ char v_volume[LEN_DKL_VVOL]; /* volume name */
+ uint16_t v_nparts; /* number of partitions */
+ struct dk_map2 v_part[NDKMAP]; /* partition hdrs, sec 2 */
+ uint32_t v_bootinfo[3]; /* info needed by mboot */
+ uint32_t v_sanity; /* to verify vtoc sanity */
+ uint32_t v_reserved[10]; /* free space */
+ time32_t v_timestamp[NDKMAP]; /* partition timestamp */
+#else
+#error "No VTOC format defined."
+#endif
+};
+
+/*
+ * define the amount of disk label padding needed to make
+ * the entire structure occupy 512 bytes.
+ */
+#if defined(_SUNOS_VTOC_16)
+#define LEN_DKL_PAD (DK_LABEL_SIZE - \
+ ((sizeof (struct dk_vtoc) + \
+ (4 * sizeof (uint32_t)) + \
+ (12 * sizeof (uint16_t)) + \
+ (2 * (sizeof (uint16_t))))))
+#elif defined(_SUNOS_VTOC_8)
+#define LEN_DKL_PAD (DK_LABEL_SIZE \
+ - ((LEN_DKL_ASCII) + \
+ (sizeof (struct dk_vtoc)) + \
+ (sizeof (struct dk_map32) * NDKMAP) + \
+ (14 * (sizeof (uint16_t))) + \
+ (2 * (sizeof (uint16_t)))))
+#else
+#error "No VTOC format defined."
+#endif
+
+
+struct dk_label {
+#if defined(_SUNOS_VTOC_16)
+ struct dk_vtoc dkl_vtoc; /* vtoc inclusions from AT&T SVr4 */
+ uint32_t dkl_pcyl; /* # of physical cylinders */
+ uint32_t dkl_ncyl; /* # of data cylinders */
+ uint16_t dkl_acyl; /* # of alternate cylinders */
+ uint16_t dkl_bcyl; /* cyl offset (for fixed head area) */
+ uint32_t dkl_nhead; /* # of heads */
+ uint32_t dkl_nsect; /* # of data sectors per track */
+ uint16_t dkl_intrlv; /* interleave factor */
+ uint16_t dkl_skew; /* skew factor */
+ uint16_t dkl_apc; /* alternates per cyl (SCSI only) */
+ uint16_t dkl_rpm; /* revolutions per minute */
+ uint16_t dkl_write_reinstruct; /* # sectors to skip, writes */
+ uint16_t dkl_read_reinstruct; /* # sectors to skip, reads */
+ uint16_t dkl_extra[4]; /* for compatible expansion */
+ char dkl_pad[LEN_DKL_PAD]; /* unused part of 512 bytes */
+#elif defined(_SUNOS_VTOC_8)
+ char dkl_asciilabel[LEN_DKL_ASCII]; /* for compatibility */
+ struct dk_vtoc dkl_vtoc; /* vtoc inclusions from AT&T SVr4 */
+ uint16_t dkl_write_reinstruct; /* # sectors to skip, writes */
+ uint16_t dkl_read_reinstruct; /* # sectors to skip, reads */
+ char dkl_pad[LEN_DKL_PAD]; /* unused part of 512 bytes */
+ uint16_t dkl_rpm; /* rotations per minute */
+ uint16_t dkl_pcyl; /* # physical cylinders */
+ uint16_t dkl_apc; /* alternates per cylinder */
+ uint16_t dkl_obs1; /* obsolete */
+ uint16_t dkl_obs2; /* obsolete */
+ uint16_t dkl_intrlv; /* interleave factor */
+ uint16_t dkl_ncyl; /* # of data cylinders */
+ uint16_t dkl_acyl; /* # of alternate cylinders */
+ uint16_t dkl_nhead; /* # of heads in this partition */
+ uint16_t dkl_nsect; /* # of 512 byte sectors per track */
+ uint16_t dkl_obs3; /* obsolete */
+ uint16_t dkl_obs4; /* obsolete */
+ struct dk_map32 dkl_map[NDKMAP]; /* logical partition headers */
+#else
+#error "No VTOC format defined."
+#endif
+ uint16_t dkl_magic; /* identifies this label format */
+ uint16_t dkl_cksum; /* xor checksum of sector */
+};
+
+#if defined(_SUNOS_VTOC_16)
+#define dkl_asciilabel dkl_vtoc.v_asciilabel
+#define v_timestamp timestamp
+
+#elif defined(_SUNOS_VTOC_8)
+
+/*
+ * These defines are for historic compatibility with old drivers.
+ */
+#define dkl_gap1 dkl_obs1 /* used to be gap1 */
+#define dkl_gap2 dkl_obs2 /* used to be gap2 */
+#define dkl_bhead dkl_obs3 /* used to be label head offset */
+#define dkl_ppart dkl_obs4 /* used to by physical partition */
+#else
+#error "No VTOC format defined."
+#endif
+
+struct fk_label { /* DOS floppy label */
+ uchar_t fkl_type;
+ uchar_t fkl_magich;
+ uchar_t fkl_magicl;
+ uchar_t filler;
+};
+
+/*
+ * Layout of stored fabricated device id (on-disk)
+ */
+#define DK_DEVID_BLKSIZE (512)
+#define DK_DEVID_SIZE (DK_DEVID_BLKSIZE - ((sizeof (uchar_t) * 7)))
+#define DK_DEVID_REV_MSB (0)
+#define DK_DEVID_REV_LSB (1)
+
+struct dk_devid {
+ uchar_t dkd_rev_hi; /* revision (MSB) */
+ uchar_t dkd_rev_lo; /* revision (LSB) */
+ uchar_t dkd_flags; /* flags (not used yet) */
+ uchar_t dkd_devid[DK_DEVID_SIZE]; /* devid stored here */
+ uchar_t dkd_checksum3; /* checksum (MSB) */
+ uchar_t dkd_checksum2;
+ uchar_t dkd_checksum1;
+ uchar_t dkd_checksum0; /* checksum (LSB) */
+};
+
+#define DKD_GETCHKSUM(dkd) ((dkd)->dkd_checksum3 << 24) + \
+ ((dkd)->dkd_checksum2 << 16) + \
+ ((dkd)->dkd_checksum1 << 8) + \
+ ((dkd)->dkd_checksum0)
+
+#define DKD_FORMCHKSUM(c, dkd) (dkd)->dkd_checksum3 = hibyte(hiword((c))); \
+ (dkd)->dkd_checksum2 = lobyte(hiword((c))); \
+ (dkd)->dkd_checksum1 = hibyte(loword((c))); \
+ (dkd)->dkd_checksum0 = lobyte(loword((c)));
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _SYS_DKLABEL_H */
diff --git a/lib/libspl/include/sys/dktp/Makefile.am b/lib/libspl/include/sys/dktp/Makefile.am
new file mode 100644
index 000000000000..4ad3695d8abc
--- /dev/null
+++ b/lib/libspl/include/sys/dktp/Makefile.am
@@ -0,0 +1,4 @@
+libspldir = $(includedir)/libspl/sys/dktp
+libspl_HEADERS = \
+ fdisk.h
+
diff --git a/lib/libspl/include/sys/dktp/fdisk.h b/lib/libspl/include/sys/dktp/fdisk.h
new file mode 100644
index 000000000000..e90135f362e6
--- /dev/null
+++ b/lib/libspl/include/sys/dktp/fdisk.h
@@ -0,0 +1,173 @@
+/*
+ * 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 2009 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+/* Copyright (c) 1984, 1986, 1987, 1988 AT&T */
+/* All Rights Reserved */
+
+
+#ifndef _SYS_DKTP_FDISK_H
+#define _SYS_DKTP_FDISK_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * fdisk.h
+ * This file defines the structure of physical disk sector 0 for use on
+ * AT386 systems. The format of this sector is constrained by the ROM
+ * BIOS and MS-DOS conventions.
+ * Note that this block does not define the partitions used by the unix
+ * driver. The unix partitions are obtained from the VTOC.
+ */
+
+/*
+ * the MAX values are the maximum usable values for BIOS chs values
+ * The MAX_CYL value of 1022 is the maximum usable value
+ * the value of 1023 is a fence value,
+ * indicating no CHS geometry exists for the corresponding LBA value.
+ * HEAD range [ 0 .. MAX_HEAD ], so number of heads is (MAX_HEAD + 1)
+ * SECT range [ 1 .. MAX_SECT ], so number of sectors is (MAX_SECT)
+ */
+#define MAX_SECT (63)
+#define MAX_CYL (1022)
+#define MAX_HEAD (254)
+
+/*
+ * BOOTSZ was reduced from 446 to 440 bytes to NOT overwrite the Windows
+ * Vista DISKID. Otherwise Vista won't boot from Solaris GRUB in a dual-boot
+ * setup.
+ * The actual size of mboot code is 425 bytes while that of GRUB stage1 is
+ * 423 bytes. So this changes does not harm them.
+ */
+#define BOOTSZ 440 /* size of boot code in master boot block */
+#define FD_NUMPART 4 /* number of 'partitions' in fdisk table */
+#define MBB_MAGIC 0xAA55 /* magic number for mboot.signature */
+#define DEFAULT_INTLV 4 /* default interleave for testing tracks */
+#define MINPSIZE 4 /* minimum number of cylinders in a partition */
+#define TSTPAT 0xE5 /* test pattern for verifying disk */
+
+/*
+ * structure to hold the fdisk partition table
+ */
+struct ipart {
+ unsigned char bootid; /* bootable or not */
+ unsigned char beghead; /* beginning head, sector, cylinder */
+ unsigned char begsect; /* begcyl is a 10-bit number. High 2 bits */
+ unsigned char begcyl; /* are in begsect. */
+ unsigned char systid; /* OS type */
+ unsigned char endhead; /* ending head, sector, cylinder */
+ unsigned char endsect; /* endcyl is a 10-bit number. High 2 bits */
+ unsigned char endcyl; /* are in endsect. */
+ uint32_t relsect; /* first sector relative to start of disk */
+ uint32_t numsect; /* number of sectors in partition */
+};
+/*
+ * Values for bootid.
+ */
+#define NOTACTIVE 0
+#define ACTIVE 128
+/*
+ * Values for systid.
+ */
+#define UNUSED 0 /* Empty Partition */
+#define DOSOS12 1 /* DOS partition, 12-bit FAT */
+#define PCIXOS 2 /* PC/IX partition */
+#define DOSOS16 4 /* DOS partition, 16-bit FAT */
+#define EXTDOS 5 /* EXT-DOS partition */
+#define DOSHUGE 6 /* Huge DOS partition > 32MB */
+#define FDISK_IFS 7 /* Installable File System (IFS): HPFS & NTFS */
+#define FDISK_AIXBOOT 8 /* AIX Boot */
+#define FDISK_AIXDATA 9 /* AIX Data */
+#define FDISK_OS2BOOT 10 /* OS/2 Boot Manager */
+#define FDISK_WINDOWS 11 /* Windows 95 FAT32 (up to 2047GB) */
+#define FDISK_EXT_WIN 12 /* Windows 95 FAT32 (extended-INT13) */
+#define FDISK_FAT95 14 /* DOS 16-bit FAT, LBA-mapped */
+#define FDISK_EXTLBA 15 /* Extended partition, LBA-mapped */
+#define DIAGPART 18 /* Diagnostic boot partition (OS independent) */
+#define FDISK_LINUX 65 /* Linux */
+#define FDISK_LINUXDSWAP 66 /* Linux swap (sharing disk w/ DRDOS) */
+#define FDISK_LINUXDNAT 67 /* Linux native (sharing disk with DRDOS) */
+#define FDISK_CPM 82 /* CP/M */
+#define DOSDATA 86 /* DOS data partition */
+#define OTHEROS 98 /* part. type for appl. (DB?) needs */
+ /* raw partition. ID was 0 but conflicted */
+ /* with DOS 3.3 fdisk */
+#define UNIXOS 99 /* UNIX V.x partition */
+#define FDISK_NOVELL2 100 /* Novell Netware 286 */
+#define FDISK_NOVELL3 101 /* Novell Netware 3.x and later */
+#define FDISK_QNX4 119 /* QNX 4.x */
+#define FDISK_QNX42 120 /* QNX 4.x 2nd part */
+#define FDISK_QNX43 121 /* QNX 4.x 3rd part */
+#define SUNIXOS 130 /* Solaris UNIX partition */
+#define FDISK_LINUXNAT 131 /* Linux native */
+#define FDISK_NTFSVOL1 134 /* NTFS volume set 1 */
+#define FDISK_NTFSVOL2 135 /* NTFS volume set 2 */
+#define FDISK_BSD 165 /* BSD/386, 386BSD, NetBSD, FreeBSD, OpenBSD */
+#define FDISK_NEXTSTEP 167 /* NeXTSTEP */
+#define FDISK_BSDIFS 183 /* BSDI file system */
+#define FDISK_BSDISWAP 184 /* BSDI swap */
+#define X86BOOT 190 /* x86 Solaris boot partition */
+#define SUNIXOS2 191 /* Solaris UNIX partition */
+#define EFI_PMBR 238 /* EFI PMBR */
+#define EFI_FS 239 /* EFI File System (System Partition) */
+#define MAXDOS 65535L /* max size (sectors) for DOS partition */
+
+/*
+ * structure to hold master boot block in physical sector 0 of the disk.
+ * Note that partitions stuff can't be directly included in the structure
+ * because of lameo '386 compiler alignment design.
+ * Alignment issues also force us to have 2 16bit entities for a single
+ * 32bit win_volserno. It is not used anywhere anyway.
+ */
+
+struct mboot { /* master boot block */
+ char bootinst[BOOTSZ];
+ uint16_t win_volserno_lo;
+ uint16_t win_volserno_hi;
+ uint16_t reserved;
+ char parts[FD_NUMPART * sizeof (struct ipart)];
+ ushort_t signature;
+};
+
+#if defined(__i386) || defined(__amd64)
+
+/* Byte offset of the start of the partition table within the sector */
+#define FDISK_PART_TABLE_START 446
+
+/* Maximum number of valid partitions assumed as 32 */
+#define MAX_EXT_PARTS 32
+
+#else
+
+#define MAX_EXT_PARTS 0
+
+#endif /* if defined(__i386) || defined(__amd64) */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _SYS_DKTP_FDISK_H */
diff --git a/lib/libspl/include/sys/feature_tests.h b/lib/libspl/include/sys/feature_tests.h
new file mode 100644
index 000000000000..1a68b75f0cdc
--- /dev/null
+++ b/lib/libspl/include/sys/feature_tests.h
@@ -0,0 +1,32 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2006 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _SYS_FEATURE_TESTS_H
+#define _SYS_FEATURE_TESTS_H
+
+#define __NORETURN __attribute__((__noreturn__))
+
+#endif
diff --git a/lib/libspl/include/sys/int_limits.h b/lib/libspl/include/sys/int_limits.h
new file mode 100644
index 000000000000..7af68cdb2998
--- /dev/null
+++ b/lib/libspl/include/sys/int_limits.h
@@ -0,0 +1,30 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2006 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _LIBSPL_SYS_INT_LIMITS_H
+#define _LIBSPL_SYS_INT_LIMITS_H
+
+#endif
diff --git a/lib/libspl/include/sys/int_types.h b/lib/libspl/include/sys/int_types.h
new file mode 100644
index 000000000000..51e9e0285490
--- /dev/null
+++ b/lib/libspl/include/sys/int_types.h
@@ -0,0 +1,32 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2006 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _SOL_SYS_INT_TYPES_H
+#define _SOL_SYS_INT_TYPES_H
+
+#include <inttypes.h>
+
+#endif
diff --git a/lib/libspl/include/sys/inttypes.h b/lib/libspl/include/sys/inttypes.h
new file mode 100644
index 000000000000..d7d063985316
--- /dev/null
+++ b/lib/libspl/include/sys/inttypes.h
@@ -0,0 +1,34 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2006 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _SOL_SYS_INTTYPES_H
+#define _SOL_SYS_INTTYPES_H
+
+#include <inttypes.h>
+
+#define _INT64_TYPE
+
+#endif
diff --git a/lib/libspl/include/sys/isa_defs.h b/lib/libspl/include/sys/isa_defs.h
new file mode 100644
index 000000000000..8c0932f57654
--- /dev/null
+++ b/lib/libspl/include/sys/isa_defs.h
@@ -0,0 +1,264 @@
+/*
+ * 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 2007 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _SYS_ISA_DEFS_H
+#define _SYS_ISA_DEFS_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* x86_64 arch specific defines */
+#if defined(__x86_64) || defined(__x86_64__)
+
+#if !defined(__x86_64)
+#define __x86_64
+#endif
+
+#if !defined(__amd64)
+#define __amd64
+#endif
+
+#if !defined(__x86)
+#define __x86
+#endif
+
+#if defined(_ILP32)
+/* x32-specific defines; careful to *not* define _LP64 here */
+#else
+#if !defined(_LP64)
+#define _LP64
+#endif
+#endif
+
+#if !defined(_ZFS_LITTLE_ENDIAN)
+#define _ZFS_LITTLE_ENDIAN
+#endif
+
+#define _SUNOS_VTOC_16
+#define HAVE_EFFICIENT_UNALIGNED_ACCESS
+
+/* i386 arch specific defines */
+#elif defined(__i386) || defined(__i386__)
+
+#if !defined(__i386)
+#define __i386
+#endif
+
+#if !defined(__x86)
+#define __x86
+#endif
+
+#if !defined(_ILP32)
+#define _ILP32
+#endif
+
+#if !defined(_ZFS_LITTLE_ENDIAN)
+#define _ZFS_LITTLE_ENDIAN
+#endif
+
+#define _SUNOS_VTOC_16
+#define HAVE_EFFICIENT_UNALIGNED_ACCESS
+
+/* powerpc arch specific defines */
+#elif defined(__powerpc) || defined(__powerpc__) || defined(__powerpc64__)
+
+#if !defined(__powerpc)
+#define __powerpc
+#endif
+
+#if !defined(__powerpc__)
+#define __powerpc__
+#endif
+
+#if defined(__powerpc64__)
+#if !defined(_LP64)
+#define _LP64
+#endif
+#else
+#if !defined(_ILP32)
+#define _ILP32
+#endif
+#endif
+
+#define _SUNOS_VTOC_16
+#define HAVE_EFFICIENT_UNALIGNED_ACCESS
+
+#if defined(__BYTE_ORDER)
+#if defined(__BIG_ENDIAN) && __BYTE_ORDER == __BIG_ENDIAN
+#define _ZFS_BIG_ENDIAN
+#elif defined(__LITTLE_ENDIAN) && __BYTE_ORDER == __LITTLE_ENDIAN
+#define _ZFS_LITTLE_ENDIAN
+#endif
+#elif defined(_BYTE_ORDER)
+#if defined(_BIG_ENDIAN) && _BYTE_ORDER == _BIG_ENDIAN
+#define _ZFS_BIG_ENDIAN
+#elif defined(_LITTLE_ENDIAN) && _BYTE_ORDER == _LITTLE_ENDIAN
+#define _ZFS_LITTLE_ENDIAN
+#endif
+#elif defined(_BIG_ENDIAN) && !defined(_LITTLE_ENDIAN)
+#define _ZFS_BIG_ENDIAN
+#elif defined(_LITTLE_ENDIAN) && !defined(_BIG_ENDIAN)
+#define _ZFS_LITTLE_ENDIAN
+#endif
+
+/* arm arch specific defines */
+#elif defined(__arm) || defined(__arm__) || defined(__aarch64__)
+
+#if !defined(__arm)
+#define __arm
+#endif
+
+#if !defined(__arm__)
+#define __arm__
+#endif
+
+#if defined(__aarch64__)
+#if !defined(_LP64)
+#define _LP64
+#endif
+#else
+#if !defined(_ILP32)
+#define _ILP32
+#endif
+#endif
+
+#if defined(__ARMEL__) || defined(__AARCH64EL__)
+#define _ZFS_LITTLE_ENDIAN
+#else
+#define _ZFS_BIG_ENDIAN
+#endif
+
+#define _SUNOS_VTOC_16
+
+#if defined(__ARM_FEATURE_UNALIGNED)
+#define HAVE_EFFICIENT_UNALIGNED_ACCESS
+#endif
+
+/* sparc arch specific defines */
+#elif defined(__sparc) || defined(__sparc__)
+
+#if !defined(__sparc)
+#define __sparc
+#endif
+
+#if !defined(__sparc__)
+#define __sparc__
+#endif
+
+#define _ZFS_BIG_ENDIAN
+#define _SUNOS_VTOC_16
+
+#if defined(__arch64__)
+#if !defined(_LP64)
+#define _LP64
+#endif
+#else
+#if !defined(_ILP32)
+#define _ILP32
+#endif
+#endif
+
+/* s390 arch specific defines */
+#elif defined(__s390__)
+#if defined(__s390x__)
+#if !defined(_LP64)
+#define _LP64
+#endif
+#else
+#if !defined(_ILP32)
+#define _ILP32
+#endif
+#endif
+
+#define _ZFS_BIG_ENDIAN
+#define _SUNOS_VTOC_16
+
+/* MIPS arch specific defines */
+#elif defined(__mips__)
+
+#if defined(__MIPSEB__)
+#define _ZFS_BIG_ENDIAN
+#elif defined(__MIPSEL__)
+#define _ZFS_LITTLE_ENDIAN
+#else
+#error MIPS no endian specified
+#endif
+
+#if !defined(_LP64) && !defined(_ILP32)
+#define _ILP32
+#endif
+
+#define _SUNOS_VTOC_16
+
+/*
+ * RISC-V arch specific defines
+ * only RV64G (including atomic) LP64 is supported yet
+ */
+#elif defined(__riscv) && defined(_LP64) && _LP64 && \
+ defined(__riscv_atomic) && __riscv_atomic
+
+#ifndef __riscv__
+#define __riscv__
+#endif
+
+#ifndef __rv64g__
+#define __rv64g__
+#endif
+
+#define _ZFS_LITTLE_ENDIAN
+
+#define _SUNOS_VTOC_16
+
+#else
+/*
+ * Currently supported:
+ * x86_64, x32, i386, arm, powerpc, s390, sparc, mips, and RV64G
+ */
+#error "Unsupported ISA type"
+#endif
+
+#if defined(_ILP32) && defined(_LP64)
+#error "Both _ILP32 and _LP64 are defined"
+#endif
+
+#if !defined(_ILP32) && !defined(_LP64)
+#error "Neither _ILP32 or _LP64 are defined"
+#endif
+
+#if defined(_ZFS_LITTLE_ENDIAN) && defined(_ZFS_BIG_ENDIAN)
+#error "Both _ZFS_LITTLE_ENDIAN and _ZFS_BIG_ENDIAN are defined"
+#endif
+
+#if !defined(_ZFS_LITTLE_ENDIAN) && !defined(_ZFS_BIG_ENDIAN)
+#error "Neither _ZFS_LITTLE_ENDIAN nor _ZFS_BIG_ENDIAN are defined"
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _SYS_ISA_DEFS_H */
diff --git a/lib/libspl/include/sys/kmem.h b/lib/libspl/include/sys/kmem.h
new file mode 100644
index 000000000000..83d47565aeaf
--- /dev/null
+++ b/lib/libspl/include/sys/kmem.h
@@ -0,0 +1,45 @@
+/*
+ * 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 2007 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _SYS_KMEM_H
+#define _SYS_KMEM_H
+
+#include <stdlib.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define KM_SLEEP 0x00000000 /* same as KM_SLEEP */
+#define KM_NOSLEEP 0x00000001 /* same as KM_NOSLEEP */
+
+#define kmem_alloc(size, flags) malloc(size)
+#define kmem_free(ptr, size) free(ptr)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _SYS_KMEM_H */
diff --git a/lib/libspl/include/sys/kstat.h b/lib/libspl/include/sys/kstat.h
new file mode 100644
index 000000000000..69fb6d401fc7
--- /dev/null
+++ b/lib/libspl/include/sys/kstat.h
@@ -0,0 +1,822 @@
+/*
+ * 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 2006 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _SYS_KSTAT_H
+#define _SYS_KSTAT_H
+
+
+
+/*
+ * Definition of general kernel statistics structures and /dev/kstat ioctls
+ */
+
+#include <sys/types.h>
+#include <sys/time.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef int kid_t; /* unique kstat id */
+
+/*
+ * Kernel statistics driver (/dev/kstat) ioctls
+ */
+
+#define KSTAT_IOC_BASE ('K' << 8)
+
+#define KSTAT_IOC_CHAIN_ID KSTAT_IOC_BASE | 0x01
+#define KSTAT_IOC_READ KSTAT_IOC_BASE | 0x02
+#define KSTAT_IOC_WRITE KSTAT_IOC_BASE | 0x03
+
+/*
+ * /dev/kstat ioctl usage (kd denotes /dev/kstat descriptor):
+ *
+ * kcid = ioctl(kd, KSTAT_IOC_CHAIN_ID, NULL);
+ * kcid = ioctl(kd, KSTAT_IOC_READ, kstat_t *);
+ * kcid = ioctl(kd, KSTAT_IOC_WRITE, kstat_t *);
+ */
+
+#define KSTAT_STRLEN 255 /* 254 chars + NULL; must be 16 * n - 1 */
+
+/*
+ * The generic kstat header
+ */
+
+typedef struct kstat {
+ /*
+ * Fields relevant to both kernel and user
+ */
+ hrtime_t ks_crtime; /* creation time (from gethrtime()) */
+ struct kstat *ks_next; /* kstat chain linkage */
+ kid_t ks_kid; /* unique kstat ID */
+ char ks_module[KSTAT_STRLEN]; /* provider module name */
+ uchar_t ks_resv; /* reserved, currently just padding */
+ int ks_instance; /* provider module's instance */
+ char ks_name[KSTAT_STRLEN]; /* kstat name */
+ uchar_t ks_type; /* kstat data type */
+ char ks_class[KSTAT_STRLEN]; /* kstat class */
+ uchar_t ks_flags; /* kstat flags */
+ void *ks_data; /* kstat type-specific data */
+ uint_t ks_ndata; /* # of type-specific data records */
+ size_t ks_data_size; /* total size of kstat data section */
+ hrtime_t ks_snaptime; /* time of last data snapshot */
+ /*
+ * Fields relevant to kernel only
+ */
+ int (*ks_update)(struct kstat *, int); /* dynamic update */
+ void *ks_private; /* arbitrary provider-private data */
+ int (*ks_snapshot)(struct kstat *, void *, int);
+ void *ks_lock; /* protects this kstat's data */
+} kstat_t;
+
+#ifdef _SYSCALL32
+
+typedef int32_t kid32_t;
+
+typedef struct kstat32 {
+ /*
+ * Fields relevant to both kernel and user
+ */
+ hrtime_t ks_crtime;
+ caddr32_t ks_next; /* struct kstat pointer */
+ kid32_t ks_kid;
+ char ks_module[KSTAT_STRLEN];
+ uint8_t ks_resv;
+ int32_t ks_instance;
+ char ks_name[KSTAT_STRLEN];
+ uint8_t ks_type;
+ char ks_class[KSTAT_STRLEN];
+ uint8_t ks_flags;
+ caddr32_t ks_data; /* type-specific data */
+ uint32_t ks_ndata;
+ size32_t ks_data_size;
+ hrtime_t ks_snaptime;
+ /*
+ * Fields relevant to kernel only (only needed here for padding)
+ */
+ int32_t _ks_update;
+ caddr32_t _ks_private;
+ int32_t _ks_snapshot;
+ caddr32_t _ks_lock;
+} kstat32_t;
+
+#endif /* _SYSCALL32 */
+
+/*
+ * kstat structure and locking strategy
+ *
+ * Each kstat consists of a header section (a kstat_t) and a data section.
+ * The system maintains a set of kstats, protected by kstat_chain_lock.
+ * kstat_chain_lock protects all additions to/deletions from this set,
+ * as well as all changes to kstat headers. kstat data sections are
+ * *optionally* protected by the per-kstat ks_lock. If ks_lock is non-NULL,
+ * kstat clients (e.g. /dev/kstat) will acquire this lock for all of their
+ * operations on that kstat. It is up to the kstat provider to decide whether
+ * guaranteeing consistent data to kstat clients is sufficiently important
+ * to justify the locking cost. Note, however, that most statistic updates
+ * already occur under one of the provider's mutexes, so if the provider sets
+ * ks_lock to point to that mutex, then kstat data locking is free.
+ *
+ * NOTE: variable-size kstats MUST employ kstat data locking, to prevent
+ * data-size races with kstat clients.
+ *
+ * NOTE: ks_lock is really of type (kmutex_t *); it is declared as (void *)
+ * in the kstat header so that users don't have to be exposed to all of the
+ * kernel's lock-related data structures.
+ */
+
+#if defined(_KERNEL)
+
+#define KSTAT_ENTER(k) \
+ { kmutex_t *lp = (k)->ks_lock; if (lp) mutex_enter(lp); }
+
+#define KSTAT_EXIT(k) \
+ { kmutex_t *lp = (k)->ks_lock; if (lp) mutex_exit(lp); }
+
+#define KSTAT_UPDATE(k, rw) (*(k)->ks_update)((k), (rw))
+
+#define KSTAT_SNAPSHOT(k, buf, rw) (*(k)->ks_snapshot)((k), (buf), (rw))
+
+#endif /* defined(_KERNEL) */
+
+/*
+ * kstat time
+ *
+ * All times associated with kstats (e.g. creation time, snapshot time,
+ * kstat_timer_t and kstat_io_t timestamps, etc.) are 64-bit nanosecond values,
+ * as returned by gethrtime(). The accuracy of these timestamps is machine
+ * dependent, but the precision (units) is the same across all platforms.
+ */
+
+/*
+ * kstat identity (KID)
+ *
+ * Each kstat is assigned a unique KID (kstat ID) when it is added to the
+ * global kstat chain. The KID is used as a cookie by /dev/kstat to
+ * request information about the corresponding kstat. There is also
+ * an identity associated with the entire kstat chain, kstat_chain_id,
+ * which is bumped each time a kstat is added or deleted. /dev/kstat uses
+ * the chain ID to detect changes in the kstat chain (e.g., a new disk
+ * coming online) between ioctl()s.
+ */
+
+/*
+ * kstat module, kstat instance
+ *
+ * ks_module and ks_instance contain the name and instance of the module
+ * that created the kstat. In cases where there can only be one instance,
+ * ks_instance is 0. The kernel proper (/kernel/unix) uses "unix" as its
+ * module name.
+ */
+
+/*
+ * kstat name
+ *
+ * ks_name gives a meaningful name to a kstat. The full kstat namespace
+ * is module.instance.name, so the name only need be unique within a
+ * module. kstat_create() will fail if you try to create a kstat with
+ * an already-used (ks_module, ks_instance, ks_name) triplet. Spaces are
+ * allowed in kstat names, but strongly discouraged, since they hinder
+ * awk-style processing at user level.
+ */
+
+/*
+ * kstat type
+ *
+ * The kstat mechanism provides several flavors of kstat data, defined
+ * below. The "raw" kstat type is just treated as an array of bytes; you
+ * can use this to export any kind of data you want.
+ *
+ * Some kstat types allow multiple data structures per kstat, e.g.
+ * KSTAT_TYPE_NAMED; others do not. This is part of the spec for each
+ * kstat data type.
+ *
+ * User-level tools should *not* rely on the #define KSTAT_NUM_TYPES. To
+ * get this information, read out the standard system kstat "kstat_types".
+ */
+
+#define KSTAT_TYPE_RAW 0 /* can be anything */
+ /* ks_ndata >= 1 */
+#define KSTAT_TYPE_NAMED 1 /* name/value pair */
+ /* ks_ndata >= 1 */
+#define KSTAT_TYPE_INTR 2 /* interrupt statistics */
+ /* ks_ndata == 1 */
+#define KSTAT_TYPE_IO 3 /* I/O statistics */
+ /* ks_ndata == 1 */
+#define KSTAT_TYPE_TIMER 4 /* event timer */
+ /* ks_ndata >= 1 */
+
+#define KSTAT_NUM_TYPES 5
+
+/*
+ * kstat class
+ *
+ * Each kstat can be characterized as belonging to some broad class
+ * of statistics, e.g. disk, tape, net, vm, streams, etc. This field
+ * can be used as a filter to extract related kstats. The following
+ * values are currently in use: disk, tape, net, controller, vm, kvm,
+ * hat, streams, kstat, and misc. (The kstat class encompasses things
+ * like kstat_types.)
+ */
+
+/*
+ * kstat flags
+ *
+ * Any of the following flags may be passed to kstat_create(). They are
+ * all zero by default.
+ *
+ * KSTAT_FLAG_VIRTUAL:
+ *
+ * Tells kstat_create() not to allocate memory for the
+ * kstat data section; instead, you will set the ks_data
+ * field to point to the data you wish to export. This
+ * provides a convenient way to export existing data
+ * structures.
+ *
+ * KSTAT_FLAG_VAR_SIZE:
+ *
+ * The size of the kstat you are creating will vary over time.
+ * For example, you may want to use the kstat mechanism to
+ * export a linked list. NOTE: The kstat framework does not
+ * manage the data section, so all variable-size kstats must be
+ * virtual kstats. Moreover, variable-size kstats MUST employ
+ * kstat data locking to prevent data-size races with kstat
+ * clients. See the section on "kstat snapshot" for details.
+ *
+ * KSTAT_FLAG_WRITABLE:
+ *
+ * Makes the kstat's data section writable by root.
+ * The ks_snapshot routine (see below) does not need to check for
+ * this; permission checking is handled in the kstat driver.
+ *
+ * KSTAT_FLAG_PERSISTENT:
+ *
+ * Indicates that this kstat is to be persistent over time.
+ * For persistent kstats, kstat_delete() simply marks the
+ * kstat as dormant; a subsequent kstat_create() reactivates
+ * the kstat. This feature is provided so that statistics
+ * are not lost across driver close/open (e.g., raw disk I/O
+ * on a disk with no mounted partitions.)
+ * NOTE: Persistent kstats cannot be virtual, since ks_data
+ * points to garbage as soon as the driver goes away.
+ *
+ * The following flags are maintained by the kstat framework:
+ *
+ * KSTAT_FLAG_DORMANT:
+ *
+ * For persistent kstats, indicates that the kstat is in the
+ * dormant state (e.g., the corresponding device is closed).
+ *
+ * KSTAT_FLAG_INVALID:
+ *
+ * This flag is set when a kstat is in a transitional state,
+ * e.g. between kstat_create() and kstat_install().
+ * kstat clients must not attempt to access the kstat's data
+ * if this flag is set.
+ */
+
+#define KSTAT_FLAG_VIRTUAL 0x01
+#define KSTAT_FLAG_VAR_SIZE 0x02
+#define KSTAT_FLAG_WRITABLE 0x04
+#define KSTAT_FLAG_PERSISTENT 0x08
+#define KSTAT_FLAG_DORMANT 0x10
+#define KSTAT_FLAG_INVALID 0x20
+#define KSTAT_FLAG_LONGSTRINGS 0x40
+#define KSTAT_FLAG_NO_HEADERS 0x80
+
+/*
+ * Dynamic update support
+ *
+ * The kstat mechanism allows for an optional ks_update function to update
+ * kstat data. This is useful for drivers where the underlying device
+ * keeps cheap hardware stats, but extraction is expensive. Instead of
+ * constantly keeping the kstat data section up to date, you can supply a
+ * ks_update function which updates the kstat's data section on demand.
+ * To take advantage of this feature, simply set the ks_update field before
+ * calling kstat_install().
+ *
+ * The ks_update function, if supplied, must have the following structure:
+ *
+ * int
+ * foo_kstat_update(kstat_t *ksp, int rw)
+ * {
+ * if (rw == KSTAT_WRITE) {
+ * ... update the native stats from ksp->ks_data;
+ * return EACCES if you don't support this
+ * } else {
+ * ... update ksp->ks_data from the native stats
+ * }
+ * }
+ *
+ * The ks_update return codes are: 0 for success, EACCES if you don't allow
+ * KSTAT_WRITE, and EIO for any other type of error.
+ *
+ * In general, the ks_update function may need to refer to provider-private
+ * data; for example, it may need a pointer to the provider's raw statistics.
+ * The ks_private field is available for this purpose. Its use is entirely
+ * at the provider's discretion.
+ *
+ * All variable-size kstats MUST supply a ks_update routine, which computes
+ * and sets ks_data_size (and ks_ndata if that is meaningful), since these
+ * are needed to perform kstat snapshots (see below).
+ *
+ * No kstat locking should be done inside the ks_update routine. The caller
+ * will already be holding the kstat's ks_lock (to ensure consistent data).
+ */
+
+#define KSTAT_READ 0
+#define KSTAT_WRITE 1
+
+/*
+ * Kstat snapshot
+ *
+ * In order to get a consistent view of a kstat's data, clients must obey
+ * the kstat's locking strategy. However, these clients may need to perform
+ * operations on the data which could cause a fault (e.g. copyout()), or
+ * operations which are simply expensive. Doing so could cause deadlock
+ * (e.g. if you're holding a disk's kstat lock which is ultimately required
+ * to resolve a copyout() fault), performance degradation (since the providers'
+ * activity is serialized at the kstat lock), device timing problems, etc.
+ *
+ * To avoid these problems, kstat data is provided via snapshots. Taking
+ * a snapshot is a simple process: allocate a wired-down kernel buffer,
+ * acquire the kstat's data lock, copy the data into the buffer ("take the
+ * snapshot"), and release the lock. This ensures that the kstat's data lock
+ * will be held as briefly as possible, and that no faults will occur while
+ * the lock is held.
+ *
+ * Normally, the snapshot is taken by default_kstat_snapshot(), which
+ * timestamps the data (sets ks_snaptime), copies it, and does a little
+ * massaging to deal with incomplete transactions on i/o kstats. However,
+ * this routine only works for kstats with contiguous data (the typical case).
+ * If you create a kstat whose data is, say, a linked list, you must provide
+ * your own ks_snapshot routine. The routine you supply must have the
+ * following prototype (replace "foo" with something appropriate):
+ *
+ * int foo_kstat_snapshot(kstat_t *ksp, void *buf, int rw);
+ *
+ * The minimal snapshot routine -- one which copies contiguous data that
+ * doesn't need any massaging -- would be this:
+ *
+ * ksp->ks_snaptime = gethrtime();
+ * if (rw == KSTAT_WRITE)
+ * bcopy(buf, ksp->ks_data, ksp->ks_data_size);
+ * else
+ * bcopy(ksp->ks_data, buf, ksp->ks_data_size);
+ * return (0);
+ *
+ * A more illuminating example is taking a snapshot of a linked list:
+ *
+ * ksp->ks_snaptime = gethrtime();
+ * if (rw == KSTAT_WRITE)
+ * return (EACCES); ... See below ...
+ * for (foo = first_foo; foo; foo = foo->next) {
+ * bcopy((char *) foo, (char *) buf, sizeof (struct foo));
+ * buf = ((struct foo *) buf) + 1;
+ * }
+ * return (0);
+ *
+ * In the example above, we have decided that we don't want to allow
+ * KSTAT_WRITE access, so we return EACCES if this is attempted.
+ *
+ * The key points are:
+ *
+ * (1) ks_snaptime must be set (via gethrtime()) to timestamp the data.
+ * (2) Data gets copied from the kstat to the buffer on KSTAT_READ,
+ * and from the buffer to the kstat on KSTAT_WRITE.
+ * (3) ks_snapshot return values are: 0 for success, EACCES if you
+ * don't allow KSTAT_WRITE, and EIO for any other type of error.
+ *
+ * Named kstats (see section on "Named statistics" below) containing long
+ * strings (KSTAT_DATA_STRING) need special handling. The kstat driver
+ * assumes that all strings are copied into the buffer after the array of
+ * named kstats, and the pointers (KSTAT_NAMED_STR_PTR()) are updated to point
+ * into the copy within the buffer. The default snapshot routine does this,
+ * but overriding routines should contain at least the following:
+ *
+ * if (rw == KSTAT_READ) {
+ * kstat_named_t *knp = buf;
+ * char *end = knp + ksp->ks_ndata;
+ * uint_t i;
+ *
+ * ... Do the regular copy ...
+ * bcopy(ksp->ks_data, buf, sizeof (kstat_named_t) * ksp->ks_ndata);
+ *
+ * for (i = 0; i < ksp->ks_ndata; i++, knp++) {
+ * if (knp[i].data_type == KSTAT_DATA_STRING &&
+ * KSTAT_NAMED_STR_PTR(knp) != NULL) {
+ * bcopy(KSTAT_NAMED_STR_PTR(knp), end,
+ * KSTAT_NAMED_STR_BUFLEN(knp));
+ * KSTAT_NAMED_STR_PTR(knp) = end;
+ * end += KSTAT_NAMED_STR_BUFLEN(knp);
+ * }
+ * }
+ */
+
+/*
+ * Named statistics.
+ *
+ * List of arbitrary name=value statistics.
+ */
+
+typedef struct kstat_named {
+ char name[KSTAT_STRLEN]; /* name of counter */
+ uchar_t data_type; /* data type */
+ union {
+ char c[16]; /* enough for 128-bit ints */
+ int32_t i32;
+ uint32_t ui32;
+ struct {
+ union {
+ char *ptr; /* NULL-term string */
+#if defined(_KERNEL) && defined(_MULTI_DATAMODEL)
+ caddr32_t ptr32;
+#endif
+ char __pad[8]; /* 64-bit padding */
+ } addr;
+ uint32_t len; /* # bytes for strlen + '\0' */
+ } str;
+/*
+ * The int64_t and uint64_t types are not valid for a maximally conformant
+ * 32-bit compilation environment (cc -Xc) using compilers prior to the
+ * introduction of C99 conforming compiler (reference ISO/IEC 9899:1990).
+ * In these cases, the visibility of i64 and ui64 is only permitted for
+ * 64-bit compilation environments or 32-bit non-maximally conformant
+ * C89 or C90 ANSI C compilation environments (cc -Xt and cc -Xa). In the
+ * C99 ANSI C compilation environment, the long long type is supported.
+ * The _INT64_TYPE is defined by the implementation (see sys/int_types.h).
+ */
+#if defined(_INT64_TYPE)
+ int64_t i64;
+ uint64_t ui64;
+#endif
+ long l;
+ ulong_t ul;
+
+ /* These structure members are obsolete */
+
+ longlong_t ll;
+ u_longlong_t ull;
+ float f;
+ double d;
+ } value; /* value of counter */
+} kstat_named_t;
+
+#define KSTAT_DATA_CHAR 0
+#define KSTAT_DATA_INT32 1
+#define KSTAT_DATA_UINT32 2
+#define KSTAT_DATA_INT64 3
+#define KSTAT_DATA_UINT64 4
+
+#if !defined(_LP64)
+#define KSTAT_DATA_LONG KSTAT_DATA_INT32
+#define KSTAT_DATA_ULONG KSTAT_DATA_UINT32
+#else
+#if !defined(_KERNEL)
+#define KSTAT_DATA_LONG KSTAT_DATA_INT64
+#define KSTAT_DATA_ULONG KSTAT_DATA_UINT64
+#else
+#define KSTAT_DATA_LONG 7 /* only visible to the kernel */
+#define KSTAT_DATA_ULONG 8 /* only visible to the kernel */
+#endif /* !_KERNEL */
+#endif /* !_LP64 */
+
+/*
+ * Statistics exporting named kstats with long strings (KSTAT_DATA_STRING)
+ * may not make the assumption that ks_data_size is equal to (ks_ndata * sizeof
+ * (kstat_named_t)). ks_data_size in these cases is equal to the sum of the
+ * amount of space required to store the strings (ie, the sum of
+ * KSTAT_NAMED_STR_BUFLEN() for all KSTAT_DATA_STRING statistics) plus the
+ * space required to store the kstat_named_t's.
+ *
+ * The default update routine will update ks_data_size automatically for
+ * variable-length kstats containing long strings (using the default update
+ * routine only makes sense if the string is the only thing that is changing
+ * in size, and ks_ndata is constant). Fixed-length kstats containing long
+ * strings must explicitly change ks_data_size (after creation but before
+ * initialization) to reflect the correct amount of space required for the
+ * long strings and the kstat_named_t's.
+ */
+#define KSTAT_DATA_STRING 9
+
+/* These types are obsolete */
+
+#define KSTAT_DATA_LONGLONG KSTAT_DATA_INT64
+#define KSTAT_DATA_ULONGLONG KSTAT_DATA_UINT64
+#define KSTAT_DATA_FLOAT 5
+#define KSTAT_DATA_DOUBLE 6
+
+#define KSTAT_NAMED_PTR(kptr) ((kstat_named_t *)(kptr)->ks_data)
+
+/*
+ * Retrieve the pointer of the string contained in the given named kstat.
+ */
+#define KSTAT_NAMED_STR_PTR(knptr) ((knptr)->value.str.addr.ptr)
+
+/*
+ * Retrieve the length of the buffer required to store the string in the given
+ * named kstat.
+ */
+#define KSTAT_NAMED_STR_BUFLEN(knptr) ((knptr)->value.str.len)
+
+/*
+ * Interrupt statistics.
+ *
+ * An interrupt is a hard interrupt (sourced from the hardware device
+ * itself), a soft interrupt (induced by the system via the use of
+ * some system interrupt source), a watchdog interrupt (induced by
+ * a periodic timer call), spurious (an interrupt entry point was
+ * entered but there was no interrupt condition to service),
+ * or multiple service (an interrupt condition was detected and
+ * serviced just prior to returning from any of the other types).
+ *
+ * Measurement of the spurious class of interrupts is useful for
+ * autovectored devices in order to pinpoint any interrupt latency
+ * problems in a particular system configuration.
+ *
+ * Devices that have more than one interrupt of the same
+ * type should use multiple structures.
+ */
+
+#define KSTAT_INTR_HARD 0
+#define KSTAT_INTR_SOFT 1
+#define KSTAT_INTR_WATCHDOG 2
+#define KSTAT_INTR_SPURIOUS 3
+#define KSTAT_INTR_MULTSVC 4
+
+#define KSTAT_NUM_INTRS 5
+
+typedef struct kstat_intr {
+ uint_t intrs[KSTAT_NUM_INTRS]; /* interrupt counters */
+} kstat_intr_t;
+
+#define KSTAT_INTR_PTR(kptr) ((kstat_intr_t *)(kptr)->ks_data)
+
+/*
+ * I/O statistics.
+ */
+
+typedef struct kstat_io {
+
+ /*
+ * Basic counters.
+ *
+ * The counters should be updated at the end of service
+ * (e.g., just prior to calling biodone()).
+ */
+
+ u_longlong_t nread; /* number of bytes read */
+ u_longlong_t nwritten; /* number of bytes written */
+ uint_t reads; /* number of read operations */
+ uint_t writes; /* number of write operations */
+
+ /*
+ * Accumulated time and queue length statistics.
+ *
+ * Accumulated time statistics are kept as a running sum
+ * of "active" time. Queue length statistics are kept as a
+ * running sum of the product of queue length and elapsed time
+ * at that length -- i.e., a Riemann sum for queue length
+ * integrated against time. (You can also think of the active time
+ * as a Riemann sum, for the boolean function (queue_length > 0)
+ * integrated against time, or you can think of it as the
+ * Lebesgue measure of the set on which queue_length > 0.)
+ *
+ * ^
+ * | _________
+ * 8 | i4 |
+ * | | |
+ * Queue 6 | |
+ * Length | _________ | |
+ * 4 | i2 |_______| |
+ * | | i3 |
+ * 2_______| |
+ * | i1 |
+ * |_______________________________|
+ * Time-> t1 t2 t3 t4
+ *
+ * At each change of state (entry or exit from the queue),
+ * we add the elapsed time (since the previous state change)
+ * to the active time if the queue length was non-zero during
+ * that interval; and we add the product of the elapsed time
+ * times the queue length to the running length*time sum.
+ *
+ * This method is generalizable to measuring residency
+ * in any defined system: instead of queue lengths, think
+ * of "outstanding RPC calls to server X".
+ *
+ * A large number of I/O subsystems have at least two basic
+ * "lists" of transactions they manage: one for transactions
+ * that have been accepted for processing but for which processing
+ * has yet to begin, and one for transactions which are actively
+ * being processed (but not done). For this reason, two cumulative
+ * time statistics are defined here: wait (pre-service) time,
+ * and run (service) time.
+ *
+ * All times are 64-bit nanoseconds (hrtime_t), as returned by
+ * gethrtime().
+ *
+ * The units of cumulative busy time are accumulated nanoseconds.
+ * The units of cumulative length*time products are elapsed time
+ * times queue length.
+ *
+ * Updates to the fields below are performed implicitly by calls to
+ * these five functions:
+ *
+ * kstat_waitq_enter()
+ * kstat_waitq_exit()
+ * kstat_runq_enter()
+ * kstat_runq_exit()
+ *
+ * kstat_waitq_to_runq() (see below)
+ * kstat_runq_back_to_waitq() (see below)
+ *
+ * Since kstat_waitq_exit() is typically followed immediately
+ * by kstat_runq_enter(), there is a single kstat_waitq_to_runq()
+ * function which performs both operations. This is a performance
+ * win since only one timestamp is required.
+ *
+ * In some instances, it may be necessary to move a request from
+ * the run queue back to the wait queue, e.g. for write throttling.
+ * For these situations, call kstat_runq_back_to_waitq().
+ *
+ * These fields should never be updated by any other means.
+ */
+
+ hrtime_t wtime; /* cumulative wait (pre-service) time */
+ hrtime_t wlentime; /* cumulative wait length*time product */
+ hrtime_t wlastupdate; /* last time wait queue changed */
+ hrtime_t rtime; /* cumulative run (service) time */
+ hrtime_t rlentime; /* cumulative run length*time product */
+ hrtime_t rlastupdate; /* last time run queue changed */
+
+ uint_t wcnt; /* count of elements in wait state */
+ uint_t rcnt; /* count of elements in run state */
+
+} kstat_io_t;
+
+#define KSTAT_IO_PTR(kptr) ((kstat_io_t *)(kptr)->ks_data)
+
+/*
+ * Event timer statistics - cumulative elapsed time and number of events.
+ *
+ * Updates to these fields are performed implicitly by calls to
+ * kstat_timer_start() and kstat_timer_stop().
+ */
+
+typedef struct kstat_timer {
+ char name[KSTAT_STRLEN]; /* event name */
+ uchar_t resv; /* reserved */
+ u_longlong_t num_events; /* number of events */
+ hrtime_t elapsed_time; /* cumulative elapsed time */
+ hrtime_t min_time; /* shortest event duration */
+ hrtime_t max_time; /* longest event duration */
+ hrtime_t start_time; /* previous event start time */
+ hrtime_t stop_time; /* previous event stop time */
+} kstat_timer_t;
+
+#define KSTAT_TIMER_PTR(kptr) ((kstat_timer_t *)(kptr)->ks_data)
+
+#if defined(_KERNEL)
+
+#include <sys/t_lock.h>
+
+extern kid_t kstat_chain_id; /* bumped at each state change */
+extern void kstat_init(void); /* initialize kstat framework */
+
+/*
+ * Adding and deleting kstats.
+ *
+ * The typical sequence to add a kstat is:
+ *
+ * ksp = kstat_create(module, instance, name, class, type, ndata, flags);
+ * if (ksp) {
+ * ... provider initialization, if necessary
+ * kstat_install(ksp);
+ * }
+ *
+ * There are three logically distinct steps here:
+ *
+ * Step 1: System Initialization (kstat_create)
+ *
+ * kstat_create() performs system initialization. kstat_create()
+ * allocates memory for the entire kstat (header plus data), initializes
+ * all header fields, initializes the data section to all zeroes, assigns
+ * a unique KID, and puts the kstat onto the system's kstat chain.
+ * The returned kstat is marked invalid (KSTAT_FLAG_INVALID is set),
+ * because the provider (caller) has not yet had a chance to initialize
+ * the data section.
+ *
+ * By default, kstats are exported to all zones on the system. A kstat may be
+ * created via kstat_create_zone() to specify a zone to which the statistics
+ * should be exported. kstat_zone_add() may be used to specify additional
+ * zones to which the statistics are to be exported.
+ *
+ * Step 2: Provider Initialization
+ *
+ * The provider performs any necessary initialization of the data section,
+ * e.g. setting the name fields in a KSTAT_TYPE_NAMED. Virtual kstats set
+ * the ks_data field at this time. The provider may also set the ks_update,
+ * ks_snapshot, ks_private, and ks_lock fields if necessary.
+ *
+ * Step 3: Installation (kstat_install)
+ *
+ * Once the kstat is completely initialized, kstat_install() clears the
+ * INVALID flag, thus making the kstat accessible to the outside world.
+ * kstat_install() also clears the DORMANT flag for persistent kstats.
+ *
+ * Removing a kstat from the system
+ *
+ * kstat_delete(ksp) removes ksp from the kstat chain and frees all
+ * associated system resources. NOTE: When you call kstat_delete(),
+ * you must NOT be holding that kstat's ks_lock. Otherwise, you may
+ * deadlock with a kstat reader.
+ *
+ * Persistent kstats
+ *
+ * From the provider's point of view, persistence is transparent. The only
+ * difference between ephemeral (normal) kstats and persistent kstats
+ * is that you pass KSTAT_FLAG_PERSISTENT to kstat_create(). Magically,
+ * this has the effect of making your data visible even when you're
+ * not home. Persistence is important to tools like iostat, which want
+ * to get a meaningful picture of disk activity. Without persistence,
+ * raw disk i/o statistics could never accumulate: they would come and
+ * go with each open/close of the raw device.
+ *
+ * The magic of persistence works by slightly altering the behavior of
+ * kstat_create() and kstat_delete(). The first call to kstat_create()
+ * creates a new kstat, as usual. However, kstat_delete() does not
+ * actually delete the kstat: it performs one final update of the data
+ * (i.e., calls the ks_update routine), marks the kstat as dormant, and
+ * sets the ks_lock, ks_update, ks_private, and ks_snapshot fields back
+ * to their default values (since they might otherwise point to garbage,
+ * e.g. if the provider is going away). kstat clients can still access
+ * the dormant kstat just like a live kstat; they just continue to see
+ * the final data values as long as the kstat remains dormant.
+ * All subsequent kstat_create() calls simply find the already-existing,
+ * dormant kstat and return a pointer to it, without altering any fields.
+ * The provider then performs its usual initialization sequence, and
+ * calls kstat_install(). kstat_install() uses the old data values to
+ * initialize the native data (i.e., ks_update is called with KSTAT_WRITE),
+ * thus making it seem like you were never gone.
+ */
+
+extern kstat_t *kstat_create(const char *, int, const char *, const char *,
+ uchar_t, uint_t, uchar_t);
+extern kstat_t *kstat_create_zone(const char *, int, const char *,
+ const char *, uchar_t, uint_t, uchar_t, zoneid_t);
+extern void kstat_install(kstat_t *);
+extern void kstat_delete(kstat_t *);
+extern void kstat_named_setstr(kstat_named_t *knp, const char *src);
+extern void kstat_set_string(char *, const char *);
+extern void kstat_delete_byname(const char *, int, const char *);
+extern void kstat_delete_byname_zone(const char *, int, const char *, zoneid_t);
+extern void kstat_named_init(kstat_named_t *, const char *, uchar_t);
+extern void kstat_timer_init(kstat_timer_t *, const char *);
+extern void kstat_waitq_enter(kstat_io_t *);
+extern void kstat_waitq_exit(kstat_io_t *);
+extern void kstat_runq_enter(kstat_io_t *);
+extern void kstat_runq_exit(kstat_io_t *);
+extern void kstat_waitq_to_runq(kstat_io_t *);
+extern void kstat_runq_back_to_waitq(kstat_io_t *);
+extern void kstat_timer_start(kstat_timer_t *);
+extern void kstat_timer_stop(kstat_timer_t *);
+
+extern void kstat_zone_add(kstat_t *, zoneid_t);
+extern void kstat_zone_remove(kstat_t *, zoneid_t);
+extern int kstat_zone_find(kstat_t *, zoneid_t);
+
+extern kstat_t *kstat_hold_bykid(kid_t kid, zoneid_t);
+extern kstat_t *kstat_hold_byname(const char *, int, const char *, zoneid_t);
+extern void kstat_rele(kstat_t *);
+
+#endif /* defined(_KERNEL) */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _SYS_KSTAT_H */
diff --git a/lib/libspl/include/sys/list.h b/lib/libspl/include/sys/list.h
new file mode 100644
index 000000000000..6db92ed42955
--- /dev/null
+++ b/lib/libspl/include/sys/list.h
@@ -0,0 +1,65 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _SYS_LIST_H
+#define _SYS_LIST_H
+
+#include <sys/list_impl.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct list_node list_node_t;
+typedef struct list list_t;
+
+void list_create(list_t *, size_t, size_t);
+void list_destroy(list_t *);
+
+void list_insert_after(list_t *, void *, void *);
+void list_insert_before(list_t *, void *, void *);
+void list_insert_head(list_t *, void *);
+void list_insert_tail(list_t *, void *);
+void list_remove(list_t *, void *);
+void *list_remove_head(list_t *);
+void *list_remove_tail(list_t *);
+void list_move_tail(list_t *, list_t *);
+
+void *list_head(list_t *);
+void *list_tail(list_t *);
+void *list_next(list_t *, void *);
+void *list_prev(list_t *, void *);
+int list_is_empty(list_t *);
+
+void list_link_init(list_node_t *);
+void list_link_replace(list_node_t *, list_node_t *);
+
+int list_link_active(list_node_t *);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _SYS_LIST_H */
diff --git a/lib/libspl/include/sys/list_impl.h b/lib/libspl/include/sys/list_impl.h
new file mode 100644
index 000000000000..b5655b972c11
--- /dev/null
+++ b/lib/libspl/include/sys/list_impl.h
@@ -0,0 +1,51 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2003 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _SYS_LIST_IMPL_H
+#define _SYS_LIST_IMPL_H
+
+#include <sys/types.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct list_node {
+ struct list_node *next;
+ struct list_node *prev;
+};
+
+struct list {
+ size_t list_size;
+ size_t list_offset;
+ struct list_node list_head;
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _SYS_LIST_IMPL_H */
diff --git a/lib/libspl/include/sys/mhd.h b/lib/libspl/include/sys/mhd.h
new file mode 100644
index 000000000000..fcc062d51c84
--- /dev/null
+++ b/lib/libspl/include/sys/mhd.h
@@ -0,0 +1,159 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2004 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _SYS_MHD_H
+#define _SYS_MHD_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Definitions for multi-host device I/O control commands
+ */
+#define MHIOC ('M'<<8)
+#define MHIOCENFAILFAST (MHIOC|1)
+#define MHIOCTKOWN (MHIOC|2)
+#define MHIOCRELEASE (MHIOC|3)
+#define MHIOCSTATUS (MHIOC|4)
+#define MHIOCGRP_INKEYS (MHIOC|5)
+#define MHIOCGRP_INRESV (MHIOC|6)
+#define MHIOCGRP_REGISTER (MHIOC|7)
+#define MHIOCGRP_RESERVE (MHIOC|8)
+#define MHIOCGRP_PREEMPTANDABORT (MHIOC|9)
+#define MHIOCGRP_PREEMPT (MHIOC|10)
+#define MHIOCGRP_CLEAR (MHIOC|11)
+#define MHIOCGRP_REGISTERANDIGNOREKEY (MHIOC|14)
+#define MHIOCQRESERVE (MHIOC|12)
+#define MHIOCREREGISTERDEVID (MHIOC|13)
+
+/*
+ * Following is the structure to specify the delay parameters in
+ * milliseconds, via the MHIOCTKOWN ioctl.
+ */
+struct mhioctkown {
+ int reinstate_resv_delay;
+ int min_ownership_delay;
+ int max_ownership_delay;
+};
+
+#define MHIOC_RESV_KEY_SIZE 8
+typedef struct mhioc_resv_key {
+ uchar_t key[MHIOC_RESV_KEY_SIZE];
+} mhioc_resv_key_t;
+
+typedef struct mhioc_key_list {
+ uint32_t listsize;
+ uint32_t listlen;
+ mhioc_resv_key_t *list;
+} mhioc_key_list_t;
+
+typedef struct mhioc_inkeys {
+ uint32_t generation;
+ mhioc_key_list_t *li;
+} mhioc_inkeys_t;
+
+#if defined(_SYSCALL32)
+struct mhioc_key_list32 {
+ uint32_t listsize;
+ uint32_t listlen;
+ caddr32_t list;
+} mhioc_key_list32_t;
+
+struct mhioc_inkeys32 {
+ uint32_t generation;
+ caddr32_t li;
+} mhioc_inkeys32_t;
+#endif
+
+typedef struct mhioc_resv_desc {
+ mhioc_resv_key_t key;
+ uint8_t type;
+ uint8_t scope;
+ uint32_t scope_specific_addr;
+} mhioc_resv_desc_t;
+
+typedef struct mhioc_resv_desc_list {
+ uint32_t listsize;
+ uint32_t listlen;
+ mhioc_resv_desc_t *list;
+} mhioc_resv_desc_list_t;
+
+typedef struct mhioc_inresvs {
+ uint32_t generation;
+ mhioc_resv_desc_list_t *li;
+} mhioc_inresvs_t;
+
+#if defined(_SYSCALL32)
+struct mhioc_resv_desc_list32 {
+ uint32_t listsize;
+ uint32_t listlen;
+ caddr32_t list;
+} mhioc_resv_desc_list32_t;
+
+typedef struct mhioc_inresvs32 {
+ uint32_t generation;
+ caddr32_t li;
+} mhioc_inresvs32_t;
+#endif
+
+typedef struct mhioc_register {
+ mhioc_resv_key_t oldkey;
+ mhioc_resv_key_t newkey;
+ boolean_t aptpl; /* True if persistent across power failures */
+} mhioc_register_t;
+
+typedef struct mhioc_preemptandabort {
+ mhioc_resv_desc_t resvdesc;
+ mhioc_resv_key_t victim_key;
+} mhioc_preemptandabort_t;
+
+typedef struct mhioc_registerandignorekey {
+ mhioc_resv_key_t newkey;
+ boolean_t aptpl; /* True if persistent across power failures */
+} mhioc_registerandignorekey_t;
+
+/*
+ * SCSI-3 PGR Reservation Type Codes. Codes with the _OBSOLETE suffix
+ * have been removed from the SCSI3 PGR standard.
+ */
+#define SCSI3_RESV_READSHARED_OBSOLETE 0
+#define SCSI3_RESV_WRITEEXCLUSIVE 1
+#define SCSI3_RESV_READEXCLUSIVE_OBSOLETE 2
+#define SCSI3_RESV_EXCLUSIVEACCESS 3
+#define SCSI3_RESV_SHAREDACCESS_OBSOLETE 4
+#define SCSI3_RESV_WRITEEXCLUSIVEREGISTRANTSONLY 5
+#define SCSI3_RESV_EXCLUSIVEACCESSREGISTRANTSONLY 6
+
+#define SCSI3_SCOPE_LOGICALUNIT 0
+#define SCSI3_SCOPE_EXTENT_OBSOLETE 1
+#define SCSI3_SCOPE_ELEMENT 2
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _SYS_MHD_H */
diff --git a/lib/libspl/include/sys/mkdev.h b/lib/libspl/include/sys/mkdev.h
new file mode 100644
index 000000000000..5978de65de50
--- /dev/null
+++ b/lib/libspl/include/sys/mkdev.h
@@ -0,0 +1,30 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _LIBSPL_SYS_MKDEV_H
+#define _LIBSPL_SYS_MKDEV_H
+
+#endif
diff --git a/lib/libspl/include/sys/policy.h b/lib/libspl/include/sys/policy.h
new file mode 100644
index 000000000000..2f695b35cd0b
--- /dev/null
+++ b/lib/libspl/include/sys/policy.h
@@ -0,0 +1,26 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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
+ */
+
+#ifndef _LIBSYS_SYS_POLICY_H
+#define _LIBSYS_SYS_POLICY_H
+
+#endif /* _LIBSYS_SYS_POLICY_H */
diff --git a/lib/libspl/include/sys/poll.h b/lib/libspl/include/sys/poll.h
new file mode 100644
index 000000000000..6ab0bddc0be5
--- /dev/null
+++ b/lib/libspl/include/sys/poll.h
@@ -0,0 +1,41 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2017 Zettabyte Software, LLC. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+/*
+ * Compiling against musl correctly points out that including sys/poll.h is
+ * disallowed by the Single UNIX Specification when building in userspace. We
+ * implement a dummy header to redirect the include to the proper header.
+ * However, glibc, klibc and uclibc break this shim by including sys/poll.h
+ * from poll.h, so we add explicit exceptions for them.
+ */
+#ifndef _LIBSPL_SYS_POLL_H
+#define _LIBSPL_SYS_POLL_H
+#if defined(__GLIBC__) || defined(__KLIBC__) || defined(__UCLIBC__)
+#include_next <sys/poll.h>
+#else
+#include <poll.h>
+#endif
+#endif /* _LIBSPL_SYS_POLL_H */
diff --git a/lib/libspl/include/sys/priv.h b/lib/libspl/include/sys/priv.h
new file mode 100644
index 000000000000..76c76d1830c2
--- /dev/null
+++ b/lib/libspl/include/sys/priv.h
@@ -0,0 +1,30 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2006 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _LIBSPL_SYS_PRIV_H
+#define _LIBSPL_SYS_PRIV_H
+
+#endif
diff --git a/lib/libspl/include/sys/processor.h b/lib/libspl/include/sys/processor.h
new file mode 100644
index 000000000000..78e95d01f1b5
--- /dev/null
+++ b/lib/libspl/include/sys/processor.h
@@ -0,0 +1,34 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2007 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _LIBSPL_SYS_PROCESSOR_H
+#define _LIBSPL_SYS_PROCESSOR_H
+
+#define getcpuid() (-1)
+
+typedef int processorid_t;
+
+#endif
diff --git a/lib/libspl/include/sys/sha2.h b/lib/libspl/include/sys/sha2.h
new file mode 100644
index 000000000000..e2f66d225e25
--- /dev/null
+++ b/lib/libspl/include/sys/sha2.h
@@ -0,0 +1,151 @@
+/*
+ * 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 2009 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+/* Copyright 2013 Saso Kiselkov. All rights reserved. */
+
+#ifndef _SYS_SHA2_H
+#define _SYS_SHA2_H
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define SHA2_HMAC_MIN_KEY_LEN 1 /* SHA2-HMAC min key length in bytes */
+#define SHA2_HMAC_MAX_KEY_LEN INT_MAX /* SHA2-HMAC max key length in bytes */
+
+#define SHA256_DIGEST_LENGTH 32 /* SHA256 digest length in bytes */
+#define SHA384_DIGEST_LENGTH 48 /* SHA384 digest length in bytes */
+#define SHA512_DIGEST_LENGTH 64 /* SHA512 digest length in bytes */
+
+/* Truncated versions of SHA-512 according to FIPS-180-4, section 5.3.6 */
+#define SHA512_224_DIGEST_LENGTH 28 /* SHA512/224 digest length */
+#define SHA512_256_DIGEST_LENGTH 32 /* SHA512/256 digest length */
+
+#define SHA256_HMAC_BLOCK_SIZE 64 /* SHA256-HMAC block size */
+#define SHA512_HMAC_BLOCK_SIZE 128 /* SHA512-HMAC block size */
+
+#define SHA256 0
+#define SHA256_HMAC 1
+#define SHA256_HMAC_GEN 2
+#define SHA384 3
+#define SHA384_HMAC 4
+#define SHA384_HMAC_GEN 5
+#define SHA512 6
+#define SHA512_HMAC 7
+#define SHA512_HMAC_GEN 8
+#define SHA512_224 9
+#define SHA512_256 10
+
+/*
+ * SHA2 context.
+ * The contents of this structure are a private interface between the
+ * Init/Update/Final calls of the functions defined below.
+ * Callers must never attempt to read or write any of the fields
+ * in this structure directly.
+ */
+typedef struct {
+ uint32_t algotype; /* Algorithm Type */
+
+ /* state (ABCDEFGH) */
+ union {
+ uint32_t s32[8]; /* for SHA256 */
+ uint64_t s64[8]; /* for SHA384/512 */
+ } state;
+ /* number of bits */
+ union {
+ uint32_t c32[2]; /* for SHA256 , modulo 2^64 */
+ uint64_t c64[2]; /* for SHA384/512, modulo 2^128 */
+ } count;
+ union {
+ uint8_t buf8[128]; /* undigested input */
+ uint32_t buf32[32]; /* realigned input */
+ uint64_t buf64[16]; /* realigned input */
+ } buf_un;
+} SHA2_CTX;
+
+typedef SHA2_CTX SHA256_CTX;
+typedef SHA2_CTX SHA384_CTX;
+typedef SHA2_CTX SHA512_CTX;
+
+extern void SHA256Init(SHA256_CTX *);
+
+extern void SHA256Update(SHA256_CTX *, const void *, size_t);
+
+extern void SHA256Final(void *, SHA256_CTX *);
+
+extern void SHA384Init(SHA384_CTX *);
+
+extern void SHA384Update(SHA384_CTX *, const void *, size_t);
+
+extern void SHA384Final(void *, SHA384_CTX *);
+
+extern void SHA512Init(SHA512_CTX *);
+
+extern void SHA512Update(SHA512_CTX *, const void *, size_t);
+
+extern void SHA512Final(void *, SHA512_CTX *);
+
+extern void SHA2Init(uint64_t mech, SHA2_CTX *);
+
+extern void SHA2Update(SHA2_CTX *, const void *, size_t);
+
+extern void SHA2Final(void *, SHA2_CTX *);
+
+#ifdef _SHA2_IMPL
+/*
+ * The following types/functions are all private to the implementation
+ * of the SHA2 functions and must not be used by consumers of the interface
+ */
+
+/*
+ * List of support mechanisms in this module.
+ *
+ * It is important to note that in the module, division or modulus calculations
+ * are used on the enumerated type to determine which mechanism is being used;
+ * therefore, changing the order or additional mechanisms should be done
+ * carefully
+ */
+typedef enum sha2_mech_type {
+ SHA256_MECH_INFO_TYPE, /* SUN_CKM_SHA256 */
+ SHA256_HMAC_MECH_INFO_TYPE, /* SUN_CKM_SHA256_HMAC */
+ SHA256_HMAC_GEN_MECH_INFO_TYPE, /* SUN_CKM_SHA256_HMAC_GENERAL */
+ SHA384_MECH_INFO_TYPE, /* SUN_CKM_SHA384 */
+ SHA384_HMAC_MECH_INFO_TYPE, /* SUN_CKM_SHA384_HMAC */
+ SHA384_HMAC_GEN_MECH_INFO_TYPE, /* SUN_CKM_SHA384_HMAC_GENERAL */
+ SHA512_MECH_INFO_TYPE, /* SUN_CKM_SHA512 */
+ SHA512_HMAC_MECH_INFO_TYPE, /* SUN_CKM_SHA512_HMAC */
+ SHA512_HMAC_GEN_MECH_INFO_TYPE, /* SUN_CKM_SHA512_HMAC_GENERAL */
+ SHA512_224_MECH_INFO_TYPE, /* SUN_CKM_SHA512_224 */
+ SHA512_256_MECH_INFO_TYPE /* SUN_CKM_SHA512_256 */
+} sha2_mech_type_t;
+
+#endif /* _SHA2_IMPL */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _SYS_SHA2_H */
diff --git a/lib/libspl/include/sys/simd.h b/lib/libspl/include/sys/simd.h
new file mode 100644
index 000000000000..dceedb698fe0
--- /dev/null
+++ b/lib/libspl/include/sys/simd.h
@@ -0,0 +1,502 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2006 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _LIBSPL_SYS_SIMD_H
+#define _LIBSPL_SYS_SIMD_H
+
+#include <sys/isa_defs.h>
+#include <sys/types.h>
+
+#if defined(__x86)
+#include <cpuid.h>
+
+#define kfpu_allowed() 1
+#define kfpu_begin() do {} while (0)
+#define kfpu_end() do {} while (0)
+#define kfpu_init() 0
+#define kfpu_fini() ((void) 0)
+
+/*
+ * CPUID feature tests for user-space.
+ *
+ * x86 registers used implicitly by CPUID
+ */
+typedef enum cpuid_regs {
+ EAX = 0,
+ EBX,
+ ECX,
+ EDX,
+ CPUID_REG_CNT = 4
+} cpuid_regs_t;
+
+/*
+ * List of instruction sets identified by CPUID
+ */
+typedef enum cpuid_inst_sets {
+ SSE = 0,
+ SSE2,
+ SSE3,
+ SSSE3,
+ SSE4_1,
+ SSE4_2,
+ OSXSAVE,
+ AVX,
+ AVX2,
+ BMI1,
+ BMI2,
+ AVX512F,
+ AVX512CD,
+ AVX512DQ,
+ AVX512BW,
+ AVX512IFMA,
+ AVX512VBMI,
+ AVX512PF,
+ AVX512ER,
+ AVX512VL,
+ AES,
+ PCLMULQDQ,
+ MOVBE
+} cpuid_inst_sets_t;
+
+/*
+ * Instruction set descriptor.
+ */
+typedef struct cpuid_feature_desc {
+ uint32_t leaf; /* CPUID leaf */
+ uint32_t subleaf; /* CPUID sub-leaf */
+ uint32_t flag; /* bit mask of the feature */
+ cpuid_regs_t reg; /* which CPUID return register to test */
+} cpuid_feature_desc_t;
+
+#define _AVX512F_BIT (1U << 16)
+#define _AVX512CD_BIT (_AVX512F_BIT | (1U << 28))
+#define _AVX512DQ_BIT (_AVX512F_BIT | (1U << 17))
+#define _AVX512BW_BIT (_AVX512F_BIT | (1U << 30))
+#define _AVX512IFMA_BIT (_AVX512F_BIT | (1U << 21))
+#define _AVX512VBMI_BIT (1U << 1) /* AVX512F_BIT is on another leaf */
+#define _AVX512PF_BIT (_AVX512F_BIT | (1U << 26))
+#define _AVX512ER_BIT (_AVX512F_BIT | (1U << 27))
+#define _AVX512VL_BIT (1U << 31) /* if used also check other levels */
+#define _AES_BIT (1U << 25)
+#define _PCLMULQDQ_BIT (1U << 1)
+#define _MOVBE_BIT (1U << 22)
+
+/*
+ * Descriptions of supported instruction sets
+ */
+static const cpuid_feature_desc_t cpuid_features[] = {
+ [SSE] = {1U, 0U, 1U << 25, EDX },
+ [SSE2] = {1U, 0U, 1U << 26, EDX },
+ [SSE3] = {1U, 0U, 1U << 0, ECX },
+ [SSSE3] = {1U, 0U, 1U << 9, ECX },
+ [SSE4_1] = {1U, 0U, 1U << 19, ECX },
+ [SSE4_2] = {1U, 0U, 1U << 20, ECX },
+ [OSXSAVE] = {1U, 0U, 1U << 27, ECX },
+ [AVX] = {1U, 0U, 1U << 28, ECX },
+ [AVX2] = {7U, 0U, 1U << 5, EBX },
+ [BMI1] = {7U, 0U, 1U << 3, EBX },
+ [BMI2] = {7U, 0U, 1U << 8, EBX },
+ [AVX512F] = {7U, 0U, _AVX512F_BIT, EBX },
+ [AVX512CD] = {7U, 0U, _AVX512CD_BIT, EBX },
+ [AVX512DQ] = {7U, 0U, _AVX512DQ_BIT, EBX },
+ [AVX512BW] = {7U, 0U, _AVX512BW_BIT, EBX },
+ [AVX512IFMA] = {7U, 0U, _AVX512IFMA_BIT, EBX },
+ [AVX512VBMI] = {7U, 0U, _AVX512VBMI_BIT, ECX },
+ [AVX512PF] = {7U, 0U, _AVX512PF_BIT, EBX },
+ [AVX512ER] = {7U, 0U, _AVX512ER_BIT, EBX },
+ [AVX512VL] = {7U, 0U, _AVX512ER_BIT, EBX },
+ [AES] = {1U, 0U, _AES_BIT, ECX },
+ [PCLMULQDQ] = {1U, 0U, _PCLMULQDQ_BIT, ECX },
+ [MOVBE] = {1U, 0U, _MOVBE_BIT, ECX },
+};
+
+/*
+ * Check if OS supports AVX and AVX2 by checking XCR0
+ * Only call this function if CPUID indicates that AVX feature is
+ * supported by the CPU, otherwise it might be an illegal instruction.
+ */
+static inline uint64_t
+xgetbv(uint32_t index)
+{
+ uint32_t eax, edx;
+ /* xgetbv - instruction byte code */
+ __asm__ __volatile__(".byte 0x0f; .byte 0x01; .byte 0xd0"
+ : "=a" (eax), "=d" (edx)
+ : "c" (index));
+
+ return ((((uint64_t)edx)<<32) | (uint64_t)eax);
+}
+
+/*
+ * Check if CPU supports a feature
+ */
+static inline boolean_t
+__cpuid_check_feature(const cpuid_feature_desc_t *desc)
+{
+ uint32_t r[CPUID_REG_CNT];
+
+ if (__get_cpuid_max(0, NULL) >= desc->leaf) {
+ /*
+ * __cpuid_count is needed to properly check
+ * for AVX2. It is a macro, so return parameters
+ * are passed by value.
+ */
+ __cpuid_count(desc->leaf, desc->subleaf,
+ r[EAX], r[EBX], r[ECX], r[EDX]);
+ return ((r[desc->reg] & desc->flag) == desc->flag);
+ }
+ return (B_FALSE);
+}
+
+#define CPUID_FEATURE_CHECK(name, id) \
+static inline boolean_t \
+__cpuid_has_ ## name(void) \
+{ \
+ return (__cpuid_check_feature(&cpuid_features[id])); \
+}
+
+/*
+ * Define functions for user-space CPUID features testing
+ */
+CPUID_FEATURE_CHECK(sse, SSE);
+CPUID_FEATURE_CHECK(sse2, SSE2);
+CPUID_FEATURE_CHECK(sse3, SSE3);
+CPUID_FEATURE_CHECK(ssse3, SSSE3);
+CPUID_FEATURE_CHECK(sse4_1, SSE4_1);
+CPUID_FEATURE_CHECK(sse4_2, SSE4_2);
+CPUID_FEATURE_CHECK(avx, AVX);
+CPUID_FEATURE_CHECK(avx2, AVX2);
+CPUID_FEATURE_CHECK(osxsave, OSXSAVE);
+CPUID_FEATURE_CHECK(bmi1, BMI1);
+CPUID_FEATURE_CHECK(bmi2, BMI2);
+CPUID_FEATURE_CHECK(avx512f, AVX512F);
+CPUID_FEATURE_CHECK(avx512cd, AVX512CD);
+CPUID_FEATURE_CHECK(avx512dq, AVX512DQ);
+CPUID_FEATURE_CHECK(avx512bw, AVX512BW);
+CPUID_FEATURE_CHECK(avx512ifma, AVX512IFMA);
+CPUID_FEATURE_CHECK(avx512vbmi, AVX512VBMI);
+CPUID_FEATURE_CHECK(avx512pf, AVX512PF);
+CPUID_FEATURE_CHECK(avx512er, AVX512ER);
+CPUID_FEATURE_CHECK(avx512vl, AVX512VL);
+CPUID_FEATURE_CHECK(aes, AES);
+CPUID_FEATURE_CHECK(pclmulqdq, PCLMULQDQ);
+CPUID_FEATURE_CHECK(movbe, MOVBE);
+
+/*
+ * Detect register set support
+ */
+static inline boolean_t
+__simd_state_enabled(const uint64_t state)
+{
+ boolean_t has_osxsave;
+ uint64_t xcr0;
+
+ has_osxsave = __cpuid_has_osxsave();
+ if (!has_osxsave)
+ return (B_FALSE);
+
+ xcr0 = xgetbv(0);
+ return ((xcr0 & state) == state);
+}
+
+#define _XSTATE_SSE_AVX (0x2 | 0x4)
+#define _XSTATE_AVX512 (0xE0 | _XSTATE_SSE_AVX)
+
+#define __ymm_enabled() __simd_state_enabled(_XSTATE_SSE_AVX)
+#define __zmm_enabled() __simd_state_enabled(_XSTATE_AVX512)
+
+/*
+ * Check if SSE instruction set is available
+ */
+static inline boolean_t
+zfs_sse_available(void)
+{
+ return (__cpuid_has_sse());
+}
+
+/*
+ * Check if SSE2 instruction set is available
+ */
+static inline boolean_t
+zfs_sse2_available(void)
+{
+ return (__cpuid_has_sse2());
+}
+
+/*
+ * Check if SSE3 instruction set is available
+ */
+static inline boolean_t
+zfs_sse3_available(void)
+{
+ return (__cpuid_has_sse3());
+}
+
+/*
+ * Check if SSSE3 instruction set is available
+ */
+static inline boolean_t
+zfs_ssse3_available(void)
+{
+ return (__cpuid_has_ssse3());
+}
+
+/*
+ * Check if SSE4.1 instruction set is available
+ */
+static inline boolean_t
+zfs_sse4_1_available(void)
+{
+ return (__cpuid_has_sse4_1());
+}
+
+/*
+ * Check if SSE4.2 instruction set is available
+ */
+static inline boolean_t
+zfs_sse4_2_available(void)
+{
+ return (__cpuid_has_sse4_2());
+}
+
+/*
+ * Check if AVX instruction set is available
+ */
+static inline boolean_t
+zfs_avx_available(void)
+{
+ return (__cpuid_has_avx() && __ymm_enabled());
+}
+
+/*
+ * Check if AVX2 instruction set is available
+ */
+static inline boolean_t
+zfs_avx2_available(void)
+{
+ return (__cpuid_has_avx2() && __ymm_enabled());
+}
+
+/*
+ * Check if BMI1 instruction set is available
+ */
+static inline boolean_t
+zfs_bmi1_available(void)
+{
+ return (__cpuid_has_bmi1());
+}
+
+/*
+ * Check if BMI2 instruction set is available
+ */
+static inline boolean_t
+zfs_bmi2_available(void)
+{
+ return (__cpuid_has_bmi2());
+}
+
+/*
+ * Check if AES instruction set is available
+ */
+static inline boolean_t
+zfs_aes_available(void)
+{
+ return (__cpuid_has_aes());
+}
+
+/*
+ * Check if PCLMULQDQ instruction set is available
+ */
+static inline boolean_t
+zfs_pclmulqdq_available(void)
+{
+ return (__cpuid_has_pclmulqdq());
+}
+
+/*
+ * Check if MOVBE instruction is available
+ */
+static inline boolean_t
+zfs_movbe_available(void)
+{
+ return (__cpuid_has_movbe());
+}
+
+/*
+ * AVX-512 family of instruction sets:
+ *
+ * AVX512F Foundation
+ * AVX512CD Conflict Detection Instructions
+ * AVX512ER Exponential and Reciprocal Instructions
+ * AVX512PF Prefetch Instructions
+ *
+ * AVX512BW Byte and Word Instructions
+ * AVX512DQ Double-word and Quadword Instructions
+ * AVX512VL Vector Length Extensions
+ *
+ * AVX512IFMA Integer Fused Multiply Add (Not supported by kernel 4.4)
+ * AVX512VBMI Vector Byte Manipulation Instructions
+ */
+
+/*
+ * Check if AVX512F instruction set is available
+ */
+static inline boolean_t
+zfs_avx512f_available(void)
+{
+ return (__cpuid_has_avx512f() && __zmm_enabled());
+}
+
+/*
+ * Check if AVX512CD instruction set is available
+ */
+static inline boolean_t
+zfs_avx512cd_available(void)
+{
+ return (__cpuid_has_avx512cd() && __zmm_enabled());
+}
+
+/*
+ * Check if AVX512ER instruction set is available
+ */
+static inline boolean_t
+zfs_avx512er_available(void)
+{
+ return (__cpuid_has_avx512er() && __zmm_enabled());
+}
+
+/*
+ * Check if AVX512PF instruction set is available
+ */
+static inline boolean_t
+zfs_avx512pf_available(void)
+{
+ return (__cpuid_has_avx512pf() && __zmm_enabled());
+}
+
+/*
+ * Check if AVX512BW instruction set is available
+ */
+static inline boolean_t
+zfs_avx512bw_available(void)
+{
+ return (__cpuid_has_avx512bw() && __zmm_enabled());
+}
+
+/*
+ * Check if AVX512DQ instruction set is available
+ */
+static inline boolean_t
+zfs_avx512dq_available(void)
+{
+ return (__cpuid_has_avx512dq() && __zmm_enabled());
+}
+
+/*
+ * Check if AVX512VL instruction set is available
+ */
+static inline boolean_t
+zfs_avx512vl_available(void)
+{
+ return (__cpuid_has_avx512vl() && __zmm_enabled());
+}
+
+/*
+ * Check if AVX512IFMA instruction set is available
+ */
+static inline boolean_t
+zfs_avx512ifma_available(void)
+{
+ return (__cpuid_has_avx512ifma() && __zmm_enabled());
+}
+
+/*
+ * Check if AVX512VBMI instruction set is available
+ */
+static inline boolean_t
+zfs_avx512vbmi_available(void)
+{
+ return (__cpuid_has_avx512f() && __cpuid_has_avx512vbmi() &&
+ __zmm_enabled());
+}
+
+#elif defined(__aarch64__)
+
+#define kfpu_allowed() 1
+#define kfpu_initialize(tsk) do {} while (0)
+#define kfpu_begin() do {} while (0)
+#define kfpu_end() do {} while (0)
+
+#elif defined(__powerpc__)
+
+#define kfpu_allowed() 1
+#define kfpu_initialize(tsk) do {} while (0)
+#define kfpu_begin() do {} while (0)
+#define kfpu_end() do {} while (0)
+
+/*
+ * Check if AltiVec instruction set is available
+ * No easy way beyond 'altivec works' :-(
+ */
+#include <signal.h>
+#include <setjmp.h>
+
+#if defined(__ALTIVEC__) && !defined(__FreeBSD__)
+static jmp_buf env;
+static void sigillhandler(int x)
+{
+ longjmp(env, 1);
+}
+#endif
+
+static inline boolean_t
+zfs_altivec_available(void)
+{
+ boolean_t has_altivec = B_FALSE;
+#if defined(__ALTIVEC__) && !defined(__FreeBSD__)
+ sighandler_t savesig;
+ savesig = signal(SIGILL, sigillhandler);
+ if (setjmp(env)) {
+ signal(SIGILL, savesig);
+ has_altivec = B_FALSE;
+ } else {
+ __asm__ __volatile__("vor 0,0,0\n" : : : "v0");
+ signal(SIGILL, savesig);
+ has_altivec = B_TRUE;
+ }
+#endif
+ return (has_altivec);
+}
+#else
+
+#define kfpu_allowed() 0
+#define kfpu_initialize(tsk) do {} while (0)
+#define kfpu_begin() do {} while (0)
+#define kfpu_end() do {} while (0)
+
+#endif
+
+#endif /* _LIBSPL_SYS_SIMD_H */
diff --git a/lib/libspl/include/sys/stack.h b/lib/libspl/include/sys/stack.h
new file mode 100644
index 000000000000..59807e97b6a0
--- /dev/null
+++ b/lib/libspl/include/sys/stack.h
@@ -0,0 +1,74 @@
+/*
+ * 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
+ */
+/*
+ * This header file distributed under the terms of the CDDL.
+ * Portions Copyright 2008 Sun Microsystems, Inc. All Rights reserved.
+ */
+#ifndef _SYS_STACK_H
+#define _SYS_STACK_H
+
+#include <pthread.h>
+
+#define STACK_BIAS 0
+
+#ifdef __USE_GNU
+
+static inline int
+stack_getbounds(stack_t *sp)
+{
+ pthread_attr_t attr;
+ int rc;
+
+ rc = pthread_getattr_np(pthread_self(), &attr);
+ if (rc)
+ return (rc);
+
+ rc = pthread_attr_getstack(&attr, &sp->ss_sp, &sp->ss_size);
+ if (rc == 0)
+ sp->ss_flags = 0;
+
+ pthread_attr_destroy(&attr);
+
+ return (rc);
+}
+
+static inline int
+thr_stksegment(stack_t *sp)
+{
+ int rc;
+
+ rc = stack_getbounds(sp);
+ if (rc)
+ return (rc);
+
+ /*
+ * thr_stksegment() is expected to set sp.ss_sp to the high stack
+ * address, but the stack_getbounds() interface is expected to
+ * set sp.ss_sp to the low address. Adjust accordingly.
+ */
+ sp->ss_sp = (void *)(((uintptr_t)sp->ss_sp) + sp->ss_size);
+ sp->ss_flags = 0;
+
+ return (rc);
+}
+
+#endif /* __USE_GNU */
+#endif /* _SYS_STACK_H */
diff --git a/lib/libspl/include/sys/stdtypes.h b/lib/libspl/include/sys/stdtypes.h
new file mode 100644
index 000000000000..c26e2dc96c4e
--- /dev/null
+++ b/lib/libspl/include/sys/stdtypes.h
@@ -0,0 +1,54 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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
+ */
+
+#ifndef __SYS_STDTYPES_H
+#define __SYS_STDTYPES_H
+
+typedef enum {
+ B_FALSE = 0,
+ B_TRUE = 1
+} boolean_t;
+
+typedef unsigned char uchar_t;
+typedef unsigned short ushort_t;
+typedef unsigned int uint_t;
+typedef unsigned long ulong_t;
+typedef unsigned long long u_longlong_t;
+typedef long long longlong_t;
+
+typedef longlong_t offset_t;
+typedef u_longlong_t u_offset_t;
+typedef u_longlong_t len_t;
+typedef longlong_t diskaddr_t;
+
+typedef ulong_t pgcnt_t; /* number of pages */
+typedef long spgcnt_t; /* signed number of pages */
+
+typedef short pri_t;
+typedef ushort_t o_mode_t; /* old file attribute type */
+
+typedef int major_t;
+typedef int minor_t;
+
+typedef short index_t;
+
+#endif /* __SYS_STDTYPES_H */
diff --git a/lib/libspl/include/sys/strings.h b/lib/libspl/include/sys/strings.h
new file mode 100644
index 000000000000..c142047dcdb8
--- /dev/null
+++ b/lib/libspl/include/sys/strings.h
@@ -0,0 +1,33 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2006 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _LIBSPL_SYS_STRINGS_H
+#define _LIBSPL_SYS_STRINGS_H
+
+#include <string.h>
+#include <strings.h>
+
+#endif
diff --git a/lib/libspl/include/sys/stropts.h b/lib/libspl/include/sys/stropts.h
new file mode 100644
index 000000000000..08c2e79bc53c
--- /dev/null
+++ b/lib/libspl/include/sys/stropts.h
@@ -0,0 +1,29 @@
+/*
+ * 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 2010 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _LIBSPL_SYS_STROPTS_H
+#define _LIBSPL_SYS_STROPTS_H
+
+#endif /* _LIBSPL_SYS_STROPTS_H */
diff --git a/lib/libspl/include/sys/sunddi.h b/lib/libspl/include/sys/sunddi.h
new file mode 100644
index 000000000000..ccd2b29b9b09
--- /dev/null
+++ b/lib/libspl/include/sys/sunddi.h
@@ -0,0 +1,29 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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) 2008 by Sun Microsystems, Inc.
+ */
+
+#ifndef _SYS_SUNDDI_H
+#define _SYS_SUNDDI_H
+
+#endif /* _SYS_SUNDDI_H */
diff --git a/lib/libspl/include/sys/systeminfo.h b/lib/libspl/include/sys/systeminfo.h
new file mode 100644
index 000000000000..cc6c1793c00e
--- /dev/null
+++ b/lib/libspl/include/sys/systeminfo.h
@@ -0,0 +1,38 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2007 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _LIBSPL_SYS_SYSTEMINFO_H
+#define _LIBSPL_SYS_SYSTEMINFO_H
+
+#define HOSTID_MASK 0xFFFFFFFF
+#define HW_INVALID_HOSTID 0xFFFFFFFF /* an invalid hostid */
+#define HW_HOSTID_LEN 11 /* minimum buffer size needed */
+ /* to hold a decimal or hex */
+ /* hostid string */
+
+unsigned long get_system_hostid(void);
+
+#endif
diff --git a/lib/libspl/include/sys/time.h b/lib/libspl/include/sys/time.h
new file mode 100644
index 000000000000..c9f6165047d2
--- /dev/null
+++ b/lib/libspl/include/sys/time.h
@@ -0,0 +1,107 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2006 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _LIBSPL_SYS_TIME_H
+#define _LIBSPL_SYS_TIME_H
+
+#include <time.h>
+#include <sys/types.h>
+#include_next <sys/time.h>
+
+#ifndef SEC
+#define SEC 1
+#endif
+
+#ifndef MILLISEC
+#define MILLISEC 1000
+#endif
+
+#ifndef MICROSEC
+#define MICROSEC 1000000
+#endif
+
+#ifndef NANOSEC
+#define NANOSEC 1000000000
+#endif
+
+#ifndef NSEC_PER_USEC
+#define NSEC_PER_USEC 1000L
+#endif
+
+#ifndef MSEC2NSEC
+#define MSEC2NSEC(m) ((hrtime_t)(m) * (NANOSEC / MILLISEC))
+#endif
+
+#ifndef NSEC2MSEC
+#define NSEC2MSEC(n) ((n) / (NANOSEC / MILLISEC))
+#endif
+
+#ifndef USEC2NSEC
+#define USEC2NSEC(m) ((hrtime_t)(m) * (NANOSEC / MICROSEC))
+#endif
+
+#ifndef NSEC2USEC
+#define NSEC2USEC(n) ((n) / (NANOSEC / MICROSEC))
+#endif
+
+#ifndef NSEC2SEC
+#define NSEC2SEC(n) ((n) / (NANOSEC / SEC))
+#endif
+
+#ifndef SEC2NSEC
+#define SEC2NSEC(m) ((hrtime_t)(m) * (NANOSEC / SEC))
+#endif
+
+typedef long long hrtime_t;
+typedef struct timespec timespec_t;
+typedef struct timespec inode_timespec_t;
+
+static inline void
+gethrestime(inode_timespec_t *ts)
+{
+ struct timeval tv;
+ (void) gettimeofday(&tv, NULL);
+ ts->tv_sec = tv.tv_sec;
+ ts->tv_nsec = tv.tv_usec * NSEC_PER_USEC;
+}
+
+static inline uint64_t
+gethrestime_sec(void)
+{
+ struct timeval tv;
+ (void) gettimeofday(&tv, NULL);
+ return (tv.tv_sec);
+}
+
+static inline hrtime_t
+gethrtime(void)
+{
+ struct timespec ts;
+ (void) clock_gettime(CLOCK_MONOTONIC, &ts);
+ return ((((u_int64_t)ts.tv_sec) * NANOSEC) + ts.tv_nsec);
+}
+
+#endif /* _LIBSPL_SYS_TIME_H */
diff --git a/lib/libspl/include/sys/trace_spl.h b/lib/libspl/include/sys/trace_spl.h
new file mode 100644
index 000000000000..b80d288f7332
--- /dev/null
+++ b/lib/libspl/include/sys/trace_spl.h
@@ -0,0 +1,24 @@
+/* Here to keep the libspl build happy */
+
+#ifndef _LIBSPL_SPL_TRACE_H
+#define _LIBSPL_SPL_TRACE_H
+
+/*
+ * The set-error SDT probe is extra static, in that we declare its fake
+ * function literally, rather than with the DTRACE_PROBE1() macro. This is
+ * necessary so that SET_ERROR() can evaluate to a value, which wouldn't
+ * be possible if it required multiple statements (to declare the function
+ * and then call it).
+ *
+ * SET_ERROR() uses the comma operator so that it can be used without much
+ * additional code. For example, "return (EINVAL);" becomes
+ * "return (SET_ERROR(EINVAL));". Note that the argument will be evaluated
+ * twice, so it should not have side effects (e.g. something like:
+ * "return (SET_ERROR(log_error(EINVAL, info)));" would log the error twice).
+ */
+#undef SET_ERROR
+#define SET_ERROR(err) \
+ (__set_error(__FILE__, __func__, __LINE__, err), err)
+
+
+#endif
diff --git a/lib/libspl/include/sys/trace_zfs.h b/lib/libspl/include/sys/trace_zfs.h
new file mode 100644
index 000000000000..87ed5ad3c3be
--- /dev/null
+++ b/lib/libspl/include/sys/trace_zfs.h
@@ -0,0 +1,24 @@
+/* Here to keep the libspl build happy */
+
+#ifndef _LIBSPL_ZFS_TRACE_H
+#define _LIBSPL_ZFS_TRACE_H
+
+/*
+ * The set-error SDT probe is extra static, in that we declare its fake
+ * function literally, rather than with the DTRACE_PROBE1() macro. This is
+ * necessary so that SET_ERROR() can evaluate to a value, which wouldn't
+ * be possible if it required multiple statements (to declare the function
+ * and then call it).
+ *
+ * SET_ERROR() uses the comma operator so that it can be used without much
+ * additional code. For example, "return (EINVAL);" becomes
+ * "return (SET_ERROR(EINVAL));". Note that the argument will be evaluated
+ * twice, so it should not have side effects (e.g. something like:
+ * "return (SET_ERROR(log_error(EINVAL, info)));" would log the error twice).
+ */
+#undef SET_ERROR
+#define SET_ERROR(err) \
+ (__set_error(__FILE__, __func__, __LINE__, err), err)
+
+
+#endif
diff --git a/lib/libspl/include/sys/types.h b/lib/libspl/include/sys/types.h
new file mode 100644
index 000000000000..ea02ffac93ac
--- /dev/null
+++ b/lib/libspl/include/sys/types.h
@@ -0,0 +1,77 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2007 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _LIBSPL_SYS_TYPES_H
+#define _LIBSPL_SYS_TYPES_H
+
+#if defined(HAVE_MAKEDEV_IN_SYSMACROS)
+#include <sys/sysmacros.h>
+#elif defined(HAVE_MAKEDEV_IN_MKDEV)
+#include <sys/mkdev.h>
+#endif
+
+#include <sys/isa_defs.h>
+#include <sys/feature_tests.h>
+#include_next <sys/types.h>
+#include <sys/types32.h>
+#include <sys/va_list.h>
+#include <sys/stdtypes.h>
+
+#ifndef HAVE_INTTYPES
+#include <inttypes.h>
+#endif /* HAVE_INTTYPES */
+
+typedef int zoneid_t;
+typedef int projid_t;
+
+/*
+ * Definitions remaining from previous partial support for 64-bit file
+ * offsets. This partial support for devices greater than 2gb requires
+ * compiler support for long long.
+ */
+#ifdef _LONG_LONG_LTOH
+typedef union {
+ offset_t _f; /* Full 64 bit offset value */
+ struct {
+ int32_t _l; /* lower 32 bits of offset value */
+ int32_t _u; /* upper 32 bits of offset value */
+ } _p;
+} lloff_t;
+#endif
+
+#ifdef _LONG_LONG_HTOL
+typedef union {
+ offset_t _f; /* Full 64 bit offset value */
+ struct {
+ int32_t _u; /* upper 32 bits of offset value */
+ int32_t _l; /* lower 32 bits of offset value */
+ } _p;
+} lloff_t;
+#endif
+
+#include <sys/param.h> /* for NBBY */
+
+#endif
diff --git a/lib/libspl/include/sys/types32.h b/lib/libspl/include/sys/types32.h
new file mode 100644
index 000000000000..bb41aa34bee0
--- /dev/null
+++ b/lib/libspl/include/sys/types32.h
@@ -0,0 +1,89 @@
+/*
+ * 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 2007 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _SYS_TYPES32_H
+#define _SYS_TYPES32_H
+
+#include <sys/inttypes.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Interoperability types for programs. Used for:
+ *
+ * Crossing between 32-bit and 64-bit domains.
+ *
+ * On disk data formats such as filesystem meta data
+ * and disk label.
+ *
+ * Note: Applications should never include this
+ * header file.
+ */
+typedef uint32_t caddr32_t;
+typedef int32_t daddr32_t;
+typedef int32_t off32_t;
+typedef uint32_t ino32_t;
+typedef int32_t blkcnt32_t;
+typedef uint32_t fsblkcnt32_t;
+typedef uint32_t fsfilcnt32_t;
+typedef int32_t id32_t;
+typedef uint32_t major32_t;
+typedef uint32_t minor32_t;
+typedef int32_t key32_t;
+typedef uint32_t mode32_t;
+typedef uint32_t uid32_t;
+typedef uint32_t gid32_t;
+typedef uint32_t nlink32_t;
+typedef uint32_t dev32_t;
+typedef int32_t pid32_t;
+typedef uint32_t size32_t;
+typedef int32_t ssize32_t;
+typedef int32_t time32_t;
+typedef int32_t clock32_t;
+
+struct timeval32 {
+ time32_t tv_sec; /* seconds */
+ int32_t tv_usec; /* and microseconds */
+};
+
+typedef struct timespec32 {
+ time32_t tv_sec; /* seconds */
+ int32_t tv_nsec; /* and nanoseconds */
+} timespec32_t;
+
+typedef struct timespec32 timestruc32_t;
+
+typedef struct itimerspec32 {
+ struct timespec32 it_interval;
+ struct timespec32 it_value;
+} itimerspec32_t;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _SYS_TYPES32_H */
diff --git a/lib/libspl/include/sys/tzfile.h b/lib/libspl/include/sys/tzfile.h
new file mode 100644
index 000000000000..e30e7566366e
--- /dev/null
+++ b/lib/libspl/include/sys/tzfile.h
@@ -0,0 +1,164 @@
+/*
+ * 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 2007 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+/*
+ * from Arthur Olson's 6.1
+ */
+
+#ifndef _LIBSPL_SYS_TZFILE_H
+#define _LIBSPL_SYS_TZFILE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Information about time zone files.
+ */
+
+#define TZDIR "/usr/share/lib/zoneinfo" /* Time zone object file directory */
+
+#define TZDEFAULT (getenv("TZ"))
+
+#define TZDEFRULES "posixrules"
+
+/*
+ * Each file begins with. . .
+ */
+
+struct tzhead {
+ char tzh_reserved[24]; /* reserved for future use */
+ char tzh_ttisstdcnt[4]; /* coded number of trans. time flags */
+ char tzh_leapcnt[4]; /* coded number of leap seconds */
+ char tzh_timecnt[4]; /* coded number of transition times */
+ char tzh_typecnt[4]; /* coded number of local time types */
+ char tzh_charcnt[4]; /* coded number of abbr. chars */
+};
+
+/*
+ * . . .followed by. . .
+ *
+ * tzh_timecnt (char [4])s coded transition times a la time(2)
+ * tzh_timecnt (unsigned char)s types of local time starting at above
+ * tzh_typecnt repetitions of
+ * one (char [4]) coded GMT offset in seconds
+ * one (unsigned char) used to set tm_isdst
+ * one (unsigned char) that's an abbreviation list index
+ * tzh_charcnt (char)s '\0'-terminated zone abbreviations
+ * tzh_leapcnt repetitions of
+ * one (char [4]) coded leap second transition times
+ * one (char [4]) total correction after above
+ * tzh_ttisstdcnt (char)s indexed by type; if TRUE, transition
+ * time is standard time, if FALSE,
+ * transition time is wall clock time
+ * if absent, transition times are
+ * assumed to be wall clock time
+ */
+
+/*
+ * In the current implementation, "tzset()" refuses to deal with files that
+ * exceed any of the limits below.
+ */
+
+/*
+ * The TZ_MAX_TIMES value below is enough to handle a bit more than a
+ * year's worth of solar time (corrected daily to the nearest second) or
+ * 138 years of Pacific Presidential Election time
+ * (where there are three time zone transitions every fourth year).
+ */
+#define TZ_MAX_TIMES 370
+
+#define TZ_MAX_TYPES 256 /* Limited by what (unsigned char)'s can hold */
+
+#define TZ_MAX_CHARS 50 /* Maximum number of abbreviation characters */
+
+#define TZ_MAX_LEAPS 50 /* Maximum number of leap second corrections */
+
+#define SECSPERMIN 60
+#define MINSPERHOUR 60
+#define HOURSPERDAY 24
+#define DAYSPERWEEK 7
+#define DAYSPERNYEAR 365
+#define DAYSPERLYEAR 366
+#define SECSPERHOUR (SECSPERMIN * MINSPERHOUR)
+#define SECSPERDAY ((long)SECSPERHOUR * HOURSPERDAY)
+#define MONSPERYEAR 12
+
+#define TM_SUNDAY 0
+#define TM_MONDAY 1
+#define TM_TUESDAY 2
+#define TM_WEDNESDAY 3
+#define TM_THURSDAY 4
+#define TM_FRIDAY 5
+#define TM_SATURDAY 6
+
+#define TM_JANUARY 0
+#define TM_FEBRUARY 1
+#define TM_MARCH 2
+#define TM_APRIL 3
+#define TM_MAY 4
+#define TM_JUNE 5
+#define TM_JULY 6
+#define TM_AUGUST 7
+#define TM_SEPTEMBER 8
+#define TM_OCTOBER 9
+#define TM_NOVEMBER 10
+#define TM_DECEMBER 11
+
+#define TM_YEAR_BASE 1900
+
+#define EPOCH_YEAR 1970
+#define EPOCH_WDAY TM_THURSDAY
+
+/*
+ * Accurate only for the past couple of centuries;
+ * that will probably do.
+ */
+
+#define isleap(y) (((y) % 4) == 0 && ((y) % 100) != 0 || ((y) % 400) == 0)
+
+/*
+ * Use of the underscored variants may cause problems if you move your code to
+ * certain System-V-based systems; for maximum portability, use the
+ * underscore-free variants. The underscored variants are provided for
+ * backward compatibility only; they may disappear from future versions of
+ * this file.
+ */
+
+#define SECS_PER_MIN SECSPERMIN
+#define MINS_PER_HOUR MINSPERHOUR
+#define HOURS_PER_DAY HOURSPERDAY
+#define DAYS_PER_WEEK DAYSPERWEEK
+#define DAYS_PER_NYEAR DAYSPERNYEAR
+#define DAYS_PER_LYEAR DAYSPERLYEAR
+#define SECS_PER_HOUR SECSPERHOUR
+#define SECS_PER_DAY SECSPERDAY
+#define MONS_PER_YEAR MONSPERYEAR
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _LIBSPL_SYS_TZFILE_H */
diff --git a/lib/libspl/include/sys/uio.h b/lib/libspl/include/sys/uio.h
new file mode 100644
index 000000000000..3a834b996add
--- /dev/null
+++ b/lib/libspl/include/sys/uio.h
@@ -0,0 +1,153 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2005 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
+/* All Rights Reserved */
+
+/*
+ * University Copyright- Copyright (c) 1982, 1986, 1988
+ * The Regents of the University of California
+ * All Rights Reserved
+ *
+ * University Acknowledgment- Portions of this document are derived from
+ * software developed by the University of California, Berkeley, and its
+ * contributors.
+ */
+
+#ifndef _LIBSPL_SYS_UIO_H
+#define _LIBSPL_SYS_UIO_H
+
+#include <sys/types.h>
+#include_next <sys/uio.h>
+
+#ifdef __APPLE__
+#include <sys/_types/_iovec_t.h>
+#endif
+
+#include <stdint.h>
+typedef struct iovec iovec_t;
+
+#if defined(__linux__) || defined(__APPLE__)
+typedef enum uio_rw {
+ UIO_READ = 0,
+ UIO_WRITE = 1,
+} uio_rw_t;
+
+typedef enum uio_seg {
+ UIO_USERSPACE = 0,
+ UIO_SYSSPACE = 1,
+ UIO_USERISPACE = 2,
+} uio_seg_t;
+
+#elif defined(__FreeBSD__)
+typedef enum uio_seg uio_seg_t;
+#endif
+
+typedef struct uio {
+ struct iovec *uio_iov; /* pointer to array of iovecs */
+ int uio_iovcnt; /* number of iovecs */
+ offset_t uio_loffset; /* file offset */
+ uio_seg_t uio_segflg; /* address space (kernel or user) */
+ uint16_t uio_fmode; /* file mode flags */
+ uint16_t uio_extflg; /* extended flags */
+ offset_t uio_limit; /* u-limit (maximum byte offset) */
+ ssize_t uio_resid; /* residual count */
+} uio_t;
+
+typedef enum xuio_type {
+ UIOTYPE_ASYNCIO,
+ UIOTYPE_ZEROCOPY,
+} xuio_type_t;
+
+#define UIOA_IOV_MAX 16
+
+typedef struct uioa_page_s { /* locked uio_iov state */
+ int uioa_pfncnt; /* count of pfn_t(s) in *uioa_ppp */
+ void **uioa_ppp; /* page_t or pfn_t array */
+ caddr_t uioa_base; /* address base */
+ size_t uioa_len; /* span length */
+} uioa_page_t;
+
+typedef struct xuio {
+ uio_t xu_uio; /* embedded UIO structure */
+
+ /* Extended uio fields */
+ enum xuio_type xu_type; /* uio type */
+ union {
+ struct {
+ uint32_t xu_a_state; /* state of async i/o */
+ ssize_t xu_a_mbytes; /* bytes moved */
+ uioa_page_t *xu_a_lcur; /* uioa_locked[] pointer */
+ void **xu_a_lppp; /* lcur->uioa_pppp[] pointer */
+ void *xu_a_hwst[4]; /* opaque hardware state */
+ uioa_page_t xu_a_locked[UIOA_IOV_MAX];
+ } xu_aio;
+
+ struct {
+ int xu_zc_rw; /* read or write buffer */
+ void *xu_zc_priv; /* fs specific */
+ } xu_zc;
+ } xu_ext;
+} xuio_t;
+
+#define XUIO_XUZC_PRIV(xuio) xuio->xu_ext.xu_zc.xu_zc_priv
+#define XUIO_XUZC_RW(xuio) xuio->xu_ext.xu_zc.xu_zc_rw
+
+#define uio_segflg(uio) (uio)->uio_segflg
+#define uio_offset(uio) (uio)->uio_loffset
+#define uio_resid(uio) (uio)->uio_resid
+#define uio_iovcnt(uio) (uio)->uio_iovcnt
+#define uio_iovlen(uio, idx) (uio)->uio_iov[(idx)].iov_len
+#define uio_iovbase(uio, idx) (uio)->uio_iov[(idx)].iov_base
+
+static inline void
+uio_iov_at_index(uio_t *uio, uint_t idx, void **base, uint64_t *len)
+{
+ *base = uio_iovbase(uio, idx);
+ *len = uio_iovlen(uio, idx);
+}
+
+static inline void
+uio_advance(uio_t *uio, size_t size)
+{
+ uio->uio_resid -= size;
+ uio->uio_loffset += size;
+}
+
+static inline offset_t
+uio_index_at_offset(uio_t *uio, offset_t off, uint_t *vec_idx)
+{
+ *vec_idx = 0;
+ while (*vec_idx < (uint_t)uio_iovcnt(uio) &&
+ off >= (offset_t)uio_iovlen(uio, *vec_idx)) {
+ off -= uio_iovlen(uio, *vec_idx);
+ (*vec_idx)++;
+ }
+
+ return (off);
+}
+
+#endif /* _SYS_UIO_H */
diff --git a/lib/libspl/include/sys/va_list.h b/lib/libspl/include/sys/va_list.h
new file mode 100644
index 000000000000..a36f5c77daa9
--- /dev/null
+++ b/lib/libspl/include/sys/va_list.h
@@ -0,0 +1,32 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2006 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _SYS_VA_LIST_H
+#define _SYS_VA_LIST_H
+
+#include <stdarg.h>
+
+#endif
diff --git a/lib/libspl/include/sys/varargs.h b/lib/libspl/include/sys/varargs.h
new file mode 100644
index 000000000000..3d00a3361d87
--- /dev/null
+++ b/lib/libspl/include/sys/varargs.h
@@ -0,0 +1,30 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2006 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _LIBSPL_SYS_VARARGS_H
+#define _LIBSPL_SYS_VARARGS_H
+
+#endif
diff --git a/lib/libspl/include/sys/vnode.h b/lib/libspl/include/sys/vnode.h
new file mode 100644
index 000000000000..efcdd2c5a5ae
--- /dev/null
+++ b/lib/libspl/include/sys/vnode.h
@@ -0,0 +1,30 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2009 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _LIBSPL_SYS_VNODE_H
+#define _LIBSPL_SYS_VNODE_H
+
+#endif /* _LIBSPL_SYS_VNODE_H */
diff --git a/lib/libspl/include/sys/vtoc.h b/lib/libspl/include/sys/vtoc.h
new file mode 100644
index 000000000000..5d8448b628dc
--- /dev/null
+++ b/lib/libspl/include/sys/vtoc.h
@@ -0,0 +1,350 @@
+/*
+ * 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 2009 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+
+/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
+/* All Rights Reserved */
+
+
+#ifndef _SYS_VTOC_H
+#define _SYS_VTOC_H
+
+#include <sys/dklabel.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Note: the VTOC is not implemented fully, nor in the manner
+ * that AT&T implements it. AT&T puts the vtoc structure
+ * into a sector, usually the second sector (pdsector is first).
+ *
+ * Sun incorporates the tag, flag, version, and volume vtoc fields into
+ * its Disk Label, which already has some vtoc-equivalent fields.
+ * Upon reading the vtoc with read_vtoc(), the following exceptions
+ * occur:
+ * v_bootinfo [all] returned as zero
+ * v_sanity returned as VTOC_SANE
+ * if Disk Label was sane
+ * v_sectorsz returned as 512
+ * v_reserved [all] returned as zero
+ * timestamp [all] returned as zero
+ *
+ * See dklabel.h, read_vtoc(), and write_vtoc().
+ */
+
+#define V_NUMPAR NDKMAP /* The number of partitions */
+ /* (from dkio.h) */
+
+#define VTOC_SANE 0x600DDEEE /* Indicates a sane VTOC */
+#define V_VERSION 0x01 /* layout version number */
+#define V_EXTVERSION V_VERSION /* extvtoc layout version number */
+
+/*
+ * Partition identification tags
+ */
+#define V_UNASSIGNED 0x00 /* unassigned partition */
+#define V_BOOT 0x01 /* Boot partition */
+#define V_ROOT 0x02 /* Root filesystem */
+#define V_SWAP 0x03 /* Swap filesystem */
+#define V_USR 0x04 /* Usr filesystem */
+#define V_BACKUP 0x05 /* full disk */
+#define V_STAND 0x06 /* Stand partition */
+#define V_VAR 0x07 /* Var partition */
+#define V_HOME 0x08 /* Home partition */
+#define V_ALTSCTR 0x09 /* Alternate sector partition */
+#define V_CACHE 0x0a /* Cache (cachefs) partition */
+#define V_RESERVED 0x0b /* SMI reserved data */
+
+/*
+ * Partition permission flags
+ */
+#define V_UNMNT 0x01 /* Unmountable partition */
+#define V_RONLY 0x10 /* Read only */
+
+/*
+ * error codes for reading & writing vtoc
+ */
+#define VT_ERROR (-2) /* errno supplies specific error */
+#define VT_EIO (-3) /* I/O error accessing vtoc */
+#define VT_EINVAL (-4) /* illegal value in vtoc or request */
+#define VT_ENOTSUP (-5) /* VTOC op. not supported */
+#define VT_ENOSPC (-6) /* requested space not found */
+#define VT_EOVERFLOW (-7) /* VTOC op. data struct limited */
+
+struct partition {
+ ushort_t p_tag; /* ID tag of partition */
+ ushort_t p_flag; /* permission flags */
+ uint64_t p_start; /* start sector no of partition */
+ long p_size; /* # of blocks in partition */
+};
+
+struct vtoc {
+ unsigned long v_bootinfo[3]; /* info needed by mboot (unsupported) */
+ unsigned long v_sanity; /* to verify vtoc sanity */
+ unsigned long v_version; /* layout version */
+ char v_volume[LEN_DKL_VVOL]; /* volume name */
+ ushort_t v_sectorsz; /* sector size in bytes */
+ ushort_t v_nparts; /* number of partitions */
+ unsigned long v_reserved[10]; /* free space */
+ struct partition v_part[V_NUMPAR]; /* partition headers */
+ time_t timestamp[V_NUMPAR]; /* partition timestamp (unsupported) */
+ char v_asciilabel[LEN_DKL_ASCII]; /* for compatibility */
+};
+
+struct extpartition {
+ ushort_t p_tag; /* ID tag of partition */
+ ushort_t p_flag; /* permission flags */
+ ushort_t p_pad[2];
+ diskaddr_t p_start; /* start sector no of partition */
+ diskaddr_t p_size; /* # of blocks in partition */
+};
+
+
+struct extvtoc {
+ uint64_t v_bootinfo[3]; /* info needed by mboot (unsupported) */
+ uint64_t v_sanity; /* to verify vtoc sanity */
+ uint64_t v_version; /* layout version */
+ char v_volume[LEN_DKL_VVOL]; /* volume name */
+ ushort_t v_sectorsz; /* sector size in bytes */
+ ushort_t v_nparts; /* number of partitions */
+ ushort_t pad[2];
+ uint64_t v_reserved[10];
+ struct extpartition v_part[V_NUMPAR]; /* partition headers */
+ uint64_t timestamp[V_NUMPAR]; /* partition timestamp (unsupported) */
+ char v_asciilabel[LEN_DKL_ASCII]; /* for compatibility */
+};
+
+#ifdef _KERNEL
+#define extvtoctovtoc(extv, v) \
+ { \
+ int i; \
+ v.v_bootinfo[0] = (unsigned long)extv.v_bootinfo[0]; \
+ v.v_bootinfo[1] = (unsigned long)extv.v_bootinfo[1]; \
+ v.v_bootinfo[2] = (unsigned long)extv.v_bootinfo[2]; \
+ v.v_sanity = (unsigned long)extv.v_sanity; \
+ v.v_version = (unsigned long)extv.v_version; \
+ bcopy(extv.v_volume, v.v_volume, LEN_DKL_VVOL); \
+ v.v_sectorsz = extv.v_sectorsz; \
+ v.v_nparts = extv.v_nparts; \
+ for (i = 0; i < 10; i++) \
+ v.v_reserved[i] = (unsigned long)extv.v_reserved[i]; \
+ for (i = 0; i < V_NUMPAR; i++) { \
+ v.v_part[i].p_tag = extv.v_part[i].p_tag; \
+ v.v_part[i].p_flag = extv.v_part[i].p_flag; \
+ v.v_part[i].p_start = (uint64_t)extv.v_part[i].p_start; \
+ v.v_part[i].p_size = (long)extv.v_part[i].p_size; \
+ v.timestamp[i] = (time_t)extv.timestamp[i]; \
+ } \
+ bcopy(extv.v_asciilabel, v.v_asciilabel, LEN_DKL_ASCII); \
+ }
+
+#define vtoctoextvtoc(v, extv) \
+ { \
+ int i; \
+ extv.v_bootinfo[0] = (uint64_t)v.v_bootinfo[0]; \
+ extv.v_bootinfo[1] = (uint64_t)v.v_bootinfo[1]; \
+ extv.v_bootinfo[2] = (uint64_t)v.v_bootinfo[2]; \
+ extv.v_sanity = (uint64_t)v.v_sanity; \
+ extv.v_version = (uint64_t)v.v_version; \
+ bcopy(v.v_volume, extv.v_volume, LEN_DKL_VVOL); \
+ extv.v_sectorsz = v.v_sectorsz; \
+ extv.v_nparts = v.v_nparts; \
+ for (i = 0; i < 10; i++) \
+ extv.v_reserved[i] = (uint64_t)v.v_reserved[i]; \
+ for (i = 0; i < V_NUMPAR; i++) { \
+ extv.v_part[i].p_tag = v.v_part[i].p_tag; \
+ extv.v_part[i].p_flag = v.v_part[i].p_flag; \
+ extv.v_part[i].p_start = \
+ (diskaddr_t)(unsigned long)v.v_part[i].p_start; \
+ extv.v_part[i].p_size = \
+ (diskaddr_t)(unsigned long)v.v_part[i].p_size; \
+ extv.timestamp[i] = (uint64_t)v.timestamp[i]; \
+ } \
+ bcopy(v.v_asciilabel, extv.v_asciilabel, LEN_DKL_ASCII); \
+ }
+#endif /* _KERNEL */
+
+#if defined(_SYSCALL32)
+struct partition32 {
+ uint16_t p_tag; /* ID tag of partition */
+ uint16_t p_flag; /* permission flags */
+ daddr32_t p_start; /* start sector no of partition */
+ int32_t p_size; /* # of blocks in partition */
+};
+
+struct vtoc32 {
+ uint32_t v_bootinfo[3]; /* info needed by mboot (unsupported) */
+ uint32_t v_sanity; /* to verify vtoc sanity */
+ uint32_t v_version; /* layout version */
+ char v_volume[LEN_DKL_VVOL]; /* volume name */
+ uint16_t v_sectorsz; /* sector size in bytes */
+ uint16_t v_nparts; /* number of partitions */
+ uint32_t v_reserved[10]; /* free space */
+ struct partition32 v_part[V_NUMPAR]; /* partition headers */
+ time32_t timestamp[V_NUMPAR]; /* partition timestamp (unsupported) */
+ char v_asciilabel[LEN_DKL_ASCII]; /* for compatibility */
+};
+
+#define vtoc32tovtoc(v32, v) \
+ { \
+ int i; \
+ v.v_bootinfo[0] = v32.v_bootinfo[0]; \
+ v.v_bootinfo[1] = v32.v_bootinfo[1]; \
+ v.v_bootinfo[2] = v32.v_bootinfo[2]; \
+ v.v_sanity = v32.v_sanity; \
+ v.v_version = v32.v_version; \
+ bcopy(v32.v_volume, v.v_volume, LEN_DKL_VVOL); \
+ v.v_sectorsz = v32.v_sectorsz; \
+ v.v_nparts = v32.v_nparts; \
+ v.v_version = v32.v_version; \
+ for (i = 0; i < 10; i++) \
+ v.v_reserved[i] = v32.v_reserved[i]; \
+ for (i = 0; i < V_NUMPAR; i++) { \
+ v.v_part[i].p_tag = (ushort_t)v32.v_part[i].p_tag; \
+ v.v_part[i].p_flag = (ushort_t)v32.v_part[i].p_flag; \
+ v.v_part[i].p_start = (unsigned)v32.v_part[i].p_start; \
+ v.v_part[i].p_size = (unsigned)v32.v_part[i].p_size; \
+ } \
+ for (i = 0; i < V_NUMPAR; i++) \
+ v.timestamp[i] = (time_t)v32.timestamp[i]; \
+ bcopy(v32.v_asciilabel, v.v_asciilabel, LEN_DKL_ASCII); \
+ }
+
+#define vtoc32toextvtoc(v32, extv) \
+ { \
+ int i; \
+ extv.v_bootinfo[0] = v32.v_bootinfo[0]; \
+ extv.v_bootinfo[1] = v32.v_bootinfo[1]; \
+ extv.v_bootinfo[2] = v32.v_bootinfo[2]; \
+ extv.v_sanity = v32.v_sanity; \
+ extv.v_version = v32.v_version; \
+ bcopy(v32.v_volume, extv.v_volume, LEN_DKL_VVOL); \
+ extv.v_sectorsz = v32.v_sectorsz; \
+ extv.v_nparts = v32.v_nparts; \
+ extv.v_version = v32.v_version; \
+ for (i = 0; i < 10; i++) \
+ extv.v_reserved[i] = v32.v_reserved[i]; \
+ for (i = 0; i < V_NUMPAR; i++) { \
+ extv.v_part[i].p_tag = (ushort_t)v32.v_part[i].p_tag; \
+ extv.v_part[i].p_flag = (ushort_t)v32.v_part[i].p_flag; \
+ extv.v_part[i].p_start = (diskaddr_t)v32.v_part[i].p_start; \
+ extv.v_part[i].p_size = (diskaddr_t)v32.v_part[i].p_size; \
+ extv.timestamp[i] = (time_t)v32.timestamp[i]; \
+ } \
+ bcopy(v32.v_asciilabel, extv.v_asciilabel, LEN_DKL_ASCII); \
+ }
+
+
+#define vtoctovtoc32(v, v32) \
+ { \
+ int i; \
+ v32.v_bootinfo[0] = v.v_bootinfo[0]; \
+ v32.v_bootinfo[1] = v.v_bootinfo[1]; \
+ v32.v_bootinfo[2] = v.v_bootinfo[2]; \
+ v32.v_sanity = v.v_sanity; \
+ v32.v_version = v.v_version; \
+ bcopy(v.v_volume, v32.v_volume, LEN_DKL_VVOL); \
+ v32.v_sectorsz = v.v_sectorsz; \
+ v32.v_nparts = v.v_nparts; \
+ v32.v_version = v.v_version; \
+ for (i = 0; i < 10; i++) \
+ v32.v_reserved[i] = v.v_reserved[i]; \
+ for (i = 0; i < V_NUMPAR; i++) { \
+ v32.v_part[i].p_tag = (ushort_t)v.v_part[i].p_tag; \
+ v32.v_part[i].p_flag = (ushort_t)v.v_part[i].p_flag; \
+ v32.v_part[i].p_start = (unsigned)v.v_part[i].p_start; \
+ v32.v_part[i].p_size = (unsigned)v.v_part[i].p_size; \
+ } \
+ for (i = 0; i < V_NUMPAR; i++) { \
+ if (v.timestamp[i] > TIME32_MAX) \
+ v32.timestamp[i] = TIME32_MAX; \
+ else \
+ v32.timestamp[i] = (time32_t)v.timestamp[i]; \
+ } \
+ bcopy(v.v_asciilabel, v32.v_asciilabel, LEN_DKL_ASCII); \
+ }
+
+#define extvtoctovtoc32(extv, v32) \
+ { \
+ int i; \
+ v32.v_bootinfo[0] = extv.v_bootinfo[0]; \
+ v32.v_bootinfo[1] = extv.v_bootinfo[1]; \
+ v32.v_bootinfo[2] = extv.v_bootinfo[2]; \
+ v32.v_sanity = extv.v_sanity; \
+ v32.v_version = extv.v_version; \
+ bcopy(extv.v_volume, v32.v_volume, LEN_DKL_VVOL); \
+ v32.v_sectorsz = extv.v_sectorsz; \
+ v32.v_nparts = extv.v_nparts; \
+ v32.v_version = extv.v_version; \
+ for (i = 0; i < 10; i++) \
+ v32.v_reserved[i] = extv.v_reserved[i]; \
+ for (i = 0; i < V_NUMPAR; i++) { \
+ v32.v_part[i].p_tag = (ushort_t)extv.v_part[i].p_tag; \
+ v32.v_part[i].p_flag = (ushort_t)extv.v_part[i].p_flag; \
+ v32.v_part[i].p_start = (unsigned)extv.v_part[i].p_start; \
+ v32.v_part[i].p_size = (unsigned)extv.v_part[i].p_size; \
+ } \
+ for (i = 0; i < V_NUMPAR; i++) { \
+ if (extv.timestamp[i] > TIME32_MAX) \
+ v32.timestamp[i] = TIME32_MAX; \
+ else \
+ v32.timestamp[i] = (time32_t)extv.timestamp[i]; \
+ } \
+ bcopy(extv.v_asciilabel, v32.v_asciilabel, LEN_DKL_ASCII); \
+ }
+
+
+#endif /* _SYSCALL32 */
+
+/*
+ * These defines are the mode parameter for the checksum routines.
+ */
+#define CK_CHECKSUM 0 /* check checksum */
+#define CK_MAKESUM 1 /* generate checksum */
+
+#if defined(__STDC__)
+
+extern int read_vtoc(int, struct vtoc *);
+extern int write_vtoc(int, struct vtoc *);
+extern int read_extvtoc(int, struct extvtoc *);
+extern int write_extvtoc(int, struct extvtoc *);
+
+#else
+
+extern int read_vtoc();
+extern int write_vtoc();
+extern int read_extvtoc();
+extern int write_extvtoc();
+
+#endif /* __STDC__ */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _SYS_VTOC_H */
diff --git a/lib/libspl/include/sys/zone.h b/lib/libspl/include/sys/zone.h
new file mode 100644
index 000000000000..bbb964dcef22
--- /dev/null
+++ b/lib/libspl/include/sys/zone.h
@@ -0,0 +1,30 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2006 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _LIBSPL_SYS_ZONE_H
+#define _LIBSPL_SYS_ZONE_H
+
+#endif
diff --git a/lib/libspl/include/thread.h b/lib/libspl/include/thread.h
new file mode 100644
index 000000000000..74694e23eed5
--- /dev/null
+++ b/lib/libspl/include/thread.h
@@ -0,0 +1,30 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2009 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _LIBSPL_THREAD_H
+#define _LIBSPL_THREAD_H
+
+#endif /* _LIBSPL_THREAD_H */
diff --git a/lib/libspl/include/tzfile.h b/lib/libspl/include/tzfile.h
new file mode 100644
index 000000000000..7bd4087cd5d1
--- /dev/null
+++ b/lib/libspl/include/tzfile.h
@@ -0,0 +1,32 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2007 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _LIBSPL_TZFILE_H
+#define _LIBSPL_TZFILE_H
+
+#include <sys/tzfile.h>
+
+#endif /* _LIBSPL_TZFILE_H */
diff --git a/lib/libspl/include/ucred.h b/lib/libspl/include/ucred.h
new file mode 100644
index 000000000000..8178fdec4c74
--- /dev/null
+++ b/lib/libspl/include/ucred.h
@@ -0,0 +1,32 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2007 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _LIBSPL_UCRED_H
+#define _LIBSPL_UCRED_H
+
+typedef int ucred_t;
+
+#endif
diff --git a/lib/libspl/include/umem.h b/lib/libspl/include/umem.h
new file mode 100644
index 000000000000..65f12595e64f
--- /dev/null
+++ b/lib/libspl/include/umem.h
@@ -0,0 +1,208 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _LIBSPL_UMEM_H
+#define _LIBSPL_UMEM_H
+
+/*
+ * XXX: We should use the real portable umem library if it is detected
+ * at configure time. However, if the library is not available, we can
+ * use a trivial malloc based implementation. This obviously impacts
+ * performance, but unless you are using a full userspace build of zpool for
+ * something other than ztest, you are likely not going to notice or care.
+ *
+ * https://labs.omniti.com/trac/portableumem
+ */
+#include <sys/debug.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef void vmem_t;
+
+/*
+ * Flags for umem_alloc/umem_free
+ */
+#define UMEM_DEFAULT 0x0000 /* normal -- may fail */
+#define UMEM_NOFAIL 0x0100 /* Never fails */
+
+/*
+ * Flags for umem_cache_create()
+ */
+#define UMC_NODEBUG 0x00020000
+
+#define UMEM_CACHE_NAMELEN 31
+
+typedef int umem_nofail_callback_t(void);
+typedef int umem_constructor_t(void *, void *, int);
+typedef void umem_destructor_t(void *, void *);
+typedef void umem_reclaim_t(void *);
+
+typedef struct umem_cache {
+ char cache_name[UMEM_CACHE_NAMELEN + 1];
+ size_t cache_bufsize;
+ size_t cache_align;
+ umem_constructor_t *cache_constructor;
+ umem_destructor_t *cache_destructor;
+ umem_reclaim_t *cache_reclaim;
+ void *cache_private;
+ void *cache_arena;
+ int cache_cflags;
+} umem_cache_t;
+
+/* Prototypes for functions to provide defaults for umem envvars */
+const char *_umem_debug_init(void);
+const char *_umem_options_init(void);
+const char *_umem_logging_init(void);
+
+static inline void *
+umem_alloc(size_t size, int flags)
+{
+ void *ptr = NULL;
+
+ do {
+ ptr = malloc(size);
+ } while (ptr == NULL && (flags & UMEM_NOFAIL));
+
+ return (ptr);
+}
+
+static inline void *
+umem_alloc_aligned(size_t size, size_t align, int flags)
+{
+ void *ptr = NULL;
+ int rc = EINVAL;
+
+ do {
+ rc = posix_memalign(&ptr, align, size);
+ } while (rc == ENOMEM && (flags & UMEM_NOFAIL));
+
+ if (rc == EINVAL) {
+ fprintf(stderr, "%s: invalid memory alignment (%zd)\n",
+ __func__, align);
+ if (flags & UMEM_NOFAIL)
+ abort();
+ return (NULL);
+ }
+
+ return (ptr);
+}
+
+static inline void *
+umem_zalloc(size_t size, int flags)
+{
+ void *ptr = NULL;
+
+ ptr = umem_alloc(size, flags);
+ if (ptr)
+ memset(ptr, 0, size);
+
+ return (ptr);
+}
+
+static inline void
+umem_free(void *ptr, size_t size __maybe_unused)
+{
+ free(ptr);
+}
+
+static inline void
+umem_nofail_callback(umem_nofail_callback_t *cb __maybe_unused)
+{}
+
+static inline umem_cache_t *
+umem_cache_create(
+ char *name, size_t bufsize, size_t align,
+ umem_constructor_t *constructor,
+ umem_destructor_t *destructor,
+ umem_reclaim_t *reclaim,
+ void *priv, void *vmp, int cflags)
+{
+ umem_cache_t *cp;
+
+ cp = (umem_cache_t *)umem_alloc(sizeof (umem_cache_t), UMEM_DEFAULT);
+ if (cp) {
+ strlcpy(cp->cache_name, name, UMEM_CACHE_NAMELEN);
+ cp->cache_bufsize = bufsize;
+ cp->cache_align = align;
+ cp->cache_constructor = constructor;
+ cp->cache_destructor = destructor;
+ cp->cache_reclaim = reclaim;
+ cp->cache_private = priv;
+ cp->cache_arena = vmp;
+ cp->cache_cflags = cflags;
+ }
+
+ return (cp);
+}
+
+static inline void
+umem_cache_destroy(umem_cache_t *cp)
+{
+ umem_free(cp, sizeof (umem_cache_t));
+}
+
+static inline void *
+umem_cache_alloc(umem_cache_t *cp, int flags)
+{
+ void *ptr = NULL;
+
+ if (cp->cache_align != 0)
+ ptr = umem_alloc_aligned(
+ cp->cache_bufsize, cp->cache_align, flags);
+ else
+ ptr = umem_alloc(cp->cache_bufsize, flags);
+
+ if (ptr && cp->cache_constructor)
+ cp->cache_constructor(ptr, cp->cache_private, UMEM_DEFAULT);
+
+ return (ptr);
+}
+
+static inline void
+umem_cache_free(umem_cache_t *cp, void *ptr)
+{
+ if (cp->cache_destructor)
+ cp->cache_destructor(ptr, cp->cache_private);
+
+ umem_free(ptr, cp->cache_bufsize);
+}
+
+static inline void
+umem_cache_reap_now(umem_cache_t *cp __maybe_unused)
+{
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/libspl/include/unistd.h b/lib/libspl/include/unistd.h
new file mode 100644
index 000000000000..0246991b4b61
--- /dev/null
+++ b/lib/libspl/include/unistd.h
@@ -0,0 +1,39 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#include_next <unistd.h>
+
+#ifndef _LIBSPL_UNISTD_H
+#define _LIBSPL_UNISTD_H
+
+#include <sys/ioctl.h>
+
+#if !defined(HAVE_ISSETUGID)
+#include <sys/types.h>
+#define issetugid() (geteuid() == 0 || getegid() == 0)
+#endif
+
+#endif /* _LIBSPL_UNISTD_H */
diff --git a/lib/libspl/include/util/Makefile.am b/lib/libspl/include/util/Makefile.am
new file mode 100644
index 000000000000..ab553bc80313
--- /dev/null
+++ b/lib/libspl/include/util/Makefile.am
@@ -0,0 +1,3 @@
+libspldir = $(includedir)/libspl
+libspl_HEADERS = \
+ sscanf.h
diff --git a/lib/libspl/include/util/sscanf.h b/lib/libspl/include/util/sscanf.h
new file mode 100644
index 000000000000..ead36acaba3e
--- /dev/null
+++ b/lib/libspl/include/util/sscanf.h
@@ -0,0 +1,30 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2010 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _LIBSPL_UTIL_SSCANF_H
+#define _LIBSPL_UTIL_SSCANF_H
+
+#endif
diff --git a/lib/libspl/include/zone.h b/lib/libspl/include/zone.h
new file mode 100644
index 000000000000..b4a6deb40ce4
--- /dev/null
+++ b/lib/libspl/include/zone.h
@@ -0,0 +1,53 @@
+/*
+ * 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 2007 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _LIBSPL_ZONE_H
+#define _LIBSPL_ZONE_H
+
+
+
+#include <sys/types.h>
+#include <sys/zone.h>
+#include <sys/priv.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define GLOBAL_ZONEID 0
+#define GLOBAL_ZONEID_NAME "global"
+
+/*
+ * Functions for mapping between id and name for active zones.
+ */
+extern zoneid_t getzoneid(void);
+extern zoneid_t getzoneidbyname(const char *);
+extern ssize_t getzonenamebyid(zoneid_t, char *, size_t);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _LIBSPL_ZONE_H */
diff --git a/lib/libspl/list.c b/lib/libspl/list.c
new file mode 100644
index 000000000000..0f2f3731b235
--- /dev/null
+++ b/lib/libspl/list.c
@@ -0,0 +1,243 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+/*
+ * Generic doubly-linked list implementation
+ */
+
+#include <sys/list.h>
+#include <sys/list_impl.h>
+#include <sys/types.h>
+#include <sys/sysmacros.h>
+#include <sys/debug.h>
+
+#define list_d2l(a, obj) ((list_node_t *)(((char *)obj) + (a)->list_offset))
+#define list_object(a, node) ((void *)(((char *)node) - (a)->list_offset))
+#define list_empty(a) ((a)->list_head.next == &(a)->list_head)
+
+#define list_insert_after_node(list, node, object) { \
+ list_node_t *lnew = list_d2l(list, object); \
+ lnew->prev = (node); \
+ lnew->next = (node)->next; \
+ (node)->next->prev = lnew; \
+ (node)->next = lnew; \
+}
+
+#define list_insert_before_node(list, node, object) { \
+ list_node_t *lnew = list_d2l(list, object); \
+ lnew->next = (node); \
+ lnew->prev = (node)->prev; \
+ (node)->prev->next = lnew; \
+ (node)->prev = lnew; \
+}
+
+#define list_remove_node(node) \
+ (node)->prev->next = (node)->next; \
+ (node)->next->prev = (node)->prev; \
+ (node)->next = (node)->prev = NULL
+
+void
+list_create(list_t *list, size_t size, size_t offset)
+{
+ ASSERT(list);
+ ASSERT(size > 0);
+ ASSERT(size >= offset + sizeof (list_node_t));
+
+ list->list_size = size;
+ list->list_offset = offset;
+ list->list_head.next = list->list_head.prev = &list->list_head;
+}
+
+void
+list_destroy(list_t *list)
+{
+ list_node_t *node = &list->list_head;
+
+ ASSERT(list);
+ ASSERT(list->list_head.next == node);
+ ASSERT(list->list_head.prev == node);
+
+ node->next = node->prev = NULL;
+}
+
+void
+list_insert_after(list_t *list, void *object, void *nobject)
+{
+ if (object == NULL) {
+ list_insert_head(list, nobject);
+ } else {
+ list_node_t *lold = list_d2l(list, object);
+ list_insert_after_node(list, lold, nobject);
+ }
+}
+
+void
+list_insert_before(list_t *list, void *object, void *nobject)
+{
+ if (object == NULL) {
+ list_insert_tail(list, nobject);
+ } else {
+ list_node_t *lold = list_d2l(list, object);
+ list_insert_before_node(list, lold, nobject);
+ }
+}
+
+void
+list_insert_head(list_t *list, void *object)
+{
+ list_node_t *lold = &list->list_head;
+ list_insert_after_node(list, lold, object);
+}
+
+void
+list_insert_tail(list_t *list, void *object)
+{
+ list_node_t *lold = &list->list_head;
+ list_insert_before_node(list, lold, object);
+}
+
+void
+list_remove(list_t *list, void *object)
+{
+ list_node_t *lold = list_d2l(list, object);
+ ASSERT(!list_empty(list));
+ ASSERT(lold->next != NULL);
+ list_remove_node(lold);
+}
+
+void *
+list_remove_head(list_t *list)
+{
+ list_node_t *head = list->list_head.next;
+ if (head == &list->list_head)
+ return (NULL);
+ list_remove_node(head);
+ return (list_object(list, head));
+}
+
+void *
+list_remove_tail(list_t *list)
+{
+ list_node_t *tail = list->list_head.prev;
+ if (tail == &list->list_head)
+ return (NULL);
+ list_remove_node(tail);
+ return (list_object(list, tail));
+}
+
+void *
+list_head(list_t *list)
+{
+ if (list_empty(list))
+ return (NULL);
+ return (list_object(list, list->list_head.next));
+}
+
+void *
+list_tail(list_t *list)
+{
+ if (list_empty(list))
+ return (NULL);
+ return (list_object(list, list->list_head.prev));
+}
+
+void *
+list_next(list_t *list, void *object)
+{
+ list_node_t *node = list_d2l(list, object);
+
+ if (node->next != &list->list_head)
+ return (list_object(list, node->next));
+
+ return (NULL);
+}
+
+void *
+list_prev(list_t *list, void *object)
+{
+ list_node_t *node = list_d2l(list, object);
+
+ if (node->prev != &list->list_head)
+ return (list_object(list, node->prev));
+
+ return (NULL);
+}
+
+/*
+ * Insert src list after dst list. Empty src list thereafter.
+ */
+void
+list_move_tail(list_t *dst, list_t *src)
+{
+ list_node_t *dstnode = &dst->list_head;
+ list_node_t *srcnode = &src->list_head;
+
+ ASSERT(dst->list_size == src->list_size);
+ ASSERT(dst->list_offset == src->list_offset);
+
+ if (list_empty(src))
+ return;
+
+ dstnode->prev->next = srcnode->next;
+ srcnode->next->prev = dstnode->prev;
+ dstnode->prev = srcnode->prev;
+ srcnode->prev->next = dstnode;
+
+ /* empty src list */
+ srcnode->next = srcnode->prev = srcnode;
+}
+
+void
+list_link_replace(list_node_t *lold, list_node_t *lnew)
+{
+ ASSERT(list_link_active(lold));
+ ASSERT(!list_link_active(lnew));
+
+ lnew->next = lold->next;
+ lnew->prev = lold->prev;
+ lold->prev->next = lnew;
+ lold->next->prev = lnew;
+ lold->next = lold->prev = NULL;
+}
+
+void
+list_link_init(list_node_t *ln)
+{
+ ln->next = NULL;
+ ln->prev = NULL;
+}
+
+int
+list_link_active(list_node_t *ln)
+{
+ EQUIV(ln->next == NULL, ln->prev == NULL);
+ return (ln->next != NULL);
+}
+
+int
+list_is_empty(list_t *list)
+{
+ return (list_empty(list));
+}
diff --git a/lib/libspl/mkdirp.c b/lib/libspl/mkdirp.c
new file mode 100644
index 000000000000..fce2c1c82eb7
--- /dev/null
+++ b/lib/libspl/mkdirp.c
@@ -0,0 +1,212 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+/* Copyright (c) 1988 AT&T */
+/* All Rights Reserved */
+
+/*
+ * Creates directory and it's parents if the parents do not
+ * exist yet.
+ *
+ * Returns -1 if fails for reasons other than non-existing
+ * parents.
+ * Does NOT simplify pathnames with . or .. in them.
+ */
+
+#include <sys/types.h>
+#include <libgen.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/stat.h>
+
+static char *simplify(const char *str);
+
+int
+mkdirp(const char *d, mode_t mode)
+{
+ char *endptr, *ptr, *slash, *str;
+
+ str = simplify(d);
+
+ /* If space couldn't be allocated for the simplified names, return. */
+
+ if (str == NULL)
+ return (-1);
+
+ /* Try to make the directory */
+
+ if (mkdir(str, mode) == 0) {
+ free(str);
+ return (0);
+ }
+ if (errno != ENOENT) {
+ free(str);
+ return (-1);
+ }
+ endptr = strrchr(str, '\0');
+ slash = strrchr(str, '/');
+
+ /* Search upward for the non-existing parent */
+
+ while (slash != NULL) {
+
+ ptr = slash;
+ *ptr = '\0';
+
+ /* If reached an existing parent, break */
+
+ if (access(str, F_OK) == 0)
+ break;
+
+ /* If non-existing parent */
+
+ else {
+ slash = strrchr(str, '/');
+
+ /* If under / or current directory, make it. */
+
+ if (slash == NULL || slash == str) {
+ if (mkdir(str, mode) != 0 && errno != EEXIST) {
+ free(str);
+ return (-1);
+ }
+ break;
+ }
+ }
+ }
+
+ /* Create directories starting from upmost non-existing parent */
+
+ while ((ptr = strchr(str, '\0')) != endptr) {
+ *ptr = '/';
+ if (mkdir(str, mode) != 0 && errno != EEXIST) {
+ /*
+ * If the mkdir fails because str already
+ * exists (EEXIST), then str has the form
+ * "existing-dir/..", and this is really
+ * ok. (Remember, this loop is creating the
+ * portion of the path that didn't exist)
+ */
+ free(str);
+ return (-1);
+ }
+ }
+ free(str);
+ return (0);
+}
+
+/*
+ * simplify - given a pathname, simplify that path by removing
+ * duplicate contiguous slashes.
+ *
+ * A simplified copy of the argument is returned to the
+ * caller, or NULL is returned on error.
+ *
+ * The caller should handle error reporting based upon the
+ * returned value, and should free the returned value,
+ * when appropriate.
+ */
+
+static char *
+simplify(const char *str)
+{
+ int i;
+ size_t mbPathlen; /* length of multi-byte path */
+ size_t wcPathlen; /* length of wide-character path */
+ wchar_t *wptr; /* scratch pointer */
+ wchar_t *wcPath; /* wide-character version of the path */
+ char *mbPath; /* The copy fo the path to be returned */
+
+ /*
+ * bail out if there is nothing there.
+ */
+
+ if (!str) {
+ errno = ENOENT;
+ return (NULL);
+ }
+
+ /*
+ * Get a copy of the argument.
+ */
+
+ if ((mbPath = strdup(str)) == NULL) {
+ return (NULL);
+ }
+
+ /*
+ * convert the multi-byte version of the path to a
+ * wide-character rendering, for doing our figuring.
+ */
+
+ mbPathlen = strlen(mbPath);
+
+ if ((wcPath = calloc(mbPathlen+1, sizeof (wchar_t))) == NULL) {
+ free(mbPath);
+ return (NULL);
+ }
+
+ if ((wcPathlen = mbstowcs(wcPath, mbPath, mbPathlen)) == (size_t)-1) {
+ free(mbPath);
+ free(wcPath);
+ return (NULL);
+ }
+
+ /*
+ * remove duplicate slashes first ("//../" -> "/")
+ */
+
+ for (wptr = wcPath, i = 0; i < wcPathlen; i++) {
+ *wptr++ = wcPath[i];
+
+ if (wcPath[i] == '/') {
+ i++;
+
+ while (wcPath[i] == '/') {
+ i++;
+ }
+
+ i--;
+ }
+ }
+
+ *wptr = '\0';
+
+ /*
+ * now convert back to the multi-byte format.
+ */
+
+ if (wcstombs(mbPath, wcPath, mbPathlen) == (size_t)-1) {
+ free(mbPath);
+ free(wcPath);
+ return (NULL);
+ }
+
+ free(wcPath);
+ return (mbPath);
+}
diff --git a/lib/libspl/os/freebsd/getexecname.c b/lib/libspl/os/freebsd/getexecname.c
new file mode 100644
index 000000000000..2b057cc73016
--- /dev/null
+++ b/lib/libspl/os/freebsd/getexecname.c
@@ -0,0 +1,71 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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
+ */
+
+
+#include <errno.h>
+#include <limits.h>
+#include <pthread.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/param.h>
+#include <sys/sysctl.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+const char *
+getexecname(void)
+{
+ static char execname[PATH_MAX + 1] = "";
+ static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
+ char *ptr = NULL;
+ ssize_t rc;
+
+ (void) pthread_mutex_lock(&mtx);
+
+ if (strlen(execname) == 0) {
+ int error, name[4];
+ size_t len;
+
+ name[0] = CTL_KERN;
+ name[1] = KERN_PROC;
+ name[2] = KERN_PROC_PATHNAME;
+ name[3] = -1;
+ len = PATH_MAX;
+ error = sysctl(name, nitems(name), execname, &len, NULL, 0);
+ if (error != 0) {
+ rc = -1;
+ } else {
+ rc = len;
+ }
+ if (rc == -1) {
+ execname[0] = '\0';
+ } else {
+ execname[rc] = '\0';
+ ptr = execname;
+ }
+ } else {
+ ptr = execname;
+ }
+
+ (void) pthread_mutex_unlock(&mtx);
+ return (ptr);
+}
diff --git a/lib/libspl/os/freebsd/gethostid.c b/lib/libspl/os/freebsd/gethostid.c
new file mode 100644
index 000000000000..7bd567fe61b5
--- /dev/null
+++ b/lib/libspl/os/freebsd/gethostid.c
@@ -0,0 +1,36 @@
+/*
+ * 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, Lawrence Livermore National Security, LLC.
+ */
+
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/systeminfo.h>
+
+unsigned long
+get_system_hostid(void)
+{
+ return (gethostid());
+}
diff --git a/lib/libspl/os/freebsd/getmntany.c b/lib/libspl/os/freebsd/getmntany.c
new file mode 100644
index 000000000000..b41e763cee43
--- /dev/null
+++ b/lib/libspl/os/freebsd/getmntany.c
@@ -0,0 +1,67 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2005 Sun Microsystems, Inc. All rights reserved.
+ * Copyright 2006 Ricardo Correia. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+/* Copyright (c) 1988 AT&T */
+/* All Rights Reserved */
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/errno.h>
+#include <sys/mnttab.h>
+#include <sys/types.h>
+#include <sys/sysmacros.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#define BUFSIZE (MNT_LINE_MAX + 2)
+
+__thread char buf[BUFSIZE];
+
+int
+getextmntent(const char *path, struct extmnttab *entry, struct stat64 *statbuf)
+{
+ struct statfs sfs;
+
+ if (strlen(path) >= MAXPATHLEN) {
+ (void) fprintf(stderr, "invalid object; pathname too long\n");
+ return (-1);
+ }
+
+ if (stat64(path, statbuf) != 0) {
+ (void) fprintf(stderr, "cannot open '%s': %s\n",
+ path, strerror(errno));
+ return (-1);
+ }
+
+ if (statfs(path, &sfs) != 0) {
+ (void) fprintf(stderr, "%s: %s\n", path,
+ strerror(errno));
+ return (-1);
+ }
+ statfs2mnttab(&sfs, (struct mnttab *)entry);
+ return (0);
+}
diff --git a/lib/libspl/os/freebsd/mnttab.c b/lib/libspl/os/freebsd/mnttab.c
new file mode 100644
index 000000000000..5b9e6429d9e3
--- /dev/null
+++ b/lib/libspl/os/freebsd/mnttab.c
@@ -0,0 +1,216 @@
+/*
+ * Copyright (c) 2006 Pawel Jakub Dawidek <pjd@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * This file implements Solaris compatible getmntany() and hasmntopt()
+ * functions.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/mount.h>
+#include <sys/mntent.h>
+#include <sys/mnttab.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static char *
+mntopt(char **p)
+{
+ char *cp = *p;
+ char *retstr;
+
+ while (*cp && isspace(*cp))
+ cp++;
+
+ retstr = cp;
+ while (*cp && *cp != ',')
+ cp++;
+
+ if (*cp) {
+ *cp = '\0';
+ cp++;
+ }
+
+ *p = cp;
+ return (retstr);
+}
+
+char *
+hasmntopt(struct mnttab *mnt, char *opt)
+{
+ char tmpopts[MNT_LINE_MAX];
+ char *f, *opts = tmpopts;
+
+ if (mnt->mnt_mntopts == NULL)
+ return (NULL);
+ (void) strcpy(opts, mnt->mnt_mntopts);
+ f = mntopt(&opts);
+ for (; *f; f = mntopt(&opts)) {
+ if (strncmp(opt, f, strlen(opt)) == 0)
+ return (f - tmpopts + mnt->mnt_mntopts);
+ }
+ return (NULL);
+}
+
+static void
+optadd(char *mntopts, size_t size, const char *opt)
+{
+
+ if (mntopts[0] != '\0')
+ strlcat(mntopts, ",", size);
+ strlcat(mntopts, opt, size);
+}
+
+void
+statfs2mnttab(struct statfs *sfs, struct mnttab *mp)
+{
+ static char mntopts[MNTMAXSTR];
+ long flags;
+
+ mntopts[0] = '\0';
+
+ flags = sfs->f_flags;
+#define OPTADD(opt) optadd(mntopts, sizeof (mntopts), (opt))
+ if (flags & MNT_RDONLY)
+ OPTADD(MNTOPT_RO);
+ else
+ OPTADD(MNTOPT_RW);
+ if (flags & MNT_NOSUID)
+ OPTADD(MNTOPT_NOSETUID);
+ else
+ OPTADD(MNTOPT_SETUID);
+ if (flags & MNT_UPDATE)
+ OPTADD(MNTOPT_REMOUNT);
+ if (flags & MNT_NOATIME)
+ OPTADD(MNTOPT_NOATIME);
+ else
+ OPTADD(MNTOPT_ATIME);
+ OPTADD(MNTOPT_NOXATTR);
+ if (flags & MNT_NOEXEC)
+ OPTADD(MNTOPT_NOEXEC);
+ else
+ OPTADD(MNTOPT_EXEC);
+#undef OPTADD
+ mp->mnt_special = strdup(sfs->f_mntfromname);
+ mp->mnt_mountp = strdup(sfs->f_mntonname);
+ mp->mnt_fstype = strdup(sfs->f_fstypename);
+ mp->mnt_mntopts = strdup(mntopts);
+}
+
+static struct statfs *gsfs = NULL;
+static int allfs = 0;
+
+static int
+statfs_init(void)
+{
+ struct statfs *sfs;
+ int error;
+
+ if (gsfs != NULL) {
+ free(gsfs);
+ gsfs = NULL;
+ }
+ allfs = getfsstat(NULL, 0, MNT_WAIT);
+ if (allfs == -1)
+ goto fail;
+ gsfs = malloc(sizeof (gsfs[0]) * allfs * 2);
+ if (gsfs == NULL)
+ goto fail;
+ allfs = getfsstat(gsfs, (long)(sizeof (gsfs[0]) * allfs * 2),
+ MNT_WAIT);
+ if (allfs == -1)
+ goto fail;
+ sfs = realloc(gsfs, allfs * sizeof (gsfs[0]));
+ if (sfs != NULL)
+ gsfs = sfs;
+ return (0);
+fail:
+ error = errno;
+ if (gsfs != NULL)
+ free(gsfs);
+ gsfs = NULL;
+ allfs = 0;
+ return (error);
+}
+
+int
+getmntany(FILE *fd __unused, struct mnttab *mgetp, struct mnttab *mrefp)
+{
+ // struct statfs *sfs;
+ int i, error;
+
+ error = statfs_init();
+ if (error != 0)
+ return (error);
+
+ for (i = 0; i < allfs; i++) {
+ if (mrefp->mnt_special != NULL &&
+ strcmp(mrefp->mnt_special, gsfs[i].f_mntfromname) != 0) {
+ continue;
+ }
+ if (mrefp->mnt_mountp != NULL &&
+ strcmp(mrefp->mnt_mountp, gsfs[i].f_mntonname) != 0) {
+ continue;
+ }
+ if (mrefp->mnt_fstype != NULL &&
+ strcmp(mrefp->mnt_fstype, gsfs[i].f_fstypename) != 0) {
+ continue;
+ }
+ statfs2mnttab(&gsfs[i], mgetp);
+ return (0);
+ }
+ return (-1);
+}
+
+int
+getmntent(FILE *fp, struct mnttab *mp)
+{
+ // struct statfs *sfs;
+ int error, nfs;
+
+ nfs = (int)lseek(fileno(fp), 0, SEEK_CUR);
+ if (nfs == -1)
+ return (errno);
+ /* If nfs is 0, we want to refresh out cache. */
+ if (nfs == 0 || gsfs == NULL) {
+ error = statfs_init();
+ if (error != 0)
+ return (error);
+ }
+ if (nfs >= allfs)
+ return (-1);
+ statfs2mnttab(&gsfs[nfs], mp);
+ if (lseek(fileno(fp), 1, SEEK_CUR) == -1)
+ return (errno);
+ return (0);
+}
diff --git a/lib/libspl/os/linux/getexecname.c b/lib/libspl/os/linux/getexecname.c
new file mode 100644
index 000000000000..6352a1a34015
--- /dev/null
+++ b/lib/libspl/os/linux/getexecname.c
@@ -0,0 +1,59 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2007 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+
+#include <limits.h>
+#include <pthread.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+const char *
+getexecname(void)
+{
+ static char execname[PATH_MAX + 1] = "";
+ static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
+ char *ptr = NULL;
+ ssize_t rc;
+
+ (void) pthread_mutex_lock(&mtx);
+
+ if (strlen(execname) == 0) {
+ rc = readlink("/proc/self/exe",
+ execname, sizeof (execname) - 1);
+ if (rc == -1) {
+ execname[0] = '\0';
+ } else {
+ execname[rc] = '\0';
+ ptr = execname;
+ }
+ } else {
+ ptr = execname;
+ }
+
+ (void) pthread_mutex_unlock(&mtx);
+ return (ptr);
+}
diff --git a/lib/libspl/os/linux/gethostid.c b/lib/libspl/os/linux/gethostid.c
new file mode 100644
index 000000000000..1eb93f441142
--- /dev/null
+++ b/lib/libspl/os/linux/gethostid.c
@@ -0,0 +1,86 @@
+/*
+ * 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, Lawrence Livermore National Security, LLC.
+ */
+
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/systeminfo.h>
+
+static unsigned long
+get_spl_hostid(void)
+{
+ FILE *f;
+ unsigned long hostid;
+ char *env;
+
+ /*
+ * Allow the hostid to be subverted for testing.
+ */
+ env = getenv("ZFS_HOSTID");
+ if (env) {
+ hostid = strtoull(env, NULL, 0);
+ return (hostid & HOSTID_MASK);
+ }
+
+ f = fopen("/sys/module/spl/parameters/spl_hostid", "r");
+ if (!f)
+ return (0);
+
+ if (fscanf(f, "%lu", &hostid) != 1)
+ hostid = 0;
+
+ fclose(f);
+
+ return (hostid & HOSTID_MASK);
+}
+
+unsigned long
+get_system_hostid(void)
+{
+ unsigned long system_hostid = get_spl_hostid();
+ /*
+ * We do not use the library call gethostid() because
+ * it generates a hostid value that the kernel is
+ * unaware of, if the spl_hostid module parameter has not
+ * been set and there is no system hostid file (e.g.
+ * /etc/hostid). The kernel and userspace must agree.
+ * See comments above hostid_read() in the SPL.
+ */
+ if (system_hostid == 0) {
+ int fd, rc;
+ unsigned long hostid;
+ int hostid_size = 4; /* 4 bytes regardless of arch */
+
+ fd = open("/etc/hostid", O_RDONLY);
+ if (fd >= 0) {
+ rc = read(fd, &hostid, hostid_size);
+ if (rc > 0)
+ system_hostid = (hostid & HOSTID_MASK);
+ close(fd);
+ }
+ }
+ return (system_hostid);
+}
diff --git a/lib/libspl/os/linux/getmntany.c b/lib/libspl/os/linux/getmntany.c
new file mode 100644
index 000000000000..f42fcc047893
--- /dev/null
+++ b/lib/libspl/os/linux/getmntany.c
@@ -0,0 +1,165 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2005 Sun Microsystems, Inc. All rights reserved.
+ * Copyright 2006 Ricardo Correia. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+/* Copyright (c) 1988 AT&T */
+/* All Rights Reserved */
+
+#include <stdio.h>
+#include <string.h>
+#include <mntent.h>
+#include <sys/errno.h>
+#include <sys/mnttab.h>
+
+#include <sys/types.h>
+#include <sys/sysmacros.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#define BUFSIZE (MNT_LINE_MAX + 2)
+
+__thread char buf[BUFSIZE];
+
+#define DIFF(xx) ( \
+ (mrefp->xx != NULL) && \
+ (mgetp->xx == NULL || strcmp(mrefp->xx, mgetp->xx) != 0))
+
+int
+getmntany(FILE *fp, struct mnttab *mgetp, struct mnttab *mrefp)
+{
+ int ret;
+
+ while (
+ ((ret = _sol_getmntent(fp, mgetp)) == 0) && (
+ DIFF(mnt_special) || DIFF(mnt_mountp) ||
+ DIFF(mnt_fstype) || DIFF(mnt_mntopts))) { }
+
+ return (ret);
+}
+
+int
+_sol_getmntent(FILE *fp, struct mnttab *mgetp)
+{
+ struct mntent mntbuf;
+ struct mntent *ret;
+
+ ret = getmntent_r(fp, &mntbuf, buf, BUFSIZE);
+
+ if (ret != NULL) {
+ mgetp->mnt_special = mntbuf.mnt_fsname;
+ mgetp->mnt_mountp = mntbuf.mnt_dir;
+ mgetp->mnt_fstype = mntbuf.mnt_type;
+ mgetp->mnt_mntopts = mntbuf.mnt_opts;
+ return (0);
+ }
+
+ if (feof(fp))
+ return (-1);
+
+ return (MNT_TOOLONG);
+}
+
+static int
+getextmntent_impl(FILE *fp, struct extmnttab *mp, int len)
+{
+ int ret;
+ struct stat64 st;
+
+ ret = _sol_getmntent(fp, (struct mnttab *)mp);
+ if (ret == 0) {
+ if (stat64(mp->mnt_mountp, &st) != 0) {
+ mp->mnt_major = 0;
+ mp->mnt_minor = 0;
+ return (ret);
+ }
+ mp->mnt_major = major(st.st_dev);
+ mp->mnt_minor = minor(st.st_dev);
+ }
+
+ return (ret);
+}
+
+int
+getextmntent(const char *path, struct extmnttab *entry, struct stat64 *statbuf)
+{
+ struct stat64 st;
+ FILE *fp;
+ int match;
+
+ if (strlen(path) >= MAXPATHLEN) {
+ (void) fprintf(stderr, "invalid object; pathname too long\n");
+ return (-1);
+ }
+
+ /*
+ * Search for the path in /proc/self/mounts. Rather than looking for the
+ * specific path, which can be fooled by non-standard paths (i.e. ".."
+ * or "//"), we stat() the path and search for the corresponding
+ * (major,minor) device pair.
+ */
+ if (stat64(path, statbuf) != 0) {
+ (void) fprintf(stderr, "cannot open '%s': %s\n",
+ path, strerror(errno));
+ return (-1);
+ }
+
+
+#ifdef HAVE_SETMNTENT
+ if ((fp = setmntent(MNTTAB, "r")) == NULL) {
+#else
+ if ((fp = fopen(MNTTAB, "r")) == NULL) {
+#endif
+ (void) fprintf(stderr, "cannot open %s\n", MNTTAB);
+ return (-1);
+ }
+
+ /*
+ * Search for the given (major,minor) pair in the mount table.
+ */
+
+ match = 0;
+ while (getextmntent_impl(fp, entry, sizeof (*entry)) == 0) {
+ if (makedev(entry->mnt_major, entry->mnt_minor) ==
+ statbuf->st_dev) {
+ match = 1;
+ break;
+ }
+ }
+
+ if (!match) {
+ (void) fprintf(stderr, "cannot find mountpoint for '%s'\n",
+ path);
+ return (-1);
+ }
+
+ if (stat64(entry->mnt_mountp, &st) != 0) {
+ entry->mnt_major = 0;
+ entry->mnt_minor = 0;
+ return (-1);
+ }
+
+ return (0);
+}
diff --git a/lib/libspl/page.c b/lib/libspl/page.c
new file mode 100644
index 000000000000..06d9fcfa05cc
--- /dev/null
+++ b/lib/libspl/page.c
@@ -0,0 +1,34 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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
+ */
+
+#include <unistd.h>
+
+size_t pagesize = 0;
+
+size_t
+spl_pagesize(void)
+{
+ if (pagesize == 0)
+ pagesize = sysconf(_SC_PAGESIZE);
+
+ return (pagesize);
+}
diff --git a/lib/libspl/strlcat.c b/lib/libspl/strlcat.c
new file mode 100644
index 000000000000..4528d875ed5e
--- /dev/null
+++ b/lib/libspl/strlcat.c
@@ -0,0 +1,60 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+#ifndef HAVE_STRLCAT
+
+#include <string.h>
+#include <sys/types.h>
+
+/*
+ * Appends src to the dstsize buffer at dst. The append will never
+ * overflow the destination buffer and the buffer will always be null
+ * terminated. Never reference beyond &dst[dstsize-1] when computing
+ * the length of the pre-existing string.
+ */
+
+size_t
+strlcat(char *dst, const char *src, size_t dstsize)
+{
+ char *df = dst;
+ size_t left = dstsize;
+ size_t l1;
+ size_t l2 = strlen(src);
+ size_t copied;
+
+ while (left-- != 0 && *df != '\0')
+ df++;
+ l1 = df - dst;
+ if (dstsize == l1)
+ return (l1 + l2);
+
+ copied = l1 + l2 >= dstsize ? dstsize - l1 - 1 : l2;
+ (void) memcpy(dst + l1, src, copied);
+ dst[l1+copied] = '\0';
+
+ return (l1 + l2);
+}
+
+#endif
diff --git a/lib/libspl/strlcpy.c b/lib/libspl/strlcpy.c
new file mode 100644
index 000000000000..d483b91f6121
--- /dev/null
+++ b/lib/libspl/strlcpy.c
@@ -0,0 +1,56 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef HAVE_STRLCPY
+
+#include <string.h>
+#include <sys/types.h>
+
+/*
+ * Copies src to the dstsize buffer at dst. The copy will never
+ * overflow the destination buffer and the buffer will always be null
+ * terminated.
+ */
+
+size_t
+strlcpy(char *dst, const char *src, size_t len)
+{
+ size_t slen = strlen(src);
+ size_t copied;
+
+ if (len == 0)
+ return (slen);
+
+ if (slen >= len)
+ copied = len - 1;
+ else
+ copied = slen;
+ (void) memcpy(dst, src, copied);
+ dst[copied] = '\0';
+ return (slen);
+}
+
+#endif /* HAVE_STRLCPY */
diff --git a/lib/libspl/timestamp.c b/lib/libspl/timestamp.c
new file mode 100644
index 000000000000..22ecb3940739
--- /dev/null
+++ b/lib/libspl/timestamp.c
@@ -0,0 +1,63 @@
+/*
+ * 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 2009 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#include <stdio.h>
+#include <time.h>
+#include <langinfo.h>
+#include "statcommon.h"
+
+#ifndef _DATE_FMT
+#ifdef D_T_FMT
+#define _DATE_FMT D_T_FMT
+#else /* D_T_FMT */
+#define _DATE_FMT "%+"
+#endif /* !D_T_FMT */
+#endif /* _DATE_FMT */
+
+/*
+ * Print timestamp as decimal reprentation of time_t value (-T u was specified)
+ * or in date(1) format (-T d was specified).
+ */
+void
+print_timestamp(uint_t timestamp_fmt)
+{
+ time_t t = time(NULL);
+ static char *fmt = NULL;
+
+ /* We only need to retrieve this once per invocation */
+ if (fmt == NULL)
+ fmt = nl_langinfo(_DATE_FMT);
+
+ if (timestamp_fmt == UDATE) {
+ (void) printf("%lld\n", (longlong_t)t);
+ } else if (timestamp_fmt == DDATE) {
+ char dstr[64];
+ int len;
+
+ len = strftime(dstr, sizeof (dstr), fmt, localtime(&t));
+ if (len > 0)
+ (void) printf("%s\n", dstr);
+ }
+}
diff --git a/lib/libspl/zone.c b/lib/libspl/zone.c
new file mode 100644
index 000000000000..5ca93b224d9e
--- /dev/null
+++ b/lib/libspl/zone.c
@@ -0,0 +1,63 @@
+/*
+ * 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 2006 Ricardo Correia. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#include <zone.h>
+#include <string.h>
+#include <errno.h>
+
+zoneid_t
+getzoneid()
+{
+ return (GLOBAL_ZONEID);
+}
+
+zoneid_t
+getzoneidbyname(const char *name)
+{
+ if (name == NULL)
+ return (GLOBAL_ZONEID);
+
+ if (strcmp(name, GLOBAL_ZONEID_NAME) == 0)
+ return (GLOBAL_ZONEID);
+
+ return (EINVAL);
+}
+
+ssize_t
+getzonenamebyid(zoneid_t id, char *buf, size_t buflen)
+{
+ if (id != GLOBAL_ZONEID)
+ return (EINVAL);
+
+ ssize_t ret = strlen(GLOBAL_ZONEID_NAME) + 1;
+
+ if (buf == NULL || buflen == 0)
+ return (ret);
+
+ strncpy(buf, GLOBAL_ZONEID_NAME, buflen);
+ buf[buflen - 1] = '\0';
+
+ return (ret);
+}
diff --git a/lib/libtpool/Makefile.am b/lib/libtpool/Makefile.am
new file mode 100644
index 000000000000..22bfa4b23a8f
--- /dev/null
+++ b/lib/libtpool/Makefile.am
@@ -0,0 +1,9 @@
+include $(top_srcdir)/config/Rules.am
+
+noinst_LTLIBRARIES = libtpool.la
+
+USER_C = \
+ thread_pool.c \
+ thread_pool_impl.h
+
+libtpool_la_SOURCES = $(USER_C)
diff --git a/lib/libtpool/thread_pool.c b/lib/libtpool/thread_pool.c
new file mode 100644
index 000000000000..892beeffa5ae
--- /dev/null
+++ b/lib/libtpool/thread_pool.c
@@ -0,0 +1,599 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#include <stdlib.h>
+#include <signal.h>
+#include <errno.h>
+#include <assert.h>
+#include "thread_pool_impl.h"
+
+static pthread_mutex_t thread_pool_lock = PTHREAD_MUTEX_INITIALIZER;
+static tpool_t *thread_pools = NULL;
+
+static void
+delete_pool(tpool_t *tpool)
+{
+ tpool_job_t *job;
+
+ ASSERT(tpool->tp_current == 0 && tpool->tp_active == NULL);
+
+ /*
+ * Unlink the pool from the global list of all pools.
+ */
+ (void) pthread_mutex_lock(&thread_pool_lock);
+ if (thread_pools == tpool)
+ thread_pools = tpool->tp_forw;
+ if (thread_pools == tpool)
+ thread_pools = NULL;
+ else {
+ tpool->tp_back->tp_forw = tpool->tp_forw;
+ tpool->tp_forw->tp_back = tpool->tp_back;
+ }
+ pthread_mutex_unlock(&thread_pool_lock);
+
+ /*
+ * There should be no pending jobs, but just in case...
+ */
+ for (job = tpool->tp_head; job != NULL; job = tpool->tp_head) {
+ tpool->tp_head = job->tpj_next;
+ free(job);
+ }
+ (void) pthread_attr_destroy(&tpool->tp_attr);
+ free(tpool);
+}
+
+/*
+ * Worker thread is terminating.
+ */
+static void
+worker_cleanup(void *arg)
+{
+ tpool_t *tpool = (tpool_t *)arg;
+
+ if (--tpool->tp_current == 0 &&
+ (tpool->tp_flags & (TP_DESTROY | TP_ABANDON))) {
+ if (tpool->tp_flags & TP_ABANDON) {
+ pthread_mutex_unlock(&tpool->tp_mutex);
+ delete_pool(tpool);
+ return;
+ }
+ if (tpool->tp_flags & TP_DESTROY)
+ (void) pthread_cond_broadcast(&tpool->tp_busycv);
+ }
+ pthread_mutex_unlock(&tpool->tp_mutex);
+}
+
+static void
+notify_waiters(tpool_t *tpool)
+{
+ if (tpool->tp_head == NULL && tpool->tp_active == NULL) {
+ tpool->tp_flags &= ~TP_WAIT;
+ (void) pthread_cond_broadcast(&tpool->tp_waitcv);
+ }
+}
+
+/*
+ * Called by a worker thread on return from a tpool_dispatch()d job.
+ */
+static void
+job_cleanup(void *arg)
+{
+ tpool_t *tpool = (tpool_t *)arg;
+
+ pthread_t my_tid = pthread_self();
+ tpool_active_t *activep;
+ tpool_active_t **activepp;
+
+ pthread_mutex_lock(&tpool->tp_mutex);
+ /* CSTYLED */
+ for (activepp = &tpool->tp_active;; activepp = &activep->tpa_next) {
+ activep = *activepp;
+ if (activep->tpa_tid == my_tid) {
+ *activepp = activep->tpa_next;
+ break;
+ }
+ }
+ if (tpool->tp_flags & TP_WAIT)
+ notify_waiters(tpool);
+}
+
+static void *
+tpool_worker(void *arg)
+{
+ tpool_t *tpool = (tpool_t *)arg;
+ int elapsed;
+ tpool_job_t *job;
+ void (*func)(void *);
+ tpool_active_t active;
+
+ pthread_mutex_lock(&tpool->tp_mutex);
+ pthread_cleanup_push(worker_cleanup, tpool);
+
+ /*
+ * This is the worker's main loop.
+ * It will only be left if a timeout or an error has occurred.
+ */
+ active.tpa_tid = pthread_self();
+ for (;;) {
+ elapsed = 0;
+ tpool->tp_idle++;
+ if (tpool->tp_flags & TP_WAIT)
+ notify_waiters(tpool);
+ while ((tpool->tp_head == NULL ||
+ (tpool->tp_flags & TP_SUSPEND)) &&
+ !(tpool->tp_flags & (TP_DESTROY | TP_ABANDON))) {
+ if (tpool->tp_current <= tpool->tp_minimum ||
+ tpool->tp_linger == 0) {
+ (void) pthread_cond_wait(&tpool->tp_workcv,
+ &tpool->tp_mutex);
+ } else {
+ struct timespec ts;
+
+ clock_gettime(CLOCK_REALTIME, &ts);
+ ts.tv_sec += tpool->tp_linger;
+
+ if (pthread_cond_timedwait(&tpool->tp_workcv,
+ &tpool->tp_mutex, &ts) != 0) {
+ elapsed = 1;
+ break;
+ }
+ }
+ }
+ tpool->tp_idle--;
+ if (tpool->tp_flags & TP_DESTROY)
+ break;
+ if (tpool->tp_flags & TP_ABANDON) {
+ /* can't abandon a suspended pool */
+ if (tpool->tp_flags & TP_SUSPEND) {
+ tpool->tp_flags &= ~TP_SUSPEND;
+ (void) pthread_cond_broadcast(
+ &tpool->tp_workcv);
+ }
+ if (tpool->tp_head == NULL)
+ break;
+ }
+ if ((job = tpool->tp_head) != NULL &&
+ !(tpool->tp_flags & TP_SUSPEND)) {
+ elapsed = 0;
+ func = job->tpj_func;
+ arg = job->tpj_arg;
+ tpool->tp_head = job->tpj_next;
+ if (job == tpool->tp_tail)
+ tpool->tp_tail = NULL;
+ tpool->tp_njobs--;
+ active.tpa_next = tpool->tp_active;
+ tpool->tp_active = &active;
+ pthread_mutex_unlock(&tpool->tp_mutex);
+ pthread_cleanup_push(job_cleanup, tpool);
+ free(job);
+
+ sigset_t maskset;
+ (void) pthread_sigmask(SIG_SETMASK, NULL, &maskset);
+
+ /*
+ * Call the specified function.
+ */
+ func(arg);
+ /*
+ * We don't know what this thread has been doing,
+ * so we reset its signal mask and cancellation
+ * state back to the values prior to calling func().
+ */
+ (void) pthread_sigmask(SIG_SETMASK, &maskset, NULL);
+ (void) pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,
+ NULL);
+ (void) pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,
+ NULL);
+ pthread_cleanup_pop(1);
+ }
+ if (elapsed && tpool->tp_current > tpool->tp_minimum) {
+ /*
+ * We timed out and there is no work to be done
+ * and the number of workers exceeds the minimum.
+ * Exit now to reduce the size of the pool.
+ */
+ break;
+ }
+ }
+ pthread_cleanup_pop(1);
+ return (arg);
+}
+
+/*
+ * Create a worker thread, with default signals blocked.
+ */
+static int
+create_worker(tpool_t *tpool)
+{
+ pthread_t thread;
+ sigset_t oset;
+ int error;
+
+ (void) pthread_sigmask(SIG_SETMASK, NULL, &oset);
+ error = pthread_create(&thread, &tpool->tp_attr, tpool_worker, tpool);
+ (void) pthread_sigmask(SIG_SETMASK, &oset, NULL);
+ return (error);
+}
+
+
+/*
+ * pthread_attr_clone: make a copy of a pthread_attr_t. When old_attr
+ * is NULL initialize the cloned attr using default values.
+ */
+static int
+pthread_attr_clone(pthread_attr_t *attr, const pthread_attr_t *old_attr)
+{
+ int error;
+
+ error = pthread_attr_init(attr);
+ if (error || (old_attr == NULL))
+ return (error);
+
+#ifdef __GLIBC__
+ cpu_set_t cpuset;
+ size_t cpusetsize = sizeof (cpuset);
+ error = pthread_attr_getaffinity_np(old_attr, cpusetsize, &cpuset);
+ if (error == 0)
+ error = pthread_attr_setaffinity_np(attr, cpusetsize, &cpuset);
+ if (error)
+ goto error;
+#endif /* __GLIBC__ */
+
+ int detachstate;
+ error = pthread_attr_getdetachstate(old_attr, &detachstate);
+ if (error == 0)
+ error = pthread_attr_setdetachstate(attr, detachstate);
+ if (error)
+ goto error;
+
+ size_t guardsize;
+ error = pthread_attr_getguardsize(old_attr, &guardsize);
+ if (error == 0)
+ error = pthread_attr_setguardsize(attr, guardsize);
+ if (error)
+ goto error;
+
+ int inheritsched;
+ error = pthread_attr_getinheritsched(old_attr, &inheritsched);
+ if (error == 0)
+ error = pthread_attr_setinheritsched(attr, inheritsched);
+ if (error)
+ goto error;
+
+ struct sched_param param;
+ error = pthread_attr_getschedparam(old_attr, &param);
+ if (error == 0)
+ error = pthread_attr_setschedparam(attr, &param);
+ if (error)
+ goto error;
+
+ int policy;
+ error = pthread_attr_getschedpolicy(old_attr, &policy);
+ if (error == 0)
+ error = pthread_attr_setschedpolicy(attr, policy);
+ if (error)
+ goto error;
+
+ int scope;
+ error = pthread_attr_getscope(old_attr, &scope);
+ if (error == 0)
+ error = pthread_attr_setscope(attr, scope);
+ if (error)
+ goto error;
+
+ void *stackaddr;
+ size_t stacksize;
+ error = pthread_attr_getstack(old_attr, &stackaddr, &stacksize);
+ if (error == 0)
+ error = pthread_attr_setstack(attr, stackaddr, stacksize);
+ if (error)
+ goto error;
+
+ return (0);
+error:
+ pthread_attr_destroy(attr);
+ return (error);
+}
+
+tpool_t *
+tpool_create(uint_t min_threads, uint_t max_threads, uint_t linger,
+ pthread_attr_t *attr)
+{
+ tpool_t *tpool;
+ void *stackaddr;
+ size_t stacksize;
+ size_t minstack;
+ int error;
+
+ if (min_threads > max_threads || max_threads < 1) {
+ errno = EINVAL;
+ return (NULL);
+ }
+ if (attr != NULL) {
+ if (pthread_attr_getstack(attr, &stackaddr, &stacksize) != 0) {
+ errno = EINVAL;
+ return (NULL);
+ }
+ /*
+ * Allow only one thread in the pool with a specified stack.
+ * Require threads to have at least the minimum stack size.
+ */
+ minstack = PTHREAD_STACK_MIN;
+ if (stackaddr != NULL) {
+ if (stacksize < minstack || max_threads != 1) {
+ errno = EINVAL;
+ return (NULL);
+ }
+ } else if (stacksize != 0 && stacksize < minstack) {
+ errno = EINVAL;
+ return (NULL);
+ }
+ }
+
+ tpool = calloc(1, sizeof (*tpool));
+ if (tpool == NULL) {
+ errno = ENOMEM;
+ return (NULL);
+ }
+ (void) pthread_mutex_init(&tpool->tp_mutex, NULL);
+ (void) pthread_cond_init(&tpool->tp_busycv, NULL);
+ (void) pthread_cond_init(&tpool->tp_workcv, NULL);
+ (void) pthread_cond_init(&tpool->tp_waitcv, NULL);
+ tpool->tp_minimum = min_threads;
+ tpool->tp_maximum = max_threads;
+ tpool->tp_linger = linger;
+
+ /*
+ * We cannot just copy the attribute pointer.
+ * We need to initialize a new pthread_attr_t structure
+ * with the values from the user-supplied pthread_attr_t.
+ * If the attribute pointer is NULL, we need to initialize
+ * the new pthread_attr_t structure with default values.
+ */
+ error = pthread_attr_clone(&tpool->tp_attr, attr);
+ if (error) {
+ free(tpool);
+ errno = error;
+ return (NULL);
+ }
+
+ /* make all pool threads be detached daemon threads */
+ (void) pthread_attr_setdetachstate(&tpool->tp_attr,
+ PTHREAD_CREATE_DETACHED);
+
+ /* insert into the global list of all thread pools */
+ pthread_mutex_lock(&thread_pool_lock);
+ if (thread_pools == NULL) {
+ tpool->tp_forw = tpool;
+ tpool->tp_back = tpool;
+ thread_pools = tpool;
+ } else {
+ thread_pools->tp_back->tp_forw = tpool;
+ tpool->tp_forw = thread_pools;
+ tpool->tp_back = thread_pools->tp_back;
+ thread_pools->tp_back = tpool;
+ }
+ pthread_mutex_unlock(&thread_pool_lock);
+
+ return (tpool);
+}
+
+/*
+ * Dispatch a work request to the thread pool.
+ * If there are idle workers, awaken one.
+ * Else, if the maximum number of workers has
+ * not been reached, spawn a new worker thread.
+ * Else just return with the job added to the queue.
+ */
+int
+tpool_dispatch(tpool_t *tpool, void (*func)(void *), void *arg)
+{
+ tpool_job_t *job;
+
+ ASSERT(!(tpool->tp_flags & (TP_DESTROY | TP_ABANDON)));
+
+ if ((job = calloc(1, sizeof (*job))) == NULL)
+ return (-1);
+ job->tpj_next = NULL;
+ job->tpj_func = func;
+ job->tpj_arg = arg;
+
+ pthread_mutex_lock(&tpool->tp_mutex);
+
+ if (tpool->tp_head == NULL)
+ tpool->tp_head = job;
+ else
+ tpool->tp_tail->tpj_next = job;
+ tpool->tp_tail = job;
+ tpool->tp_njobs++;
+
+ if (!(tpool->tp_flags & TP_SUSPEND)) {
+ if (tpool->tp_idle > 0)
+ (void) pthread_cond_signal(&tpool->tp_workcv);
+ else if (tpool->tp_current < tpool->tp_maximum &&
+ create_worker(tpool) == 0)
+ tpool->tp_current++;
+ }
+
+ pthread_mutex_unlock(&tpool->tp_mutex);
+ return (0);
+}
+
+static void
+tpool_cleanup(void *arg)
+{
+ tpool_t *tpool = (tpool_t *)arg;
+
+ pthread_mutex_unlock(&tpool->tp_mutex);
+}
+
+/*
+ * Assumes: by the time tpool_destroy() is called no one will use this
+ * thread pool in any way and no one will try to dispatch entries to it.
+ * Calling tpool_destroy() from a job in the pool will cause deadlock.
+ */
+void
+tpool_destroy(tpool_t *tpool)
+{
+ tpool_active_t *activep;
+
+ ASSERT(!tpool_member(tpool));
+ ASSERT(!(tpool->tp_flags & (TP_DESTROY | TP_ABANDON)));
+
+ pthread_mutex_lock(&tpool->tp_mutex);
+ pthread_cleanup_push(tpool_cleanup, tpool);
+
+ /* mark the pool as being destroyed; wakeup idle workers */
+ tpool->tp_flags |= TP_DESTROY;
+ tpool->tp_flags &= ~TP_SUSPEND;
+ (void) pthread_cond_broadcast(&tpool->tp_workcv);
+
+ /* cancel all active workers */
+ for (activep = tpool->tp_active; activep; activep = activep->tpa_next)
+ (void) pthread_cancel(activep->tpa_tid);
+
+ /* wait for all active workers to finish */
+ while (tpool->tp_active != NULL) {
+ tpool->tp_flags |= TP_WAIT;
+ (void) pthread_cond_wait(&tpool->tp_waitcv, &tpool->tp_mutex);
+ }
+
+ /* the last worker to terminate will wake us up */
+ while (tpool->tp_current != 0)
+ (void) pthread_cond_wait(&tpool->tp_busycv, &tpool->tp_mutex);
+
+ pthread_cleanup_pop(1); /* pthread_mutex_unlock(&tpool->tp_mutex); */
+ delete_pool(tpool);
+}
+
+/*
+ * Like tpool_destroy(), but don't cancel workers or wait for them to finish.
+ * The last worker to terminate will delete the pool.
+ */
+void
+tpool_abandon(tpool_t *tpool)
+{
+ ASSERT(!(tpool->tp_flags & (TP_DESTROY | TP_ABANDON)));
+
+ pthread_mutex_lock(&tpool->tp_mutex);
+ if (tpool->tp_current == 0) {
+ /* no workers, just delete the pool */
+ pthread_mutex_unlock(&tpool->tp_mutex);
+ delete_pool(tpool);
+ } else {
+ /* wake up all workers, last one will delete the pool */
+ tpool->tp_flags |= TP_ABANDON;
+ tpool->tp_flags &= ~TP_SUSPEND;
+ (void) pthread_cond_broadcast(&tpool->tp_workcv);
+ pthread_mutex_unlock(&tpool->tp_mutex);
+ }
+}
+
+/*
+ * Wait for all jobs to complete.
+ * Calling tpool_wait() from a job in the pool will cause deadlock.
+ */
+void
+tpool_wait(tpool_t *tpool)
+{
+ ASSERT(!tpool_member(tpool));
+ ASSERT(!(tpool->tp_flags & (TP_DESTROY | TP_ABANDON)));
+
+ pthread_mutex_lock(&tpool->tp_mutex);
+ pthread_cleanup_push(tpool_cleanup, tpool);
+ while (tpool->tp_head != NULL || tpool->tp_active != NULL) {
+ tpool->tp_flags |= TP_WAIT;
+ (void) pthread_cond_wait(&tpool->tp_waitcv, &tpool->tp_mutex);
+ ASSERT(!(tpool->tp_flags & (TP_DESTROY | TP_ABANDON)));
+ }
+ pthread_cleanup_pop(1); /* pthread_mutex_unlock(&tpool->tp_mutex); */
+}
+
+void
+tpool_suspend(tpool_t *tpool)
+{
+ ASSERT(!(tpool->tp_flags & (TP_DESTROY | TP_ABANDON)));
+
+ pthread_mutex_lock(&tpool->tp_mutex);
+ tpool->tp_flags |= TP_SUSPEND;
+ pthread_mutex_unlock(&tpool->tp_mutex);
+}
+
+int
+tpool_suspended(tpool_t *tpool)
+{
+ int suspended;
+
+ ASSERT(!(tpool->tp_flags & (TP_DESTROY | TP_ABANDON)));
+
+ pthread_mutex_lock(&tpool->tp_mutex);
+ suspended = (tpool->tp_flags & TP_SUSPEND) != 0;
+ pthread_mutex_unlock(&tpool->tp_mutex);
+
+ return (suspended);
+}
+
+void
+tpool_resume(tpool_t *tpool)
+{
+ int excess;
+
+ ASSERT(!(tpool->tp_flags & (TP_DESTROY | TP_ABANDON)));
+
+ pthread_mutex_lock(&tpool->tp_mutex);
+ if (!(tpool->tp_flags & TP_SUSPEND)) {
+ pthread_mutex_unlock(&tpool->tp_mutex);
+ return;
+ }
+ tpool->tp_flags &= ~TP_SUSPEND;
+ (void) pthread_cond_broadcast(&tpool->tp_workcv);
+ excess = tpool->tp_njobs - tpool->tp_idle;
+ while (excess-- > 0 && tpool->tp_current < tpool->tp_maximum) {
+ if (create_worker(tpool) != 0)
+ break; /* pthread_create() failed */
+ tpool->tp_current++;
+ }
+ pthread_mutex_unlock(&tpool->tp_mutex);
+}
+
+int
+tpool_member(tpool_t *tpool)
+{
+ pthread_t my_tid = pthread_self();
+ tpool_active_t *activep;
+
+ ASSERT(!(tpool->tp_flags & (TP_DESTROY | TP_ABANDON)));
+
+ pthread_mutex_lock(&tpool->tp_mutex);
+ for (activep = tpool->tp_active; activep; activep = activep->tpa_next) {
+ if (activep->tpa_tid == my_tid) {
+ pthread_mutex_unlock(&tpool->tp_mutex);
+ return (1);
+ }
+ }
+ pthread_mutex_unlock(&tpool->tp_mutex);
+ return (0);
+}
diff --git a/lib/libtpool/thread_pool_impl.h b/lib/libtpool/thread_pool_impl.h
new file mode 100644
index 000000000000..5349c2ade8c3
--- /dev/null
+++ b/lib/libtpool/thread_pool_impl.h
@@ -0,0 +1,93 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _THREAD_POOL_IMPL_H
+#define _THREAD_POOL_IMPL_H
+
+#include <thread_pool.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Thread pool implementation definitions.
+ * See <thread_pool.h> for interface declarations.
+ */
+
+/*
+ * FIFO queued job
+ */
+typedef struct tpool_job tpool_job_t;
+struct tpool_job {
+ tpool_job_t *tpj_next; /* list of jobs */
+ void (*tpj_func)(void *); /* function to call */
+ void *tpj_arg; /* its argument */
+};
+
+/*
+ * List of active threads, linked through their stacks.
+ */
+typedef struct tpool_active tpool_active_t;
+struct tpool_active {
+ tpool_active_t *tpa_next; /* list of active threads */
+ pthread_t tpa_tid; /* active thread id */
+};
+
+/*
+ * The thread pool.
+ */
+struct tpool {
+ tpool_t *tp_forw; /* circular list of all thread pools */
+ tpool_t *tp_back;
+ pthread_mutex_t tp_mutex; /* protects the pool data */
+ pthread_cond_t tp_busycv; /* synchronization in tpool_dispatch */
+ pthread_cond_t tp_workcv; /* synchronization with workers */
+ pthread_cond_t tp_waitcv; /* synchronization in tpool_wait() */
+ tpool_active_t *tp_active; /* threads performing work */
+ tpool_job_t *tp_head; /* FIFO job queue */
+ tpool_job_t *tp_tail;
+ pthread_attr_t tp_attr; /* attributes of the workers */
+ int tp_flags; /* see below */
+ uint_t tp_linger; /* seconds before idle workers exit */
+ int tp_njobs; /* number of jobs in job queue */
+ int tp_minimum; /* minimum number of worker threads */
+ int tp_maximum; /* maximum number of worker threads */
+ int tp_current; /* current number of worker threads */
+ int tp_idle; /* number of idle workers */
+};
+
+/* tp_flags */
+#define TP_WAIT 0x01 /* waiting in tpool_wait() */
+#define TP_SUSPEND 0x02 /* pool is being suspended */
+#define TP_DESTROY 0x04 /* pool is being destroyed */
+#define TP_ABANDON 0x08 /* pool is abandoned (auto-destroy) */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _THREAD_POOL_IMPL_H */
diff --git a/lib/libunicode/Makefile.am b/lib/libunicode/Makefile.am
new file mode 100644
index 000000000000..fa9dd359d2ba
--- /dev/null
+++ b/lib/libunicode/Makefile.am
@@ -0,0 +1,15 @@
+include $(top_srcdir)/config/Rules.am
+
+VPATH = $(top_srcdir)/module/unicode
+
+# Includes kernel code, generate warnings for large stack frames
+AM_CFLAGS += $(FRAME_LARGER_THAN)
+
+noinst_LTLIBRARIES = libunicode.la
+
+KERNEL_C = \
+ u8_textprep.c \
+ uconv.c
+
+nodist_libunicode_la_SOURCES = \
+ $(KERNEL_C)
diff --git a/lib/libuutil/Makefile.am b/lib/libuutil/Makefile.am
new file mode 100644
index 000000000000..8d9b32e44802
--- /dev/null
+++ b/lib/libuutil/Makefile.am
@@ -0,0 +1,34 @@
+include $(top_srcdir)/config/Rules.am
+
+lib_LTLIBRARIES = libuutil.la
+
+USER_C = \
+ uu_alloc.c \
+ uu_avl.c \
+ uu_dprintf.c \
+ uu_ident.c \
+ uu_list.c \
+ uu_misc.c \
+ uu_open.c \
+ uu_pname.c \
+ uu_string.c
+
+libuutil_la_SOURCES = $(USER_C)
+
+libuutil_la_LIBADD = \
+ $(abs_top_builddir)/lib/libavl/libavl.la \
+ $(abs_top_builddir)/lib/libspl/libspl.la
+
+libuutil_la_LIBADD += $(LTLIBINTL)
+
+libuutil_la_LDFLAGS = -pthread
+
+if !ASAN_ENABLED
+libuutil_la_LDFLAGS += -Wl,-z,defs
+endif
+
+if BUILD_FREEBSD
+libuutil_la_LDFLAGS += -version-info 3:0:0
+else
+libuutil_la_LDFLAGS += -version-info 1:1:0
+endif
diff --git a/lib/libuutil/uu_alloc.c b/lib/libuutil/uu_alloc.c
new file mode 100644
index 000000000000..2bef759d525e
--- /dev/null
+++ b/lib/libuutil/uu_alloc.c
@@ -0,0 +1,135 @@
+/*
+ * 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) 2004, 2010, Oracle and/or its affiliates. All rights reserved.
+ */
+
+#include "libuutil_common.h"
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+void *
+uu_zalloc(size_t n)
+{
+ void *p = malloc(n);
+
+ if (p == NULL) {
+ uu_set_error(UU_ERROR_SYSTEM);
+ return (NULL);
+ }
+
+ (void) memset(p, 0, n);
+
+ return (p);
+}
+
+void
+uu_free(void *p)
+{
+ free(p);
+}
+
+char *
+uu_strdup(const char *str)
+{
+ char *buf = NULL;
+
+ if (str != NULL) {
+ size_t sz;
+
+ sz = strlen(str) + 1;
+ buf = uu_zalloc(sz);
+ if (buf != NULL)
+ (void) memcpy(buf, str, sz);
+ }
+ return (buf);
+}
+
+/*
+ * Duplicate up to n bytes of a string. Kind of sort of like
+ * strdup(strlcpy(s, n)).
+ */
+char *
+uu_strndup(const char *s, size_t n)
+{
+ size_t len;
+ char *p;
+
+ len = strnlen(s, n);
+ p = uu_zalloc(len + 1);
+ if (p == NULL)
+ return (NULL);
+
+ if (len > 0)
+ (void) memcpy(p, s, len);
+ p[len] = '\0';
+
+ return (p);
+}
+
+/*
+ * Duplicate a block of memory. Combines malloc with memcpy, much as
+ * strdup combines malloc, strlen, and strcpy.
+ */
+void *
+uu_memdup(const void *buf, size_t sz)
+{
+ void *p;
+
+ p = uu_zalloc(sz);
+ if (p == NULL)
+ return (NULL);
+ (void) memcpy(p, buf, sz);
+ return (p);
+}
+
+char *
+uu_msprintf(const char *format, ...)
+{
+ va_list args;
+ char attic[1];
+ uint_t M, m;
+ char *b;
+
+ va_start(args, format);
+ M = vsnprintf(attic, 1, format, args);
+ va_end(args);
+
+ for (;;) {
+ m = M;
+ if ((b = uu_zalloc(m + 1)) == NULL)
+ return (NULL);
+
+ va_start(args, format);
+ M = vsnprintf(b, m + 1, format, args);
+ va_end(args);
+
+ if (M == m)
+ break; /* sizes match */
+
+ uu_free(b);
+ }
+
+ return (b);
+}
diff --git a/lib/libuutil/uu_avl.c b/lib/libuutil/uu_avl.c
new file mode 100644
index 000000000000..040008883aec
--- /dev/null
+++ b/lib/libuutil/uu_avl.c
@@ -0,0 +1,569 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+
+
+#include "libuutil_common.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/avl.h>
+
+static uu_avl_pool_t uu_null_apool = { &uu_null_apool, &uu_null_apool };
+static pthread_mutex_t uu_apool_list_lock = PTHREAD_MUTEX_INITIALIZER;
+
+/*
+ * The index mark change on every insert and delete, to catch stale
+ * references.
+ *
+ * We leave the low bit alone, since the avl code uses it.
+ */
+#define INDEX_MAX (sizeof (uintptr_t) - 2)
+#define INDEX_NEXT(m) (((m) == INDEX_MAX)? 2 : ((m) + 2) & INDEX_MAX)
+
+#define INDEX_DECODE(i) ((i) & ~INDEX_MAX)
+#define INDEX_ENCODE(p, n) (((n) & ~INDEX_MAX) | (p)->ua_index)
+#define INDEX_VALID(p, i) (((i) & INDEX_MAX) == (p)->ua_index)
+#define INDEX_CHECK(i) (((i) & INDEX_MAX) != 0)
+
+/*
+ * When an element is inactive (not in a tree), we keep a marked pointer to
+ * its containing pool in its first word, and a NULL pointer in its second.
+ *
+ * On insert, we use these to verify that it comes from the correct pool.
+ */
+#define NODE_ARRAY(p, n) ((uintptr_t *)((uintptr_t)(n) + \
+ (pp)->uap_nodeoffset))
+
+#define POOL_TO_MARKER(pp) (((uintptr_t)(pp) | 1))
+
+#define DEAD_MARKER 0xc4
+
+uu_avl_pool_t *
+uu_avl_pool_create(const char *name, size_t objsize, size_t nodeoffset,
+ uu_compare_fn_t *compare_func, uint32_t flags)
+{
+ uu_avl_pool_t *pp, *next, *prev;
+
+ if (name == NULL ||
+ uu_check_name(name, UU_NAME_DOMAIN) == -1 ||
+ nodeoffset + sizeof (uu_avl_node_t) > objsize ||
+ compare_func == NULL) {
+ uu_set_error(UU_ERROR_INVALID_ARGUMENT);
+ return (NULL);
+ }
+
+ if (flags & ~UU_AVL_POOL_DEBUG) {
+ uu_set_error(UU_ERROR_UNKNOWN_FLAG);
+ return (NULL);
+ }
+
+ pp = uu_zalloc(sizeof (uu_avl_pool_t));
+ if (pp == NULL) {
+ uu_set_error(UU_ERROR_NO_MEMORY);
+ return (NULL);
+ }
+
+ (void) strlcpy(pp->uap_name, name, sizeof (pp->uap_name));
+ pp->uap_nodeoffset = nodeoffset;
+ pp->uap_objsize = objsize;
+ pp->uap_cmp = compare_func;
+ if (flags & UU_AVL_POOL_DEBUG)
+ pp->uap_debug = 1;
+ pp->uap_last_index = 0;
+
+ (void) pthread_mutex_init(&pp->uap_lock, NULL);
+
+ pp->uap_null_avl.ua_next_enc = UU_PTR_ENCODE(&pp->uap_null_avl);
+ pp->uap_null_avl.ua_prev_enc = UU_PTR_ENCODE(&pp->uap_null_avl);
+
+ (void) pthread_mutex_lock(&uu_apool_list_lock);
+ pp->uap_next = next = &uu_null_apool;
+ pp->uap_prev = prev = next->uap_prev;
+ next->uap_prev = pp;
+ prev->uap_next = pp;
+ (void) pthread_mutex_unlock(&uu_apool_list_lock);
+
+ return (pp);
+}
+
+void
+uu_avl_pool_destroy(uu_avl_pool_t *pp)
+{
+ if (pp->uap_debug) {
+ if (pp->uap_null_avl.ua_next_enc !=
+ UU_PTR_ENCODE(&pp->uap_null_avl) ||
+ pp->uap_null_avl.ua_prev_enc !=
+ UU_PTR_ENCODE(&pp->uap_null_avl)) {
+ uu_panic("uu_avl_pool_destroy: Pool \"%.*s\" (%p) has "
+ "outstanding avls, or is corrupt.\n",
+ (int)sizeof (pp->uap_name), pp->uap_name,
+ (void *)pp);
+ }
+ }
+ (void) pthread_mutex_lock(&uu_apool_list_lock);
+ pp->uap_next->uap_prev = pp->uap_prev;
+ pp->uap_prev->uap_next = pp->uap_next;
+ (void) pthread_mutex_unlock(&uu_apool_list_lock);
+ pp->uap_prev = NULL;
+ pp->uap_next = NULL;
+ uu_free(pp);
+}
+
+void
+uu_avl_node_init(void *base, uu_avl_node_t *np, uu_avl_pool_t *pp)
+{
+ uintptr_t *na = (uintptr_t *)np;
+
+ if (pp->uap_debug) {
+ uintptr_t offset = (uintptr_t)np - (uintptr_t)base;
+ if (offset + sizeof (*np) > pp->uap_objsize) {
+ uu_panic("uu_avl_node_init(%p, %p, %p (\"%s\")): "
+ "offset %ld doesn't fit in object (size %ld)\n",
+ base, (void *)np, (void *)pp, pp->uap_name,
+ (long)offset, (long)pp->uap_objsize);
+ }
+ if (offset != pp->uap_nodeoffset) {
+ uu_panic("uu_avl_node_init(%p, %p, %p (\"%s\")): "
+ "offset %ld doesn't match pool's offset (%ld)\n",
+ base, (void *)np, (void *)pp, pp->uap_name,
+ (long)offset, (long)pp->uap_objsize);
+ }
+ }
+
+ na[0] = POOL_TO_MARKER(pp);
+ na[1] = 0;
+}
+
+void
+uu_avl_node_fini(void *base, uu_avl_node_t *np, uu_avl_pool_t *pp)
+{
+ uintptr_t *na = (uintptr_t *)np;
+
+ if (pp->uap_debug) {
+ if (na[0] == DEAD_MARKER && na[1] == DEAD_MARKER) {
+ uu_panic("uu_avl_node_fini(%p, %p, %p (\"%s\")): "
+ "node already finied\n",
+ base, (void *)np, (void *)pp, pp->uap_name);
+ }
+ if (na[0] != POOL_TO_MARKER(pp) || na[1] != 0) {
+ uu_panic("uu_avl_node_fini(%p, %p, %p (\"%s\")): "
+ "node corrupt, in tree, or in different pool\n",
+ base, (void *)np, (void *)pp, pp->uap_name);
+ }
+ }
+
+ na[0] = DEAD_MARKER;
+ na[1] = DEAD_MARKER;
+ na[2] = DEAD_MARKER;
+}
+
+struct uu_avl_node_compare_info {
+ uu_compare_fn_t *ac_compare;
+ void *ac_private;
+ void *ac_right;
+ void *ac_found;
+};
+
+static int
+uu_avl_node_compare(const void *l, const void *r)
+{
+ struct uu_avl_node_compare_info *info =
+ (struct uu_avl_node_compare_info *)l;
+
+ int res = info->ac_compare(r, info->ac_right, info->ac_private);
+
+ if (res == 0) {
+ if (info->ac_found == NULL)
+ info->ac_found = (void *)r;
+ return (-1);
+ }
+ if (res < 0)
+ return (1);
+ return (-1);
+}
+
+uu_avl_t *
+uu_avl_create(uu_avl_pool_t *pp, void *parent, uint32_t flags)
+{
+ uu_avl_t *ap, *next, *prev;
+
+ if (flags & ~UU_AVL_DEBUG) {
+ uu_set_error(UU_ERROR_UNKNOWN_FLAG);
+ return (NULL);
+ }
+
+ ap = uu_zalloc(sizeof (*ap));
+ if (ap == NULL) {
+ uu_set_error(UU_ERROR_NO_MEMORY);
+ return (NULL);
+ }
+
+ ap->ua_pool = pp;
+ ap->ua_parent_enc = UU_PTR_ENCODE(parent);
+ ap->ua_debug = pp->uap_debug || (flags & UU_AVL_DEBUG);
+ ap->ua_index = (pp->uap_last_index = INDEX_NEXT(pp->uap_last_index));
+
+ avl_create(&ap->ua_tree, &uu_avl_node_compare, pp->uap_objsize,
+ pp->uap_nodeoffset);
+
+ ap->ua_null_walk.uaw_next = &ap->ua_null_walk;
+ ap->ua_null_walk.uaw_prev = &ap->ua_null_walk;
+
+ (void) pthread_mutex_lock(&pp->uap_lock);
+ next = &pp->uap_null_avl;
+ prev = UU_PTR_DECODE(next->ua_prev_enc);
+ ap->ua_next_enc = UU_PTR_ENCODE(next);
+ ap->ua_prev_enc = UU_PTR_ENCODE(prev);
+ next->ua_prev_enc = UU_PTR_ENCODE(ap);
+ prev->ua_next_enc = UU_PTR_ENCODE(ap);
+ (void) pthread_mutex_unlock(&pp->uap_lock);
+
+ return (ap);
+}
+
+void
+uu_avl_destroy(uu_avl_t *ap)
+{
+ uu_avl_pool_t *pp = ap->ua_pool;
+
+ if (ap->ua_debug) {
+ if (avl_numnodes(&ap->ua_tree) != 0) {
+ uu_panic("uu_avl_destroy(%p): tree not empty\n",
+ (void *)ap);
+ }
+ if (ap->ua_null_walk.uaw_next != &ap->ua_null_walk ||
+ ap->ua_null_walk.uaw_prev != &ap->ua_null_walk) {
+ uu_panic("uu_avl_destroy(%p): outstanding walkers\n",
+ (void *)ap);
+ }
+ }
+ (void) pthread_mutex_lock(&pp->uap_lock);
+ UU_AVL_PTR(ap->ua_next_enc)->ua_prev_enc = ap->ua_prev_enc;
+ UU_AVL_PTR(ap->ua_prev_enc)->ua_next_enc = ap->ua_next_enc;
+ (void) pthread_mutex_unlock(&pp->uap_lock);
+ ap->ua_prev_enc = UU_PTR_ENCODE(NULL);
+ ap->ua_next_enc = UU_PTR_ENCODE(NULL);
+
+ ap->ua_pool = NULL;
+ avl_destroy(&ap->ua_tree);
+
+ uu_free(ap);
+}
+
+size_t
+uu_avl_numnodes(uu_avl_t *ap)
+{
+ return (avl_numnodes(&ap->ua_tree));
+}
+
+void *
+uu_avl_first(uu_avl_t *ap)
+{
+ return (avl_first(&ap->ua_tree));
+}
+
+void *
+uu_avl_last(uu_avl_t *ap)
+{
+ return (avl_last(&ap->ua_tree));
+}
+
+void *
+uu_avl_next(uu_avl_t *ap, void *node)
+{
+ return (AVL_NEXT(&ap->ua_tree, node));
+}
+
+void *
+uu_avl_prev(uu_avl_t *ap, void *node)
+{
+ return (AVL_PREV(&ap->ua_tree, node));
+}
+
+static void
+_avl_walk_init(uu_avl_walk_t *wp, uu_avl_t *ap, uint32_t flags)
+{
+ uu_avl_walk_t *next, *prev;
+
+ int robust = (flags & UU_WALK_ROBUST);
+ int direction = (flags & UU_WALK_REVERSE)? -1 : 1;
+
+ (void) memset(wp, 0, sizeof (*wp));
+ wp->uaw_avl = ap;
+ wp->uaw_robust = robust;
+ wp->uaw_dir = direction;
+
+ if (direction > 0)
+ wp->uaw_next_result = avl_first(&ap->ua_tree);
+ else
+ wp->uaw_next_result = avl_last(&ap->ua_tree);
+
+ if (ap->ua_debug || robust) {
+ wp->uaw_next = next = &ap->ua_null_walk;
+ wp->uaw_prev = prev = next->uaw_prev;
+ next->uaw_prev = wp;
+ prev->uaw_next = wp;
+ }
+}
+
+static void *
+_avl_walk_advance(uu_avl_walk_t *wp, uu_avl_t *ap)
+{
+ void *np = wp->uaw_next_result;
+
+ avl_tree_t *t = &ap->ua_tree;
+
+ if (np == NULL)
+ return (NULL);
+
+ wp->uaw_next_result = (wp->uaw_dir > 0)? AVL_NEXT(t, np) :
+ AVL_PREV(t, np);
+
+ return (np);
+}
+
+static void
+_avl_walk_fini(uu_avl_walk_t *wp)
+{
+ if (wp->uaw_next != NULL) {
+ wp->uaw_next->uaw_prev = wp->uaw_prev;
+ wp->uaw_prev->uaw_next = wp->uaw_next;
+ wp->uaw_next = NULL;
+ wp->uaw_prev = NULL;
+ }
+ wp->uaw_avl = NULL;
+ wp->uaw_next_result = NULL;
+}
+
+uu_avl_walk_t *
+uu_avl_walk_start(uu_avl_t *ap, uint32_t flags)
+{
+ uu_avl_walk_t *wp;
+
+ if (flags & ~(UU_WALK_ROBUST | UU_WALK_REVERSE)) {
+ uu_set_error(UU_ERROR_UNKNOWN_FLAG);
+ return (NULL);
+ }
+
+ wp = uu_zalloc(sizeof (*wp));
+ if (wp == NULL) {
+ uu_set_error(UU_ERROR_NO_MEMORY);
+ return (NULL);
+ }
+
+ _avl_walk_init(wp, ap, flags);
+ return (wp);
+}
+
+void *
+uu_avl_walk_next(uu_avl_walk_t *wp)
+{
+ return (_avl_walk_advance(wp, wp->uaw_avl));
+}
+
+void
+uu_avl_walk_end(uu_avl_walk_t *wp)
+{
+ _avl_walk_fini(wp);
+ uu_free(wp);
+}
+
+int
+uu_avl_walk(uu_avl_t *ap, uu_walk_fn_t *func, void *private, uint32_t flags)
+{
+ void *e;
+ uu_avl_walk_t my_walk;
+
+ int status = UU_WALK_NEXT;
+
+ if (flags & ~(UU_WALK_ROBUST | UU_WALK_REVERSE)) {
+ uu_set_error(UU_ERROR_UNKNOWN_FLAG);
+ return (-1);
+ }
+
+ _avl_walk_init(&my_walk, ap, flags);
+ while (status == UU_WALK_NEXT &&
+ (e = _avl_walk_advance(&my_walk, ap)) != NULL)
+ status = (*func)(e, private);
+ _avl_walk_fini(&my_walk);
+
+ if (status >= 0)
+ return (0);
+ uu_set_error(UU_ERROR_CALLBACK_FAILED);
+ return (-1);
+}
+
+void
+uu_avl_remove(uu_avl_t *ap, void *elem)
+{
+ uu_avl_walk_t *wp;
+ uu_avl_pool_t *pp = ap->ua_pool;
+ uintptr_t *na = NODE_ARRAY(pp, elem);
+
+ if (ap->ua_debug) {
+ /*
+ * invalidate outstanding uu_avl_index_ts.
+ */
+ ap->ua_index = INDEX_NEXT(ap->ua_index);
+ }
+
+ /*
+ * Robust walkers most be advanced, if we are removing the node
+ * they are currently using. In debug mode, non-robust walkers
+ * are also on the walker list.
+ */
+ for (wp = ap->ua_null_walk.uaw_next; wp != &ap->ua_null_walk;
+ wp = wp->uaw_next) {
+ if (wp->uaw_robust) {
+ if (elem == wp->uaw_next_result)
+ (void) _avl_walk_advance(wp, ap);
+ } else if (wp->uaw_next_result != NULL) {
+ uu_panic("uu_avl_remove(%p, %p): active non-robust "
+ "walker\n", (void *)ap, elem);
+ }
+ }
+
+ avl_remove(&ap->ua_tree, elem);
+
+ na[0] = POOL_TO_MARKER(pp);
+ na[1] = 0;
+}
+
+void *
+uu_avl_teardown(uu_avl_t *ap, void **cookie)
+{
+ void *elem = avl_destroy_nodes(&ap->ua_tree, cookie);
+
+ if (elem != NULL) {
+ uu_avl_pool_t *pp = ap->ua_pool;
+ uintptr_t *na = NODE_ARRAY(pp, elem);
+
+ na[0] = POOL_TO_MARKER(pp);
+ na[1] = 0;
+ }
+ return (elem);
+}
+
+void *
+uu_avl_find(uu_avl_t *ap, void *elem, void *private, uu_avl_index_t *out)
+{
+ struct uu_avl_node_compare_info info;
+ void *result;
+
+ info.ac_compare = ap->ua_pool->uap_cmp;
+ info.ac_private = private;
+ info.ac_right = elem;
+ info.ac_found = NULL;
+
+ result = avl_find(&ap->ua_tree, &info, out);
+ if (out != NULL)
+ *out = INDEX_ENCODE(ap, *out);
+
+ if (ap->ua_debug && result != NULL)
+ uu_panic("uu_avl_find: internal error: avl_find succeeded\n");
+
+ return (info.ac_found);
+}
+
+void
+uu_avl_insert(uu_avl_t *ap, void *elem, uu_avl_index_t idx)
+{
+ if (ap->ua_debug) {
+ uu_avl_pool_t *pp = ap->ua_pool;
+ uintptr_t *na = NODE_ARRAY(pp, elem);
+
+ if (na[1] != 0)
+ uu_panic("uu_avl_insert(%p, %p, %p): node already "
+ "in tree, or corrupt\n",
+ (void *)ap, elem, (void *)idx);
+ if (na[0] == 0)
+ uu_panic("uu_avl_insert(%p, %p, %p): node not "
+ "initialized\n",
+ (void *)ap, elem, (void *)idx);
+ if (na[0] != POOL_TO_MARKER(pp))
+ uu_panic("uu_avl_insert(%p, %p, %p): node from "
+ "other pool, or corrupt\n",
+ (void *)ap, elem, (void *)idx);
+
+ if (!INDEX_VALID(ap, idx))
+ uu_panic("uu_avl_insert(%p, %p, %p): %s\n",
+ (void *)ap, elem, (void *)idx,
+ INDEX_CHECK(idx)? "outdated index" :
+ "invalid index");
+
+ /*
+ * invalidate outstanding uu_avl_index_ts.
+ */
+ ap->ua_index = INDEX_NEXT(ap->ua_index);
+ }
+ avl_insert(&ap->ua_tree, elem, INDEX_DECODE(idx));
+}
+
+void *
+uu_avl_nearest_next(uu_avl_t *ap, uu_avl_index_t idx)
+{
+ if (ap->ua_debug && !INDEX_VALID(ap, idx))
+ uu_panic("uu_avl_nearest_next(%p, %p): %s\n",
+ (void *)ap, (void *)idx, INDEX_CHECK(idx)?
+ "outdated index" : "invalid index");
+ return (avl_nearest(&ap->ua_tree, INDEX_DECODE(idx), AVL_AFTER));
+}
+
+void *
+uu_avl_nearest_prev(uu_avl_t *ap, uu_avl_index_t idx)
+{
+ if (ap->ua_debug && !INDEX_VALID(ap, idx))
+ uu_panic("uu_avl_nearest_prev(%p, %p): %s\n",
+ (void *)ap, (void *)idx, INDEX_CHECK(idx)?
+ "outdated index" : "invalid index");
+ return (avl_nearest(&ap->ua_tree, INDEX_DECODE(idx), AVL_BEFORE));
+}
+
+/*
+ * called from uu_lockup() and uu_release(), as part of our fork1()-safety.
+ */
+void
+uu_avl_lockup(void)
+{
+ uu_avl_pool_t *pp;
+
+ (void) pthread_mutex_lock(&uu_apool_list_lock);
+ for (pp = uu_null_apool.uap_next; pp != &uu_null_apool;
+ pp = pp->uap_next)
+ (void) pthread_mutex_lock(&pp->uap_lock);
+}
+
+void
+uu_avl_release(void)
+{
+ uu_avl_pool_t *pp;
+
+ for (pp = uu_null_apool.uap_next; pp != &uu_null_apool;
+ pp = pp->uap_next)
+ (void) pthread_mutex_unlock(&pp->uap_lock);
+ (void) pthread_mutex_unlock(&uu_apool_list_lock);
+}
diff --git a/lib/libuutil/uu_dprintf.c b/lib/libuutil/uu_dprintf.c
new file mode 100644
index 000000000000..6958057b29c4
--- /dev/null
+++ b/lib/libuutil/uu_dprintf.c
@@ -0,0 +1,130 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2004 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+
+
+#include "libuutil_common.h"
+
+#include <errno.h>
+#include <libintl.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+
+#define FACILITY_FMT "%s (%s): "
+
+#if !defined(TEXT_DOMAIN)
+#define TEXT_DOMAIN "SYS_TEST"
+#endif
+
+static const char *
+strseverity(uu_dprintf_severity_t severity)
+{
+ switch (severity) {
+ case UU_DPRINTF_SILENT:
+ return (dgettext(TEXT_DOMAIN, "silent"));
+ case UU_DPRINTF_FATAL:
+ return (dgettext(TEXT_DOMAIN, "FATAL"));
+ case UU_DPRINTF_WARNING:
+ return (dgettext(TEXT_DOMAIN, "WARNING"));
+ case UU_DPRINTF_NOTICE:
+ return (dgettext(TEXT_DOMAIN, "note"));
+ case UU_DPRINTF_INFO:
+ return (dgettext(TEXT_DOMAIN, "info"));
+ case UU_DPRINTF_DEBUG:
+ return (dgettext(TEXT_DOMAIN, "debug"));
+ default:
+ return (dgettext(TEXT_DOMAIN, "unspecified"));
+ }
+}
+
+uu_dprintf_t *
+uu_dprintf_create(const char *name, uu_dprintf_severity_t severity,
+ uint_t flags)
+{
+ uu_dprintf_t *D;
+
+ if (name != NULL &&
+ uu_check_name(name, UU_NAME_DOMAIN) == -1) {
+ uu_set_error(UU_ERROR_INVALID_ARGUMENT);
+ return (NULL);
+ }
+
+ if ((D = uu_zalloc(sizeof (uu_dprintf_t))) == NULL)
+ return (NULL);
+
+ if (name != NULL) {
+ D->uud_name = strdup(name);
+ if (D->uud_name == NULL) {
+ uu_free(D);
+ return (NULL);
+ }
+ } else {
+ D->uud_name = NULL;
+ }
+
+ D->uud_severity = severity;
+ D->uud_flags = flags;
+
+ return (D);
+}
+
+/*PRINTFLIKE3*/
+void
+uu_dprintf(uu_dprintf_t *D, uu_dprintf_severity_t severity,
+ const char *format, ...)
+{
+ va_list alist;
+
+ /* XXX Assert that severity is not UU_DPRINTF_SILENT. */
+
+ if (severity > D->uud_severity)
+ return;
+
+ (void) fprintf(stderr, FACILITY_FMT, D->uud_name,
+ strseverity(severity));
+
+ va_start(alist, format);
+ (void) vfprintf(stderr, format, alist);
+ va_end(alist);
+}
+
+void
+uu_dprintf_destroy(uu_dprintf_t *D)
+{
+ if (D->uud_name)
+ free(D->uud_name);
+
+ uu_free(D);
+}
+
+const char *
+uu_dprintf_getname(uu_dprintf_t *D)
+{
+ return (D->uud_name);
+}
diff --git a/lib/libuutil/uu_ident.c b/lib/libuutil/uu_ident.c
new file mode 100644
index 000000000000..382139316ebc
--- /dev/null
+++ b/lib/libuutil/uu_ident.c
@@ -0,0 +1,122 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2004 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+
+
+#include "libuutil_common.h"
+
+#include <string.h>
+
+/*
+ * We require names of the form:
+ * [provider,]identifier[/[provider,]identifier]...
+ *
+ * Where provider is either a stock symbol (SUNW) or a java-style reversed
+ * domain name (com.sun).
+ *
+ * Both providers and identifiers must start with a letter, and may
+ * only contain alphanumerics, dashes, and underlines. Providers
+ * may also contain periods.
+ *
+ * Note that we do _not_ use the macros in <ctype.h>, since they are affected
+ * by the current locale settings.
+ */
+
+#define IS_ALPHA(c) \
+ (((c) >= 'a' && (c) <= 'z') || ((c) >= 'A' && (c) <= 'Z'))
+
+#define IS_DIGIT(c) \
+ ((c) >= '0' && (c) <= '9')
+
+static int
+is_valid_ident(const char *s, const char *e, int allowdot)
+{
+ char c;
+
+ if (s >= e)
+ return (0); /* name is empty */
+
+ c = *s++;
+ if (!IS_ALPHA(c))
+ return (0); /* does not start with letter */
+
+ while (s < e && (c = *s++) != 0) {
+ if (IS_ALPHA(c) || IS_DIGIT(c) || c == '-' || c == '_' ||
+ (allowdot && c == '.'))
+ continue;
+ return (0); /* invalid character */
+ }
+ return (1);
+}
+
+static int
+is_valid_component(const char *b, const char *e, uint_t flags)
+{
+ char *sp;
+
+ if (flags & UU_NAME_DOMAIN) {
+ sp = strchr(b, ',');
+ if (sp != NULL && sp < e) {
+ if (!is_valid_ident(b, sp, 1))
+ return (0);
+ b = sp + 1;
+ }
+ }
+
+ return (is_valid_ident(b, e, 0));
+}
+
+int
+uu_check_name(const char *name, uint_t flags)
+{
+ const char *end = name + strlen(name);
+ const char *p;
+
+ if (flags & ~(UU_NAME_DOMAIN | UU_NAME_PATH)) {
+ uu_set_error(UU_ERROR_UNKNOWN_FLAG);
+ return (-1);
+ }
+
+ if (!(flags & UU_NAME_PATH)) {
+ if (!is_valid_component(name, end, flags))
+ goto bad;
+ return (0);
+ }
+
+ while ((p = strchr(name, '/')) != NULL) {
+ if (!is_valid_component(name, p - 1, flags))
+ goto bad;
+ name = p + 1;
+ }
+ if (!is_valid_component(name, end, flags))
+ goto bad;
+
+ return (0);
+
+bad:
+ uu_set_error(UU_ERROR_INVALID_ARGUMENT);
+ return (-1);
+}
diff --git a/lib/libuutil/uu_list.c b/lib/libuutil/uu_list.c
new file mode 100644
index 000000000000..c3a447d01de3
--- /dev/null
+++ b/lib/libuutil/uu_list.c
@@ -0,0 +1,718 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+
+
+#include "libuutil_common.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/time.h>
+
+#define ELEM_TO_NODE(lp, e) \
+ ((uu_list_node_impl_t *)((uintptr_t)(e) + (lp)->ul_offset))
+
+#define NODE_TO_ELEM(lp, n) \
+ ((void *)((uintptr_t)(n) - (lp)->ul_offset))
+
+/*
+ * uu_list_index_ts define a location for insertion. They are simply a
+ * pointer to the object after the insertion point. We store a mark
+ * in the low-bits of the index, to help prevent mistakes.
+ *
+ * When debugging, the index mark changes on every insert and delete, to
+ * catch stale references.
+ */
+#define INDEX_MAX (sizeof (uintptr_t) - 1)
+#define INDEX_NEXT(m) (((m) == INDEX_MAX)? 1 : ((m) + 1) & INDEX_MAX)
+
+#define INDEX_TO_NODE(i) ((uu_list_node_impl_t *)((i) & ~INDEX_MAX))
+#define NODE_TO_INDEX(p, n) (((uintptr_t)(n) & ~INDEX_MAX) | (p)->ul_index)
+#define INDEX_VALID(p, i) (((i) & INDEX_MAX) == (p)->ul_index)
+#define INDEX_CHECK(i) (((i) & INDEX_MAX) != 0)
+
+#define POOL_TO_MARKER(pp) ((void *)((uintptr_t)(pp) | 1))
+
+static uu_list_pool_t uu_null_lpool = { &uu_null_lpool, &uu_null_lpool };
+static pthread_mutex_t uu_lpool_list_lock = PTHREAD_MUTEX_INITIALIZER;
+
+uu_list_pool_t *
+uu_list_pool_create(const char *name, size_t objsize,
+ size_t nodeoffset, uu_compare_fn_t *compare_func, uint32_t flags)
+{
+ uu_list_pool_t *pp, *next, *prev;
+
+ if (name == NULL ||
+ uu_check_name(name, UU_NAME_DOMAIN) == -1 ||
+ nodeoffset + sizeof (uu_list_node_t) > objsize) {
+ uu_set_error(UU_ERROR_INVALID_ARGUMENT);
+ return (NULL);
+ }
+
+ if (flags & ~UU_LIST_POOL_DEBUG) {
+ uu_set_error(UU_ERROR_UNKNOWN_FLAG);
+ return (NULL);
+ }
+
+ pp = uu_zalloc(sizeof (uu_list_pool_t));
+ if (pp == NULL) {
+ uu_set_error(UU_ERROR_NO_MEMORY);
+ return (NULL);
+ }
+
+ (void) strlcpy(pp->ulp_name, name, sizeof (pp->ulp_name));
+ pp->ulp_nodeoffset = nodeoffset;
+ pp->ulp_objsize = objsize;
+ pp->ulp_cmp = compare_func;
+ if (flags & UU_LIST_POOL_DEBUG)
+ pp->ulp_debug = 1;
+ pp->ulp_last_index = 0;
+
+ (void) pthread_mutex_init(&pp->ulp_lock, NULL);
+
+ pp->ulp_null_list.ul_next_enc = UU_PTR_ENCODE(&pp->ulp_null_list);
+ pp->ulp_null_list.ul_prev_enc = UU_PTR_ENCODE(&pp->ulp_null_list);
+
+ (void) pthread_mutex_lock(&uu_lpool_list_lock);
+ pp->ulp_next = next = &uu_null_lpool;
+ pp->ulp_prev = prev = next->ulp_prev;
+ next->ulp_prev = pp;
+ prev->ulp_next = pp;
+ (void) pthread_mutex_unlock(&uu_lpool_list_lock);
+
+ return (pp);
+}
+
+void
+uu_list_pool_destroy(uu_list_pool_t *pp)
+{
+ if (pp->ulp_debug) {
+ if (pp->ulp_null_list.ul_next_enc !=
+ UU_PTR_ENCODE(&pp->ulp_null_list) ||
+ pp->ulp_null_list.ul_prev_enc !=
+ UU_PTR_ENCODE(&pp->ulp_null_list)) {
+ uu_panic("uu_list_pool_destroy: Pool \"%.*s\" (%p) has "
+ "outstanding lists, or is corrupt.\n",
+ (int)sizeof (pp->ulp_name), pp->ulp_name,
+ (void *)pp);
+ }
+ }
+ (void) pthread_mutex_lock(&uu_lpool_list_lock);
+ pp->ulp_next->ulp_prev = pp->ulp_prev;
+ pp->ulp_prev->ulp_next = pp->ulp_next;
+ (void) pthread_mutex_unlock(&uu_lpool_list_lock);
+ pp->ulp_prev = NULL;
+ pp->ulp_next = NULL;
+ uu_free(pp);
+}
+
+void
+uu_list_node_init(void *base, uu_list_node_t *np_arg, uu_list_pool_t *pp)
+{
+ uu_list_node_impl_t *np = (uu_list_node_impl_t *)np_arg;
+
+ if (pp->ulp_debug) {
+ uintptr_t offset = (uintptr_t)np - (uintptr_t)base;
+ if (offset + sizeof (*np) > pp->ulp_objsize) {
+ uu_panic("uu_list_node_init(%p, %p, %p (\"%s\")): "
+ "offset %ld doesn't fit in object (size %ld)\n",
+ base, (void *)np, (void *)pp, pp->ulp_name,
+ (long)offset, (long)pp->ulp_objsize);
+ }
+ if (offset != pp->ulp_nodeoffset) {
+ uu_panic("uu_list_node_init(%p, %p, %p (\"%s\")): "
+ "offset %ld doesn't match pool's offset (%ld)\n",
+ base, (void *)np, (void *)pp, pp->ulp_name,
+ (long)offset, (long)pp->ulp_objsize);
+ }
+ }
+ np->uln_next = POOL_TO_MARKER(pp);
+ np->uln_prev = NULL;
+}
+
+void
+uu_list_node_fini(void *base, uu_list_node_t *np_arg, uu_list_pool_t *pp)
+{
+ uu_list_node_impl_t *np = (uu_list_node_impl_t *)np_arg;
+
+ if (pp->ulp_debug) {
+ if (np->uln_next == NULL &&
+ np->uln_prev == NULL) {
+ uu_panic("uu_list_node_fini(%p, %p, %p (\"%s\")): "
+ "node already finied\n",
+ base, (void *)np_arg, (void *)pp, pp->ulp_name);
+ }
+ if (np->uln_next != POOL_TO_MARKER(pp) ||
+ np->uln_prev != NULL) {
+ uu_panic("uu_list_node_fini(%p, %p, %p (\"%s\")): "
+ "node corrupt or on list\n",
+ base, (void *)np_arg, (void *)pp, pp->ulp_name);
+ }
+ }
+ np->uln_next = NULL;
+ np->uln_prev = NULL;
+}
+
+uu_list_t *
+uu_list_create(uu_list_pool_t *pp, void *parent, uint32_t flags)
+{
+ uu_list_t *lp, *next, *prev;
+
+ if (flags & ~(UU_LIST_DEBUG | UU_LIST_SORTED)) {
+ uu_set_error(UU_ERROR_UNKNOWN_FLAG);
+ return (NULL);
+ }
+
+ if ((flags & UU_LIST_SORTED) && pp->ulp_cmp == NULL) {
+ if (pp->ulp_debug)
+ uu_panic("uu_list_create(%p, ...): requested "
+ "UU_LIST_SORTED, but pool has no comparison func\n",
+ (void *)pp);
+ uu_set_error(UU_ERROR_NOT_SUPPORTED);
+ return (NULL);
+ }
+
+ lp = uu_zalloc(sizeof (*lp));
+ if (lp == NULL) {
+ uu_set_error(UU_ERROR_NO_MEMORY);
+ return (NULL);
+ }
+
+ lp->ul_pool = pp;
+ lp->ul_parent_enc = UU_PTR_ENCODE(parent);
+ lp->ul_offset = pp->ulp_nodeoffset;
+ lp->ul_debug = pp->ulp_debug || (flags & UU_LIST_DEBUG);
+ lp->ul_sorted = (flags & UU_LIST_SORTED);
+ lp->ul_numnodes = 0;
+ lp->ul_index = (pp->ulp_last_index = INDEX_NEXT(pp->ulp_last_index));
+
+ lp->ul_null_node.uln_next = &lp->ul_null_node;
+ lp->ul_null_node.uln_prev = &lp->ul_null_node;
+
+ lp->ul_null_walk.ulw_next = &lp->ul_null_walk;
+ lp->ul_null_walk.ulw_prev = &lp->ul_null_walk;
+
+ (void) pthread_mutex_lock(&pp->ulp_lock);
+ next = &pp->ulp_null_list;
+ prev = UU_PTR_DECODE(next->ul_prev_enc);
+ lp->ul_next_enc = UU_PTR_ENCODE(next);
+ lp->ul_prev_enc = UU_PTR_ENCODE(prev);
+ next->ul_prev_enc = UU_PTR_ENCODE(lp);
+ prev->ul_next_enc = UU_PTR_ENCODE(lp);
+ (void) pthread_mutex_unlock(&pp->ulp_lock);
+
+ return (lp);
+}
+
+void
+uu_list_destroy(uu_list_t *lp)
+{
+ uu_list_pool_t *pp = lp->ul_pool;
+
+ if (lp->ul_debug) {
+ if (lp->ul_null_node.uln_next != &lp->ul_null_node ||
+ lp->ul_null_node.uln_prev != &lp->ul_null_node) {
+ uu_panic("uu_list_destroy(%p): list not empty\n",
+ (void *)lp);
+ }
+ if (lp->ul_numnodes != 0) {
+ uu_panic("uu_list_destroy(%p): numnodes is nonzero, "
+ "but list is empty\n", (void *)lp);
+ }
+ if (lp->ul_null_walk.ulw_next != &lp->ul_null_walk ||
+ lp->ul_null_walk.ulw_prev != &lp->ul_null_walk) {
+ uu_panic("uu_list_destroy(%p): outstanding walkers\n",
+ (void *)lp);
+ }
+ }
+
+ (void) pthread_mutex_lock(&pp->ulp_lock);
+ UU_LIST_PTR(lp->ul_next_enc)->ul_prev_enc = lp->ul_prev_enc;
+ UU_LIST_PTR(lp->ul_prev_enc)->ul_next_enc = lp->ul_next_enc;
+ (void) pthread_mutex_unlock(&pp->ulp_lock);
+ lp->ul_prev_enc = UU_PTR_ENCODE(NULL);
+ lp->ul_next_enc = UU_PTR_ENCODE(NULL);
+ lp->ul_pool = NULL;
+ uu_free(lp);
+}
+
+static void
+list_insert(uu_list_t *lp, uu_list_node_impl_t *np, uu_list_node_impl_t *prev,
+ uu_list_node_impl_t *next)
+{
+ if (lp->ul_debug) {
+ if (next->uln_prev != prev || prev->uln_next != next)
+ uu_panic("insert(%p): internal error: %p and %p not "
+ "neighbors\n", (void *)lp, (void *)next,
+ (void *)prev);
+
+ if (np->uln_next != POOL_TO_MARKER(lp->ul_pool) ||
+ np->uln_prev != NULL) {
+ uu_panic("insert(%p): elem %p node %p corrupt, "
+ "not initialized, or already in a list.\n",
+ (void *)lp, NODE_TO_ELEM(lp, np), (void *)np);
+ }
+ /*
+ * invalidate outstanding uu_list_index_ts.
+ */
+ lp->ul_index = INDEX_NEXT(lp->ul_index);
+ }
+ np->uln_next = next;
+ np->uln_prev = prev;
+ next->uln_prev = np;
+ prev->uln_next = np;
+
+ lp->ul_numnodes++;
+}
+
+void
+uu_list_insert(uu_list_t *lp, void *elem, uu_list_index_t idx)
+{
+ uu_list_node_impl_t *np;
+
+ np = INDEX_TO_NODE(idx);
+ if (np == NULL)
+ np = &lp->ul_null_node;
+
+ if (lp->ul_debug) {
+ if (!INDEX_VALID(lp, idx))
+ uu_panic("uu_list_insert(%p, %p, %p): %s\n",
+ (void *)lp, elem, (void *)idx,
+ INDEX_CHECK(idx)? "outdated index" :
+ "invalid index");
+ if (np->uln_prev == NULL)
+ uu_panic("uu_list_insert(%p, %p, %p): out-of-date "
+ "index\n", (void *)lp, elem, (void *)idx);
+ }
+
+ list_insert(lp, ELEM_TO_NODE(lp, elem), np->uln_prev, np);
+}
+
+void *
+uu_list_find(uu_list_t *lp, void *elem, void *private, uu_list_index_t *out)
+{
+ int sorted = lp->ul_sorted;
+ uu_compare_fn_t *func = lp->ul_pool->ulp_cmp;
+ uu_list_node_impl_t *np;
+
+ if (func == NULL) {
+ if (out != NULL)
+ *out = 0;
+ uu_set_error(UU_ERROR_NOT_SUPPORTED);
+ return (NULL);
+ }
+ for (np = lp->ul_null_node.uln_next; np != &lp->ul_null_node;
+ np = np->uln_next) {
+ void *ep = NODE_TO_ELEM(lp, np);
+ int cmp = func(ep, elem, private);
+ if (cmp == 0) {
+ if (out != NULL)
+ *out = NODE_TO_INDEX(lp, np);
+ return (ep);
+ }
+ if (sorted && cmp > 0) {
+ if (out != NULL)
+ *out = NODE_TO_INDEX(lp, np);
+ return (NULL);
+ }
+ }
+ if (out != NULL)
+ *out = NODE_TO_INDEX(lp, 0);
+ return (NULL);
+}
+
+void *
+uu_list_nearest_next(uu_list_t *lp, uu_list_index_t idx)
+{
+ uu_list_node_impl_t *np = INDEX_TO_NODE(idx);
+
+ if (np == NULL)
+ np = &lp->ul_null_node;
+
+ if (lp->ul_debug) {
+ if (!INDEX_VALID(lp, idx))
+ uu_panic("uu_list_nearest_next(%p, %p): %s\n",
+ (void *)lp, (void *)idx,
+ INDEX_CHECK(idx)? "outdated index" :
+ "invalid index");
+ if (np->uln_prev == NULL)
+ uu_panic("uu_list_nearest_next(%p, %p): out-of-date "
+ "index\n", (void *)lp, (void *)idx);
+ }
+
+ if (np == &lp->ul_null_node)
+ return (NULL);
+ else
+ return (NODE_TO_ELEM(lp, np));
+}
+
+void *
+uu_list_nearest_prev(uu_list_t *lp, uu_list_index_t idx)
+{
+ uu_list_node_impl_t *np = INDEX_TO_NODE(idx);
+
+ if (np == NULL)
+ np = &lp->ul_null_node;
+
+ if (lp->ul_debug) {
+ if (!INDEX_VALID(lp, idx))
+ uu_panic("uu_list_nearest_prev(%p, %p): %s\n",
+ (void *)lp, (void *)idx, INDEX_CHECK(idx)?
+ "outdated index" : "invalid index");
+ if (np->uln_prev == NULL)
+ uu_panic("uu_list_nearest_prev(%p, %p): out-of-date "
+ "index\n", (void *)lp, (void *)idx);
+ }
+
+ if ((np = np->uln_prev) == &lp->ul_null_node)
+ return (NULL);
+ else
+ return (NODE_TO_ELEM(lp, np));
+}
+
+static void
+list_walk_init(uu_list_walk_t *wp, uu_list_t *lp, uint32_t flags)
+{
+ uu_list_walk_t *next, *prev;
+
+ int robust = (flags & UU_WALK_ROBUST);
+ int direction = (flags & UU_WALK_REVERSE)? -1 : 1;
+
+ (void) memset(wp, 0, sizeof (*wp));
+ wp->ulw_list = lp;
+ wp->ulw_robust = robust;
+ wp->ulw_dir = direction;
+ if (direction > 0)
+ wp->ulw_next_result = lp->ul_null_node.uln_next;
+ else
+ wp->ulw_next_result = lp->ul_null_node.uln_prev;
+
+ if (lp->ul_debug || robust) {
+ /*
+ * Add this walker to the list's list of walkers so
+ * uu_list_remove() can advance us if somebody tries to
+ * remove ulw_next_result.
+ */
+ wp->ulw_next = next = &lp->ul_null_walk;
+ wp->ulw_prev = prev = next->ulw_prev;
+ next->ulw_prev = wp;
+ prev->ulw_next = wp;
+ }
+}
+
+static uu_list_node_impl_t *
+list_walk_advance(uu_list_walk_t *wp, uu_list_t *lp)
+{
+ uu_list_node_impl_t *np = wp->ulw_next_result;
+ uu_list_node_impl_t *next;
+
+ if (np == &lp->ul_null_node)
+ return (NULL);
+
+ next = (wp->ulw_dir > 0)? np->uln_next : np->uln_prev;
+
+ wp->ulw_next_result = next;
+ return (np);
+}
+
+static void
+list_walk_fini(uu_list_walk_t *wp)
+{
+ /* GLXXX debugging? */
+ if (wp->ulw_next != NULL) {
+ wp->ulw_next->ulw_prev = wp->ulw_prev;
+ wp->ulw_prev->ulw_next = wp->ulw_next;
+ wp->ulw_next = NULL;
+ wp->ulw_prev = NULL;
+ }
+ wp->ulw_list = NULL;
+ wp->ulw_next_result = NULL;
+}
+
+uu_list_walk_t *
+uu_list_walk_start(uu_list_t *lp, uint32_t flags)
+{
+ uu_list_walk_t *wp;
+
+ if (flags & ~(UU_WALK_ROBUST | UU_WALK_REVERSE)) {
+ uu_set_error(UU_ERROR_UNKNOWN_FLAG);
+ return (NULL);
+ }
+
+ wp = uu_zalloc(sizeof (*wp));
+ if (wp == NULL) {
+ uu_set_error(UU_ERROR_NO_MEMORY);
+ return (NULL);
+ }
+
+ list_walk_init(wp, lp, flags);
+ return (wp);
+}
+
+void *
+uu_list_walk_next(uu_list_walk_t *wp)
+{
+ uu_list_t *lp = wp->ulw_list;
+ uu_list_node_impl_t *np = list_walk_advance(wp, lp);
+
+ if (np == NULL)
+ return (NULL);
+
+ return (NODE_TO_ELEM(lp, np));
+}
+
+void
+uu_list_walk_end(uu_list_walk_t *wp)
+{
+ list_walk_fini(wp);
+ uu_free(wp);
+}
+
+int
+uu_list_walk(uu_list_t *lp, uu_walk_fn_t *func, void *private, uint32_t flags)
+{
+ uu_list_node_impl_t *np;
+
+ int status = UU_WALK_NEXT;
+
+ int robust = (flags & UU_WALK_ROBUST);
+ int reverse = (flags & UU_WALK_REVERSE);
+
+ if (flags & ~(UU_WALK_ROBUST | UU_WALK_REVERSE)) {
+ uu_set_error(UU_ERROR_UNKNOWN_FLAG);
+ return (-1);
+ }
+
+ if (lp->ul_debug || robust) {
+ uu_list_walk_t my_walk;
+ void *e;
+
+ list_walk_init(&my_walk, lp, flags);
+ while (status == UU_WALK_NEXT &&
+ (e = uu_list_walk_next(&my_walk)) != NULL)
+ status = (*func)(e, private);
+ list_walk_fini(&my_walk);
+ } else {
+ if (!reverse) {
+ for (np = lp->ul_null_node.uln_next;
+ status == UU_WALK_NEXT && np != &lp->ul_null_node;
+ np = np->uln_next) {
+ status = (*func)(NODE_TO_ELEM(lp, np), private);
+ }
+ } else {
+ for (np = lp->ul_null_node.uln_prev;
+ status == UU_WALK_NEXT && np != &lp->ul_null_node;
+ np = np->uln_prev) {
+ status = (*func)(NODE_TO_ELEM(lp, np), private);
+ }
+ }
+ }
+ if (status >= 0)
+ return (0);
+ uu_set_error(UU_ERROR_CALLBACK_FAILED);
+ return (-1);
+}
+
+void
+uu_list_remove(uu_list_t *lp, void *elem)
+{
+ uu_list_node_impl_t *np = ELEM_TO_NODE(lp, elem);
+ uu_list_walk_t *wp;
+
+ if (lp->ul_debug) {
+ if (np->uln_prev == NULL)
+ uu_panic("uu_list_remove(%p, %p): elem not on list\n",
+ (void *)lp, elem);
+ /*
+ * invalidate outstanding uu_list_index_ts.
+ */
+ lp->ul_index = INDEX_NEXT(lp->ul_index);
+ }
+
+ /*
+ * robust walkers must be advanced. In debug mode, non-robust
+ * walkers are also on the list. If there are any, it's an error.
+ */
+ for (wp = lp->ul_null_walk.ulw_next; wp != &lp->ul_null_walk;
+ wp = wp->ulw_next) {
+ if (wp->ulw_robust) {
+ if (np == wp->ulw_next_result)
+ (void) list_walk_advance(wp, lp);
+ } else if (wp->ulw_next_result != NULL) {
+ uu_panic("uu_list_remove(%p, %p): active non-robust "
+ "walker\n", (void *)lp, elem);
+ }
+ }
+
+ np->uln_next->uln_prev = np->uln_prev;
+ np->uln_prev->uln_next = np->uln_next;
+
+ lp->ul_numnodes--;
+
+ np->uln_next = POOL_TO_MARKER(lp->ul_pool);
+ np->uln_prev = NULL;
+}
+
+void *
+uu_list_teardown(uu_list_t *lp, void **cookie)
+{
+ void *ep;
+
+ /*
+ * XXX: disable list modification until list is empty
+ */
+ if (lp->ul_debug && *cookie != NULL)
+ uu_panic("uu_list_teardown(%p, %p): unexpected cookie\n",
+ (void *)lp, (void *)cookie);
+
+ ep = uu_list_first(lp);
+ if (ep)
+ uu_list_remove(lp, ep);
+ return (ep);
+}
+
+int
+uu_list_insert_before(uu_list_t *lp, void *target, void *elem)
+{
+ uu_list_node_impl_t *np = ELEM_TO_NODE(lp, target);
+
+ if (target == NULL)
+ np = &lp->ul_null_node;
+
+ if (lp->ul_debug) {
+ if (np->uln_prev == NULL)
+ uu_panic("uu_list_insert_before(%p, %p, %p): %p is "
+ "not currently on a list\n",
+ (void *)lp, target, elem, target);
+ }
+ if (lp->ul_sorted) {
+ if (lp->ul_debug)
+ uu_panic("uu_list_insert_before(%p, ...): list is "
+ "UU_LIST_SORTED\n", (void *)lp);
+ uu_set_error(UU_ERROR_NOT_SUPPORTED);
+ return (-1);
+ }
+
+ list_insert(lp, ELEM_TO_NODE(lp, elem), np->uln_prev, np);
+ return (0);
+}
+
+int
+uu_list_insert_after(uu_list_t *lp, void *target, void *elem)
+{
+ uu_list_node_impl_t *np = ELEM_TO_NODE(lp, target);
+
+ if (target == NULL)
+ np = &lp->ul_null_node;
+
+ if (lp->ul_debug) {
+ if (np->uln_prev == NULL)
+ uu_panic("uu_list_insert_after(%p, %p, %p): %p is "
+ "not currently on a list\n",
+ (void *)lp, target, elem, target);
+ }
+ if (lp->ul_sorted) {
+ if (lp->ul_debug)
+ uu_panic("uu_list_insert_after(%p, ...): list is "
+ "UU_LIST_SORTED\n", (void *)lp);
+ uu_set_error(UU_ERROR_NOT_SUPPORTED);
+ return (-1);
+ }
+
+ list_insert(lp, ELEM_TO_NODE(lp, elem), np, np->uln_next);
+ return (0);
+}
+
+size_t
+uu_list_numnodes(uu_list_t *lp)
+{
+ return (lp->ul_numnodes);
+}
+
+void *
+uu_list_first(uu_list_t *lp)
+{
+ uu_list_node_impl_t *n = lp->ul_null_node.uln_next;
+ if (n == &lp->ul_null_node)
+ return (NULL);
+ return (NODE_TO_ELEM(lp, n));
+}
+
+void *
+uu_list_last(uu_list_t *lp)
+{
+ uu_list_node_impl_t *n = lp->ul_null_node.uln_prev;
+ if (n == &lp->ul_null_node)
+ return (NULL);
+ return (NODE_TO_ELEM(lp, n));
+}
+
+void *
+uu_list_next(uu_list_t *lp, void *elem)
+{
+ uu_list_node_impl_t *n = ELEM_TO_NODE(lp, elem);
+
+ n = n->uln_next;
+ if (n == &lp->ul_null_node)
+ return (NULL);
+ return (NODE_TO_ELEM(lp, n));
+}
+
+void *
+uu_list_prev(uu_list_t *lp, void *elem)
+{
+ uu_list_node_impl_t *n = ELEM_TO_NODE(lp, elem);
+
+ n = n->uln_prev;
+ if (n == &lp->ul_null_node)
+ return (NULL);
+ return (NODE_TO_ELEM(lp, n));
+}
+
+/*
+ * called from uu_lockup() and uu_release(), as part of our fork1()-safety.
+ */
+void
+uu_list_lockup(void)
+{
+ uu_list_pool_t *pp;
+
+ (void) pthread_mutex_lock(&uu_lpool_list_lock);
+ for (pp = uu_null_lpool.ulp_next; pp != &uu_null_lpool;
+ pp = pp->ulp_next)
+ (void) pthread_mutex_lock(&pp->ulp_lock);
+}
+
+void
+uu_list_release(void)
+{
+ uu_list_pool_t *pp;
+
+ for (pp = uu_null_lpool.ulp_next; pp != &uu_null_lpool;
+ pp = pp->ulp_next)
+ (void) pthread_mutex_unlock(&pp->ulp_lock);
+ (void) pthread_mutex_unlock(&uu_lpool_list_lock);
+}
diff --git a/lib/libuutil/uu_misc.c b/lib/libuutil/uu_misc.c
new file mode 100644
index 000000000000..b10afd8eadf1
--- /dev/null
+++ b/lib/libuutil/uu_misc.c
@@ -0,0 +1,281 @@
+/*
+ * 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) 2004, 2010, Oracle and/or its affiliates. All rights reserved.
+ */
+
+#include "libuutil_common.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <libintl.h>
+#include <pthread.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/debug.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#if !defined(TEXT_DOMAIN)
+#define TEXT_DOMAIN "SYS_TEST"
+#endif
+
+/*
+ * All of the old code under !defined(PTHREAD_ONCE_KEY_NP)
+ * is here to enable the building of a native version of
+ * libuutil.so when the build machine has not yet been upgraded
+ * to a version of libc that provides pthread_key_create_once_np().
+ * It should all be deleted when solaris_nevada ships.
+ * The code is not MT-safe in a relaxed memory model.
+ */
+
+#if defined(PTHREAD_ONCE_KEY_NP)
+static pthread_key_t uu_error_key = PTHREAD_ONCE_KEY_NP;
+#else /* PTHREAD_ONCE_KEY_NP */
+static pthread_key_t uu_error_key = 0;
+static pthread_mutex_t uu_key_lock = PTHREAD_MUTEX_INITIALIZER;
+#endif /* PTHREAD_ONCE_KEY_NP */
+
+static int uu_error_key_setup = 0;
+
+static pthread_mutex_t uu_panic_lock = PTHREAD_MUTEX_INITIALIZER;
+/* LINTED static unused */
+static const char *uu_panic_format;
+/* LINTED static unused */
+static va_list uu_panic_args;
+static pthread_t uu_panic_thread;
+
+static uint32_t _uu_main_error;
+static __thread int _uu_main_thread = 0;
+
+void
+uu_set_error(uint_t code)
+{
+ if (_uu_main_thread) {
+ _uu_main_error = code;
+ return;
+ }
+#if defined(PTHREAD_ONCE_KEY_NP)
+ if (pthread_key_create_once_np(&uu_error_key, NULL) != 0)
+ uu_error_key_setup = -1;
+ else
+ uu_error_key_setup = 1;
+#else /* PTHREAD_ONCE_KEY_NP */
+ if (uu_error_key_setup == 0) {
+ (void) pthread_mutex_lock(&uu_key_lock);
+ if (uu_error_key_setup == 0) {
+ if (pthread_key_create(&uu_error_key, NULL) != 0)
+ uu_error_key_setup = -1;
+ else
+ uu_error_key_setup = 1;
+ }
+ (void) pthread_mutex_unlock(&uu_key_lock);
+ }
+#endif /* PTHREAD_ONCE_KEY_NP */
+ if (uu_error_key_setup > 0)
+ (void) pthread_setspecific(uu_error_key,
+ (void *)(uintptr_t)code);
+}
+
+uint32_t
+uu_error(void)
+{
+ if (_uu_main_thread)
+ return (_uu_main_error);
+
+ if (uu_error_key_setup < 0) /* can't happen? */
+ return (UU_ERROR_UNKNOWN);
+
+ /*
+ * Because UU_ERROR_NONE == 0, if uu_set_error() was
+ * never called, then this will return UU_ERROR_NONE:
+ */
+ return ((uint32_t)(uintptr_t)pthread_getspecific(uu_error_key));
+}
+
+const char *
+uu_strerror(uint32_t code)
+{
+ const char *str;
+
+ switch (code) {
+ case UU_ERROR_NONE:
+ str = dgettext(TEXT_DOMAIN, "No error");
+ break;
+
+ case UU_ERROR_INVALID_ARGUMENT:
+ str = dgettext(TEXT_DOMAIN, "Invalid argument");
+ break;
+
+ case UU_ERROR_UNKNOWN_FLAG:
+ str = dgettext(TEXT_DOMAIN, "Unknown flag passed");
+ break;
+
+ case UU_ERROR_NO_MEMORY:
+ str = dgettext(TEXT_DOMAIN, "Out of memory");
+ break;
+
+ case UU_ERROR_CALLBACK_FAILED:
+ str = dgettext(TEXT_DOMAIN, "Callback-initiated failure");
+ break;
+
+ case UU_ERROR_NOT_SUPPORTED:
+ str = dgettext(TEXT_DOMAIN, "Operation not supported");
+ break;
+
+ case UU_ERROR_EMPTY:
+ str = dgettext(TEXT_DOMAIN, "No value provided");
+ break;
+
+ case UU_ERROR_UNDERFLOW:
+ str = dgettext(TEXT_DOMAIN, "Value too small");
+ break;
+
+ case UU_ERROR_OVERFLOW:
+ str = dgettext(TEXT_DOMAIN, "Value too large");
+ break;
+
+ case UU_ERROR_INVALID_CHAR:
+ str = dgettext(TEXT_DOMAIN,
+ "Value contains unexpected character");
+ break;
+
+ case UU_ERROR_INVALID_DIGIT:
+ str = dgettext(TEXT_DOMAIN,
+ "Value contains digit not in base");
+ break;
+
+ case UU_ERROR_SYSTEM:
+ str = dgettext(TEXT_DOMAIN, "Underlying system error");
+ break;
+
+ case UU_ERROR_UNKNOWN:
+ str = dgettext(TEXT_DOMAIN, "Error status not known");
+ break;
+
+ default:
+ errno = ESRCH;
+ str = NULL;
+ break;
+ }
+ return (str);
+}
+
+void
+uu_panic(const char *format, ...)
+{
+ va_list args;
+
+ va_start(args, format);
+
+ (void) pthread_mutex_lock(&uu_panic_lock);
+ if (uu_panic_thread == 0) {
+ uu_panic_thread = pthread_self();
+ uu_panic_format = format;
+ va_copy(uu_panic_args, args);
+ }
+ (void) pthread_mutex_unlock(&uu_panic_lock);
+
+ (void) vfprintf(stderr, format, args);
+
+ va_end(args);
+
+ if (uu_panic_thread == pthread_self())
+ abort();
+ else
+ for (;;)
+ (void) pause();
+}
+
+static void
+uu_lockup(void)
+{
+ (void) pthread_mutex_lock(&uu_panic_lock);
+#if !defined(PTHREAD_ONCE_KEY_NP)
+ (void) pthread_mutex_lock(&uu_key_lock);
+#endif
+ uu_avl_lockup();
+ uu_list_lockup();
+}
+
+static void
+uu_release(void)
+{
+ (void) pthread_mutex_unlock(&uu_panic_lock);
+#if !defined(PTHREAD_ONCE_KEY_NP)
+ (void) pthread_mutex_unlock(&uu_key_lock);
+#endif
+ uu_avl_release();
+ uu_list_release();
+}
+
+static void
+uu_release_child(void)
+{
+ uu_panic_format = NULL;
+ uu_panic_thread = 0;
+
+ uu_release();
+}
+
+#ifdef __GNUC__
+static void
+uu_init(void) __attribute__((constructor));
+#else
+#pragma init(uu_init)
+#endif
+
+static void
+uu_init(void)
+{
+ _uu_main_thread = 1;
+ (void) pthread_atfork(uu_lockup, uu_release, uu_release_child);
+}
+
+/*
+ * Dump a block of memory in hex+ascii, for debugging
+ */
+void
+uu_dump(FILE *out, const char *prefix, const void *buf, size_t len)
+{
+ const unsigned char *p = buf;
+ int i;
+
+ for (i = 0; i < len; i += 16) {
+ int j;
+
+ (void) fprintf(out, "%s", prefix);
+ for (j = 0; j < 16 && i + j < len; j++) {
+ (void) fprintf(out, "%2.2x ", p[i + j]);
+ }
+ for (; j < 16; j++) {
+ (void) fprintf(out, " ");
+ }
+ for (j = 0; j < 16 && i + j < len; j++) {
+ (void) fprintf(out, "%c",
+ isprint(p[i + j]) ? p[i + j] : '.');
+ }
+ (void) fprintf(out, "\n");
+ }
+}
diff --git a/lib/libuutil/uu_open.c b/lib/libuutil/uu_open.c
new file mode 100644
index 000000000000..cf5c5450b820
--- /dev/null
+++ b/lib/libuutil/uu_open.c
@@ -0,0 +1,70 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2004 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+
+
+#include "libuutil_common.h"
+
+#include <sys/time.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#ifdef _LP64
+#define TMPPATHFMT "%s/uu%ld"
+#else /* _LP64 */
+#define TMPPATHFMT "%s/uu%lld"
+#endif /* _LP64 */
+
+/*ARGSUSED*/
+int
+uu_open_tmp(const char *dir, uint_t uflags)
+{
+ int f;
+ char *fname = uu_zalloc(PATH_MAX);
+
+ if (fname == NULL)
+ return (-1);
+
+ for (;;) {
+ (void) snprintf(fname, PATH_MAX, "%s/uu%lld", dir, gethrtime());
+
+ f = open(fname, O_CREAT | O_EXCL | O_RDWR, 0600);
+
+ if (f >= 0 || errno != EEXIST)
+ break;
+ }
+
+ if (f >= 0)
+ (void) unlink(fname);
+
+ uu_free(fname);
+
+ return (f);
+}
diff --git a/lib/libuutil/uu_pname.c b/lib/libuutil/uu_pname.c
new file mode 100644
index 000000000000..a6a0f22661e5
--- /dev/null
+++ b/lib/libuutil/uu_pname.c
@@ -0,0 +1,207 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2005 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+
+
+#include "libuutil_common.h"
+
+#include <libintl.h>
+#include <limits.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <errno.h>
+#include <wchar.h>
+#include <unistd.h>
+
+static const char PNAME_FMT[] = "%s: ";
+static const char ERRNO_FMT[] = ": %s\n";
+
+static const char *pname;
+
+static void
+uu_die_internal(int status, const char *format, va_list alist) __NORETURN;
+
+int uu_exit_ok_value = EXIT_SUCCESS;
+int uu_exit_fatal_value = EXIT_FAILURE;
+int uu_exit_usage_value = 2;
+
+int *
+uu_exit_ok(void)
+{
+ return (&uu_exit_ok_value);
+}
+
+int *
+uu_exit_fatal(void)
+{
+ return (&uu_exit_fatal_value);
+}
+
+int *
+uu_exit_usage(void)
+{
+ return (&uu_exit_usage_value);
+}
+
+void
+uu_alt_exit(int profile)
+{
+ switch (profile) {
+ case UU_PROFILE_DEFAULT:
+ uu_exit_ok_value = EXIT_SUCCESS;
+ uu_exit_fatal_value = EXIT_FAILURE;
+ uu_exit_usage_value = 2;
+ break;
+ case UU_PROFILE_LAUNCHER:
+ uu_exit_ok_value = EXIT_SUCCESS;
+ uu_exit_fatal_value = 124;
+ uu_exit_usage_value = 125;
+ break;
+ }
+}
+
+static void
+uu_warn_internal(int err, const char *format, va_list alist)
+{
+ if (pname != NULL)
+ (void) fprintf(stderr, PNAME_FMT, pname);
+
+ (void) vfprintf(stderr, format, alist);
+
+ if (strrchr(format, '\n') == NULL)
+ (void) fprintf(stderr, ERRNO_FMT, strerror(err));
+}
+
+void
+uu_vwarn(const char *format, va_list alist)
+{
+ uu_warn_internal(errno, format, alist);
+}
+
+/*PRINTFLIKE1*/
+void
+uu_warn(const char *format, ...)
+{
+ va_list alist;
+ va_start(alist, format);
+ uu_warn_internal(errno, format, alist);
+ va_end(alist);
+}
+
+static void
+uu_die_internal(int status, const char *format, va_list alist)
+{
+ uu_warn_internal(errno, format, alist);
+#ifdef DEBUG
+ {
+ char *cp;
+
+ if (!issetugid()) {
+ cp = getenv("UU_DIE_ABORTS");
+ if (cp != NULL && *cp != '\0')
+ abort();
+ }
+ }
+#endif
+ exit(status);
+}
+
+void
+uu_vdie(const char *format, va_list alist)
+{
+ uu_die_internal(UU_EXIT_FATAL, format, alist);
+}
+
+/*PRINTFLIKE1*/
+void
+uu_die(const char *format, ...)
+{
+ va_list alist;
+ va_start(alist, format);
+ uu_die_internal(UU_EXIT_FATAL, format, alist);
+ va_end(alist);
+}
+
+void
+uu_vxdie(int status, const char *format, va_list alist)
+{
+ uu_die_internal(status, format, alist);
+}
+
+/*PRINTFLIKE2*/
+void
+uu_xdie(int status, const char *format, ...)
+{
+ va_list alist;
+ va_start(alist, format);
+ uu_die_internal(status, format, alist);
+ va_end(alist);
+}
+
+const char *
+uu_setpname(char *arg0)
+{
+ /*
+ * Having a NULL argv[0], while uncommon, is possible. It
+ * makes more sense to handle this event in uu_setpname rather
+ * than in each of its consumers.
+ */
+ if (arg0 == NULL) {
+ pname = getexecname();
+ if (pname == NULL)
+ pname = "unknown_command";
+ return (pname);
+ }
+
+ /*
+ * Guard against '/' at end of command invocation.
+ */
+ for (;;) {
+ char *p = strrchr(arg0, '/');
+ if (p == NULL) {
+ pname = arg0;
+ break;
+ } else {
+ if (*(p + 1) == '\0') {
+ *p = '\0';
+ continue;
+ }
+
+ pname = p + 1;
+ break;
+ }
+ }
+
+ return (pname);
+}
+
+const char *
+uu_getpname(void)
+{
+ return (pname);
+}
diff --git a/lib/libuutil/uu_string.c b/lib/libuutil/uu_string.c
new file mode 100644
index 000000000000..67024c3b50b4
--- /dev/null
+++ b/lib/libuutil/uu_string.c
@@ -0,0 +1,54 @@
+/*
+ * 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) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
+ */
+
+/*
+ * String helper functions
+ */
+
+#include <string.h>
+#include <sys/types.h>
+#include <ctype.h>
+#include "libuutil.h"
+
+/* Return true if strings are equal */
+boolean_t
+uu_streq(const char *a, const char *b)
+{
+ return (strcmp(a, b) == 0);
+}
+
+/* Return true if strings are equal, case-insensitively */
+boolean_t
+uu_strcaseeq(const char *a, const char *b)
+{
+ return (strcasecmp(a, b) == 0);
+}
+
+/* Return true if string a Begins With string b */
+boolean_t
+uu_strbw(const char *a, const char *b)
+{
+ return (strncmp(a, b, strlen(b)) == 0);
+}
diff --git a/lib/libzfs/.gitignore b/lib/libzfs/.gitignore
new file mode 100644
index 000000000000..9336a5c00b46
--- /dev/null
+++ b/lib/libzfs/.gitignore
@@ -0,0 +1 @@
+/libzfs.pc
diff --git a/lib/libzfs/Makefile.am b/lib/libzfs/Makefile.am
new file mode 100644
index 000000000000..f88fb828d53d
--- /dev/null
+++ b/lib/libzfs/Makefile.am
@@ -0,0 +1,93 @@
+include $(top_srcdir)/config/Rules.am
+
+VPATH = \
+ $(top_srcdir)/module/icp \
+ $(top_srcdir)/module/zcommon \
+ $(top_srcdir)/lib/libzfs
+
+# Suppress unused but set variable warnings often due to ASSERTs
+AM_CFLAGS += $(NO_UNUSED_BUT_SET_VARIABLE)
+AM_CFLAGS += $(LIBCRYPTO_CFLAGS) $(ZLIB_CFLAGS)
+
+pkgconfig_DATA = libzfs.pc
+
+lib_LTLIBRARIES = libzfs.la
+
+USER_C = \
+ libzfs_changelist.c \
+ libzfs_config.c \
+ libzfs_crypto.c \
+ libzfs_dataset.c \
+ libzfs_diff.c \
+ libzfs_import.c \
+ libzfs_iter.c \
+ libzfs_mount.c \
+ libzfs_pool.c \
+ libzfs_sendrecv.c \
+ libzfs_status.c \
+ libzfs_util.c
+
+
+if BUILD_FREEBSD
+USER_C += \
+ os/freebsd/libzfs_compat.c \
+ os/freebsd/libzfs_ioctl_compat.c \
+ os/freebsd/libzfs_zmount.c
+endif
+
+if BUILD_LINUX
+USER_C += \
+ os/linux/libzfs_mount_os.c \
+ os/linux/libzfs_pool_os.c \
+ os/linux/libzfs_sendrecv_os.c \
+ os/linux/libzfs_util_os.c
+endif
+
+KERNEL_C = \
+ algs/sha2/sha2.c \
+ cityhash.c \
+ zfeature_common.c \
+ zfs_comutil.c \
+ zfs_deleg.c \
+ zfs_fletcher.c \
+ zfs_fletcher_aarch64_neon.c \
+ zfs_fletcher_avx512.c \
+ zfs_fletcher_intel.c \
+ zfs_fletcher_sse.c \
+ zfs_fletcher_superscalar.c \
+ zfs_fletcher_superscalar4.c \
+ zfs_namecheck.c \
+ zfs_prop.c \
+ zfs_uio.c \
+ zpool_prop.c \
+ zprop_common.c
+
+dist_libzfs_la_SOURCES = \
+ $(USER_C)
+
+nodist_libzfs_la_SOURCES = \
+ $(KERNEL_C)
+
+libzfs_la_LIBADD = \
+ $(abs_top_builddir)/lib/libshare/libshare.la \
+ $(abs_top_builddir)/lib/libzfs_core/libzfs_core.la \
+ $(abs_top_builddir)/lib/libnvpair/libnvpair.la \
+ $(abs_top_builddir)/lib/libuutil/libuutil.la
+
+libzfs_la_LIBADD += -lm $(LIBCRYPTO_LIBS) $(ZLIB_LIBS) $(LTLIBINTL)
+
+libzfs_la_LDFLAGS = -pthread
+
+if !ASAN_ENABLED
+libzfs_la_LDFLAGS += -Wl,-z,defs
+endif
+
+if BUILD_FREEBSD
+libzfs_la_LIBADD += -lutil -lgeom
+libzfs_la_LDFLAGS += -version-info 4:0:0
+else
+libzfs_la_LDFLAGS += -version-info 2:0:0
+endif
+
+# Licensing data
+EXTRA_DIST = THIRDPARTYLICENSE.openssl THIRDPARTYLICENSE.openssl.descrip
diff --git a/lib/libzfs/THIRDPARTYLICENSE.openssl b/lib/libzfs/THIRDPARTYLICENSE.openssl
new file mode 100644
index 000000000000..92c9e196a318
--- /dev/null
+++ b/lib/libzfs/THIRDPARTYLICENSE.openssl
@@ -0,0 +1,127 @@
+
+ LICENSE ISSUES
+ ==============
+
+ The OpenSSL toolkit stays under a dual license, i.e. both the conditions of
+ the OpenSSL License and the original SSLeay license apply to the toolkit.
+ See below for the actual license texts. Actually both licenses are BSD-style
+ Open Source licenses. In case of any license issues related to OpenSSL
+ please contact openssl-core@openssl.org.
+
+ OpenSSL License
+ ---------------
+
+/* ====================================================================
+ * Copyright (c) 1998-2008 The OpenSSL Project. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * 3. All advertising materials mentioning features or use of this
+ * software must display the following acknowledgment:
+ * "This product includes software developed by the OpenSSL Project
+ * for use in the OpenSSL Toolkit. (http://www.openssl.org/)"
+ *
+ * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to
+ * endorse or promote products derived from this software without
+ * prior written permission. For written permission, please contact
+ * openssl-core@openssl.org.
+ *
+ * 5. Products derived from this software may not be called "OpenSSL"
+ * nor may "OpenSSL" appear in their names without prior written
+ * permission of the OpenSSL Project.
+ *
+ * 6. Redistributions of any form whatsoever must retain the following
+ * acknowledgment:
+ * "This product includes software developed by the OpenSSL Project
+ * for use in the OpenSSL Toolkit (http://www.openssl.org/)"
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY
+ * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This product includes cryptographic software written by Eric Young
+ * (eay@cryptsoft.com). This product includes software written by Tim
+ * Hudson (tjh@cryptsoft.com).
+ *
+ */
+
+ Original SSLeay License
+ -----------------------
+
+/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com)
+ * All rights reserved.
+ *
+ * This package is an SSL implementation written
+ * by Eric Young (eay@cryptsoft.com).
+ * The implementation was written so as to conform with Netscapes SSL.
+ *
+ * This library is free for commercial and non-commercial use as long as
+ * the following conditions are aheared to. The following conditions
+ * apply to all code found in this distribution, be it the RC4, RSA,
+ * lhash, DES, etc., code; not just the SSL code. The SSL documentation
+ * included with this distribution is covered by the same copyright terms
+ * except that the holder is Tim Hudson (tjh@cryptsoft.com).
+ *
+ * Copyright remains Eric Young's, and as such any Copyright notices in
+ * the code are not to be removed.
+ * If this package is used in a product, Eric Young should be given attribution
+ * as the author of the parts of the library used.
+ * This can be in the form of a textual message at program startup or
+ * in documentation (online or textual) provided with the package.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * "This product includes cryptographic software written by
+ * Eric Young (eay@cryptsoft.com)"
+ * The word 'cryptographic' can be left out if the routines from the library
+ * being used are not cryptographic related :-).
+ * 4. If you include any Windows specific code (or a derivative thereof) from
+ * the apps directory (application code) you must include an acknowledgement:
+ * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)"
+ *
+ * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * The licence and distribution terms for any publically available version or
+ * derivative of this code cannot be changed. i.e. this code cannot simply be
+ * copied and put under another distribution licence
+ * [including the GNU Public Licence.]
+ */
+
diff --git a/lib/libzfs/THIRDPARTYLICENSE.openssl.descrip b/lib/libzfs/THIRDPARTYLICENSE.openssl.descrip
new file mode 100644
index 000000000000..a9125ea21122
--- /dev/null
+++ b/lib/libzfs/THIRDPARTYLICENSE.openssl.descrip
@@ -0,0 +1 @@
+PBKDF2 IMPLEMENTATION
diff --git a/lib/libzfs/libzfs.pc.in b/lib/libzfs/libzfs.pc.in
new file mode 100644
index 000000000000..6caf49d221f1
--- /dev/null
+++ b/lib/libzfs/libzfs.pc.in
@@ -0,0 +1,14 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: libzfs
+Description: LibZFS library
+Version: @VERSION@
+URL: https://zfsonlinux.org
+Requires: libzfs_core
+Requires.private: libcrypto zlib
+Cflags: -I${includedir}/libzfs -I${includedir}/libspl
+Libs: -L${libdir} -lzfs -lnvpair
+Libs.private: -luutil -lm -pthread
diff --git a/lib/libzfs/libzfs_changelist.c b/lib/libzfs/libzfs_changelist.c
new file mode 100644
index 000000000000..fec2fd5f2648
--- /dev/null
+++ b/lib/libzfs/libzfs_changelist.c
@@ -0,0 +1,781 @@
+/*
+ * 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 2010 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ *
+ * Portions Copyright 2007 Ramprakash Jelari
+ * Copyright (c) 2014, 2020 by Delphix. All rights reserved.
+ * Copyright 2016 Igor Kozhukhov <ikozhukhov@gmail.com>
+ * Copyright (c) 2018 Datto Inc.
+ */
+
+#include <libintl.h>
+#include <libuutil.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <zone.h>
+
+#include <libzfs.h>
+
+#include "libzfs_impl.h"
+
+/*
+ * Structure to keep track of dataset state. Before changing the 'sharenfs' or
+ * 'mountpoint' property, we record whether the filesystem was previously
+ * mounted/shared. This prior state dictates whether we remount/reshare the
+ * dataset after the property has been changed.
+ *
+ * The interface consists of the following sequence of functions:
+ *
+ * changelist_gather()
+ * changelist_prefix()
+ * < change property >
+ * changelist_postfix()
+ * changelist_free()
+ *
+ * Other interfaces:
+ *
+ * changelist_remove() - remove a node from a gathered list
+ * changelist_rename() - renames all datasets appropriately when doing a rename
+ * changelist_unshare() - unshares all the nodes in a given changelist
+ * changelist_haszonedchild() - check if there is any child exported to
+ * a local zone
+ */
+typedef struct prop_changenode {
+ zfs_handle_t *cn_handle;
+ int cn_shared;
+ int cn_mounted;
+ int cn_zoned;
+ boolean_t cn_needpost; /* is postfix() needed? */
+ uu_avl_node_t cn_treenode;
+} prop_changenode_t;
+
+struct prop_changelist {
+ zfs_prop_t cl_prop;
+ zfs_prop_t cl_realprop;
+ zfs_prop_t cl_shareprop; /* used with sharenfs/sharesmb */
+ uu_avl_pool_t *cl_pool;
+ uu_avl_t *cl_tree;
+ boolean_t cl_waslegacy;
+ boolean_t cl_allchildren;
+ boolean_t cl_alldependents;
+ int cl_mflags; /* Mount flags */
+ int cl_gflags; /* Gather request flags */
+ boolean_t cl_haszonedchild;
+};
+
+/*
+ * If the property is 'mountpoint', go through and unmount filesystems as
+ * necessary. We don't do the same for 'sharenfs', because we can just re-share
+ * with different options without interrupting service. We do handle 'sharesmb'
+ * since there may be old resource names that need to be removed.
+ */
+int
+changelist_prefix(prop_changelist_t *clp)
+{
+ prop_changenode_t *cn;
+ uu_avl_walk_t *walk;
+ int ret = 0;
+ boolean_t commit_smb_shares = B_FALSE;
+
+ if (clp->cl_prop != ZFS_PROP_MOUNTPOINT &&
+ clp->cl_prop != ZFS_PROP_SHARESMB)
+ return (0);
+
+ if ((walk = uu_avl_walk_start(clp->cl_tree, UU_WALK_ROBUST)) == NULL)
+ return (-1);
+
+ while ((cn = uu_avl_walk_next(walk)) != NULL) {
+
+ /* if a previous loop failed, set the remaining to false */
+ if (ret == -1) {
+ cn->cn_needpost = B_FALSE;
+ continue;
+ }
+
+ /*
+ * If we are in the global zone, but this dataset is exported
+ * to a local zone, do nothing.
+ */
+ if (getzoneid() == GLOBAL_ZONEID && cn->cn_zoned)
+ continue;
+
+ if (!ZFS_IS_VOLUME(cn->cn_handle)) {
+ /*
+ * Do the property specific processing.
+ */
+ switch (clp->cl_prop) {
+ case ZFS_PROP_MOUNTPOINT:
+ if (zfs_unmount(cn->cn_handle, NULL,
+ clp->cl_mflags) != 0) {
+ ret = -1;
+ cn->cn_needpost = B_FALSE;
+ }
+ break;
+ case ZFS_PROP_SHARESMB:
+ (void) zfs_unshare_smb(cn->cn_handle, NULL);
+ commit_smb_shares = B_TRUE;
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+
+ if (commit_smb_shares)
+ zfs_commit_smb_shares();
+ uu_avl_walk_end(walk);
+
+ if (ret == -1)
+ (void) changelist_postfix(clp);
+
+ return (ret);
+}
+
+/*
+ * If the property is 'mountpoint' or 'sharenfs', go through and remount and/or
+ * reshare the filesystems as necessary. In changelist_gather() we recorded
+ * whether the filesystem was previously shared or mounted. The action we take
+ * depends on the previous state, and whether the value was previously 'legacy'.
+ * For non-legacy properties, we only remount/reshare the filesystem if it was
+ * previously mounted/shared. Otherwise, we always remount/reshare the
+ * filesystem.
+ */
+int
+changelist_postfix(prop_changelist_t *clp)
+{
+ prop_changenode_t *cn;
+ uu_avl_walk_t *walk;
+ char shareopts[ZFS_MAXPROPLEN];
+ int errors = 0;
+ boolean_t commit_smb_shares = B_FALSE;
+ boolean_t commit_nfs_shares = B_FALSE;
+
+ /*
+ * If we're changing the mountpoint, attempt to destroy the underlying
+ * mountpoint. All other datasets will have inherited from this dataset
+ * (in which case their mountpoints exist in the filesystem in the new
+ * location), or have explicit mountpoints set (in which case they won't
+ * be in the changelist).
+ */
+ if ((cn = uu_avl_last(clp->cl_tree)) == NULL)
+ return (0);
+
+ if (clp->cl_prop == ZFS_PROP_MOUNTPOINT)
+ remove_mountpoint(cn->cn_handle);
+
+ /*
+ * We walk the datasets in reverse, because we want to mount any parent
+ * datasets before mounting the children. We walk all datasets even if
+ * there are errors.
+ */
+ if ((walk = uu_avl_walk_start(clp->cl_tree,
+ UU_WALK_REVERSE | UU_WALK_ROBUST)) == NULL)
+ return (-1);
+
+ while ((cn = uu_avl_walk_next(walk)) != NULL) {
+
+ boolean_t sharenfs;
+ boolean_t sharesmb;
+ boolean_t mounted;
+ boolean_t needs_key;
+
+ /*
+ * If we are in the global zone, but this dataset is exported
+ * to a local zone, do nothing.
+ */
+ if (getzoneid() == GLOBAL_ZONEID && cn->cn_zoned)
+ continue;
+
+ /* Only do post-processing if it's required */
+ if (!cn->cn_needpost)
+ continue;
+ cn->cn_needpost = B_FALSE;
+
+ zfs_refresh_properties(cn->cn_handle);
+
+ if (ZFS_IS_VOLUME(cn->cn_handle))
+ continue;
+
+ /*
+ * Remount if previously mounted or mountpoint was legacy,
+ * or sharenfs or sharesmb property is set.
+ */
+ sharenfs = ((zfs_prop_get(cn->cn_handle, ZFS_PROP_SHARENFS,
+ shareopts, sizeof (shareopts), NULL, NULL, 0,
+ B_FALSE) == 0) && (strcmp(shareopts, "off") != 0));
+
+ sharesmb = ((zfs_prop_get(cn->cn_handle, ZFS_PROP_SHARESMB,
+ shareopts, sizeof (shareopts), NULL, NULL, 0,
+ B_FALSE) == 0) && (strcmp(shareopts, "off") != 0));
+
+ needs_key = (zfs_prop_get_int(cn->cn_handle,
+ ZFS_PROP_KEYSTATUS) == ZFS_KEYSTATUS_UNAVAILABLE);
+
+ mounted = zfs_is_mounted(cn->cn_handle, NULL);
+
+ if (!mounted && !needs_key && (cn->cn_mounted ||
+ ((sharenfs || sharesmb || clp->cl_waslegacy) &&
+ (zfs_prop_get_int(cn->cn_handle,
+ ZFS_PROP_CANMOUNT) == ZFS_CANMOUNT_ON)))) {
+
+ if (zfs_mount(cn->cn_handle, NULL, 0) != 0)
+ errors++;
+ else
+ mounted = TRUE;
+ }
+
+ /*
+ * If the file system is mounted we always re-share even
+ * if the filesystem is currently shared, so that we can
+ * adopt any new options.
+ */
+ if (sharenfs && mounted) {
+ errors += zfs_share_nfs(cn->cn_handle);
+ commit_nfs_shares = B_TRUE;
+ } else if (cn->cn_shared || clp->cl_waslegacy) {
+ errors += zfs_unshare_nfs(cn->cn_handle, NULL);
+ commit_nfs_shares = B_TRUE;
+ }
+ if (sharesmb && mounted) {
+ errors += zfs_share_smb(cn->cn_handle);
+ commit_smb_shares = B_TRUE;
+ } else if (cn->cn_shared || clp->cl_waslegacy) {
+ errors += zfs_unshare_smb(cn->cn_handle, NULL);
+ commit_smb_shares = B_TRUE;
+ }
+ }
+ if (commit_nfs_shares)
+ zfs_commit_nfs_shares();
+ if (commit_smb_shares)
+ zfs_commit_smb_shares();
+ uu_avl_walk_end(walk);
+
+ return (errors ? -1 : 0);
+}
+
+/*
+ * Is this "dataset" a child of "parent"?
+ */
+boolean_t
+isa_child_of(const char *dataset, const char *parent)
+{
+ int len;
+
+ len = strlen(parent);
+
+ if (strncmp(dataset, parent, len) == 0 &&
+ (dataset[len] == '@' || dataset[len] == '/' ||
+ dataset[len] == '\0'))
+ return (B_TRUE);
+ else
+ return (B_FALSE);
+
+}
+
+/*
+ * If we rename a filesystem, child filesystem handles are no longer valid
+ * since we identify each dataset by its name in the ZFS namespace. As a
+ * result, we have to go through and fix up all the names appropriately. We
+ * could do this automatically if libzfs kept track of all open handles, but
+ * this is a lot less work.
+ */
+void
+changelist_rename(prop_changelist_t *clp, const char *src, const char *dst)
+{
+ prop_changenode_t *cn;
+ uu_avl_walk_t *walk;
+ char newname[ZFS_MAX_DATASET_NAME_LEN];
+
+ if ((walk = uu_avl_walk_start(clp->cl_tree, UU_WALK_ROBUST)) == NULL)
+ return;
+
+ while ((cn = uu_avl_walk_next(walk)) != NULL) {
+ /*
+ * Do not rename a clone that's not in the source hierarchy.
+ */
+ if (!isa_child_of(cn->cn_handle->zfs_name, src))
+ continue;
+
+ /*
+ * Destroy the previous mountpoint if needed.
+ */
+ remove_mountpoint(cn->cn_handle);
+
+ (void) strlcpy(newname, dst, sizeof (newname));
+ (void) strlcat(newname, cn->cn_handle->zfs_name + strlen(src),
+ sizeof (newname));
+
+ (void) strlcpy(cn->cn_handle->zfs_name, newname,
+ sizeof (cn->cn_handle->zfs_name));
+ }
+
+ uu_avl_walk_end(walk);
+}
+
+/*
+ * Given a gathered changelist for the 'sharenfs' or 'sharesmb' property,
+ * unshare all the datasets in the list.
+ */
+int
+changelist_unshare(prop_changelist_t *clp, zfs_share_proto_t *proto)
+{
+ prop_changenode_t *cn;
+ uu_avl_walk_t *walk;
+ int ret = 0;
+
+ if (clp->cl_prop != ZFS_PROP_SHARENFS &&
+ clp->cl_prop != ZFS_PROP_SHARESMB)
+ return (0);
+
+ if ((walk = uu_avl_walk_start(clp->cl_tree, UU_WALK_ROBUST)) == NULL)
+ return (-1);
+
+ while ((cn = uu_avl_walk_next(walk)) != NULL) {
+ if (zfs_unshare_proto(cn->cn_handle, NULL, proto) != 0)
+ ret = -1;
+ }
+
+ zfs_commit_proto(proto);
+ uu_avl_walk_end(walk);
+
+ return (ret);
+}
+
+/*
+ * Check if there is any child exported to a local zone in a given changelist.
+ * This information has already been recorded while gathering the changelist
+ * via changelist_gather().
+ */
+int
+changelist_haszonedchild(prop_changelist_t *clp)
+{
+ return (clp->cl_haszonedchild);
+}
+
+/*
+ * Remove a node from a gathered list.
+ */
+void
+changelist_remove(prop_changelist_t *clp, const char *name)
+{
+ prop_changenode_t *cn;
+ uu_avl_walk_t *walk;
+
+ if ((walk = uu_avl_walk_start(clp->cl_tree, UU_WALK_ROBUST)) == NULL)
+ return;
+
+ while ((cn = uu_avl_walk_next(walk)) != NULL) {
+ if (strcmp(cn->cn_handle->zfs_name, name) == 0) {
+ uu_avl_remove(clp->cl_tree, cn);
+ zfs_close(cn->cn_handle);
+ free(cn);
+ uu_avl_walk_end(walk);
+ return;
+ }
+ }
+
+ uu_avl_walk_end(walk);
+}
+
+/*
+ * Release any memory associated with a changelist.
+ */
+void
+changelist_free(prop_changelist_t *clp)
+{
+ prop_changenode_t *cn;
+
+ if (clp->cl_tree) {
+ uu_avl_walk_t *walk;
+
+ if ((walk = uu_avl_walk_start(clp->cl_tree,
+ UU_WALK_ROBUST)) == NULL)
+ return;
+
+ while ((cn = uu_avl_walk_next(walk)) != NULL) {
+ uu_avl_remove(clp->cl_tree, cn);
+ zfs_close(cn->cn_handle);
+ free(cn);
+ }
+
+ uu_avl_walk_end(walk);
+ uu_avl_destroy(clp->cl_tree);
+ }
+ if (clp->cl_pool)
+ uu_avl_pool_destroy(clp->cl_pool);
+
+ free(clp);
+}
+
+/*
+ * Add one dataset to changelist
+ */
+static int
+changelist_add_mounted(zfs_handle_t *zhp, void *data)
+{
+ prop_changelist_t *clp = data;
+ prop_changenode_t *cn;
+ uu_avl_index_t idx;
+
+ ASSERT3U(clp->cl_prop, ==, ZFS_PROP_MOUNTPOINT);
+
+ if ((cn = zfs_alloc(zfs_get_handle(zhp),
+ sizeof (prop_changenode_t))) == NULL) {
+ zfs_close(zhp);
+ return (ENOMEM);
+ }
+
+ cn->cn_handle = zhp;
+ cn->cn_mounted = zfs_is_mounted(zhp, NULL);
+ ASSERT3U(cn->cn_mounted, ==, B_TRUE);
+ cn->cn_shared = zfs_is_shared(zhp);
+ cn->cn_zoned = zfs_prop_get_int(zhp, ZFS_PROP_ZONED);
+ cn->cn_needpost = B_TRUE;
+
+ /* Indicate if any child is exported to a local zone. */
+ if (getzoneid() == GLOBAL_ZONEID && cn->cn_zoned)
+ clp->cl_haszonedchild = B_TRUE;
+
+ uu_avl_node_init(cn, &cn->cn_treenode, clp->cl_pool);
+
+ if (uu_avl_find(clp->cl_tree, cn, NULL, &idx) == NULL) {
+ uu_avl_insert(clp->cl_tree, cn, idx);
+ } else {
+ free(cn);
+ zfs_close(zhp);
+ }
+
+ return (0);
+}
+
+static int
+change_one(zfs_handle_t *zhp, void *data)
+{
+ prop_changelist_t *clp = data;
+ char property[ZFS_MAXPROPLEN];
+ char where[64];
+ prop_changenode_t *cn = NULL;
+ zprop_source_t sourcetype = ZPROP_SRC_NONE;
+ zprop_source_t share_sourcetype = ZPROP_SRC_NONE;
+ int ret = 0;
+
+ /*
+ * We only want to unmount/unshare those filesystems that may inherit
+ * from the target filesystem. If we find any filesystem with a
+ * locally set mountpoint, we ignore any children since changing the
+ * property will not affect them. If this is a rename, we iterate
+ * over all children regardless, since we need them unmounted in
+ * order to do the rename. Also, if this is a volume and we're doing
+ * a rename, then always add it to the changelist.
+ */
+
+ if (!(ZFS_IS_VOLUME(zhp) && clp->cl_realprop == ZFS_PROP_NAME) &&
+ zfs_prop_get(zhp, clp->cl_prop, property,
+ sizeof (property), &sourcetype, where, sizeof (where),
+ B_FALSE) != 0) {
+ goto out;
+ }
+
+ /*
+ * If we are "watching" sharenfs or sharesmb
+ * then check out the companion property which is tracked
+ * in cl_shareprop
+ */
+ if (clp->cl_shareprop != ZPROP_INVAL &&
+ zfs_prop_get(zhp, clp->cl_shareprop, property,
+ sizeof (property), &share_sourcetype, where, sizeof (where),
+ B_FALSE) != 0) {
+ goto out;
+ }
+
+ if (clp->cl_alldependents || clp->cl_allchildren ||
+ sourcetype == ZPROP_SRC_DEFAULT ||
+ sourcetype == ZPROP_SRC_INHERITED ||
+ (clp->cl_shareprop != ZPROP_INVAL &&
+ (share_sourcetype == ZPROP_SRC_DEFAULT ||
+ share_sourcetype == ZPROP_SRC_INHERITED))) {
+ if ((cn = zfs_alloc(zfs_get_handle(zhp),
+ sizeof (prop_changenode_t))) == NULL) {
+ ret = -1;
+ goto out;
+ }
+
+ cn->cn_handle = zhp;
+ cn->cn_mounted = (clp->cl_gflags & CL_GATHER_MOUNT_ALWAYS) ||
+ zfs_is_mounted(zhp, NULL);
+ cn->cn_shared = zfs_is_shared(zhp);
+ cn->cn_zoned = zfs_prop_get_int(zhp, ZFS_PROP_ZONED);
+ cn->cn_needpost = B_TRUE;
+
+ /* Indicate if any child is exported to a local zone. */
+ if (getzoneid() == GLOBAL_ZONEID && cn->cn_zoned)
+ clp->cl_haszonedchild = B_TRUE;
+
+ uu_avl_node_init(cn, &cn->cn_treenode, clp->cl_pool);
+
+ uu_avl_index_t idx;
+
+ if (uu_avl_find(clp->cl_tree, cn, NULL, &idx) == NULL) {
+ uu_avl_insert(clp->cl_tree, cn, idx);
+ } else {
+ free(cn);
+ cn = NULL;
+ }
+
+ if (!clp->cl_alldependents)
+ ret = zfs_iter_children(zhp, change_one, data);
+
+ /*
+ * If we added the handle to the changelist, we will re-use it
+ * later so return without closing it.
+ */
+ if (cn != NULL)
+ return (ret);
+ }
+
+out:
+ zfs_close(zhp);
+ return (ret);
+}
+
+static int
+compare_props(const void *a, const void *b, zfs_prop_t prop)
+{
+ const prop_changenode_t *ca = a;
+ const prop_changenode_t *cb = b;
+
+ char propa[MAXPATHLEN];
+ char propb[MAXPATHLEN];
+
+ boolean_t haspropa, haspropb;
+
+ haspropa = (zfs_prop_get(ca->cn_handle, prop, propa, sizeof (propa),
+ NULL, NULL, 0, B_FALSE) == 0);
+ haspropb = (zfs_prop_get(cb->cn_handle, prop, propb, sizeof (propb),
+ NULL, NULL, 0, B_FALSE) == 0);
+
+ if (!haspropa && haspropb)
+ return (-1);
+ else if (haspropa && !haspropb)
+ return (1);
+ else if (!haspropa && !haspropb)
+ return (0);
+ else
+ return (strcmp(propb, propa));
+}
+
+/*ARGSUSED*/
+static int
+compare_mountpoints(const void *a, const void *b, void *unused)
+{
+ /*
+ * When unsharing or unmounting filesystems, we need to do it in
+ * mountpoint order. This allows the user to have a mountpoint
+ * hierarchy that is different from the dataset hierarchy, and still
+ * allow it to be changed.
+ */
+ return (compare_props(a, b, ZFS_PROP_MOUNTPOINT));
+}
+
+/*ARGSUSED*/
+static int
+compare_dataset_names(const void *a, const void *b, void *unused)
+{
+ return (compare_props(a, b, ZFS_PROP_NAME));
+}
+
+/*
+ * Given a ZFS handle and a property, construct a complete list of datasets
+ * that need to be modified as part of this process. For anything but the
+ * 'mountpoint' and 'sharenfs' properties, this just returns an empty list.
+ * Otherwise, we iterate over all children and look for any datasets that
+ * inherit the property. For each such dataset, we add it to the list and
+ * mark whether it was shared beforehand.
+ */
+prop_changelist_t *
+changelist_gather(zfs_handle_t *zhp, zfs_prop_t prop, int gather_flags,
+ int mnt_flags)
+{
+ prop_changelist_t *clp;
+ prop_changenode_t *cn;
+ zfs_handle_t *temp;
+ char property[ZFS_MAXPROPLEN];
+ boolean_t legacy = B_FALSE;
+
+ if ((clp = zfs_alloc(zhp->zfs_hdl, sizeof (prop_changelist_t))) == NULL)
+ return (NULL);
+
+ /*
+ * For mountpoint-related tasks, we want to sort everything by
+ * mountpoint, so that we mount and unmount them in the appropriate
+ * order, regardless of their position in the hierarchy.
+ */
+ if (prop == ZFS_PROP_NAME || prop == ZFS_PROP_ZONED ||
+ prop == ZFS_PROP_MOUNTPOINT || prop == ZFS_PROP_SHARENFS ||
+ prop == ZFS_PROP_SHARESMB) {
+
+ if (zfs_prop_get(zhp, ZFS_PROP_MOUNTPOINT,
+ property, sizeof (property),
+ NULL, NULL, 0, B_FALSE) == 0 &&
+ (strcmp(property, "legacy") == 0 ||
+ strcmp(property, "none") == 0)) {
+ legacy = B_TRUE;
+ }
+ }
+
+ clp->cl_pool = uu_avl_pool_create("changelist_pool",
+ sizeof (prop_changenode_t),
+ offsetof(prop_changenode_t, cn_treenode),
+ legacy ? compare_dataset_names : compare_mountpoints, 0);
+ if (clp->cl_pool == NULL) {
+ assert(uu_error() == UU_ERROR_NO_MEMORY);
+ (void) zfs_error(zhp->zfs_hdl, EZFS_NOMEM, "internal error");
+ changelist_free(clp);
+ return (NULL);
+ }
+
+ clp->cl_tree = uu_avl_create(clp->cl_pool, NULL, UU_DEFAULT);
+ clp->cl_gflags = gather_flags;
+ clp->cl_mflags = mnt_flags;
+
+ if (clp->cl_tree == NULL) {
+ assert(uu_error() == UU_ERROR_NO_MEMORY);
+ (void) zfs_error(zhp->zfs_hdl, EZFS_NOMEM, "internal error");
+ changelist_free(clp);
+ return (NULL);
+ }
+
+ /*
+ * If this is a rename or the 'zoned' property, we pretend we're
+ * changing the mountpoint and flag it so we can catch all children in
+ * change_one().
+ *
+ * Flag cl_alldependents to catch all children plus the dependents
+ * (clones) that are not in the hierarchy.
+ */
+ if (prop == ZFS_PROP_NAME) {
+ clp->cl_prop = ZFS_PROP_MOUNTPOINT;
+ clp->cl_alldependents = B_TRUE;
+ } else if (prop == ZFS_PROP_ZONED) {
+ clp->cl_prop = ZFS_PROP_MOUNTPOINT;
+ clp->cl_allchildren = B_TRUE;
+ } else if (prop == ZFS_PROP_CANMOUNT) {
+ clp->cl_prop = ZFS_PROP_MOUNTPOINT;
+ } else if (prop == ZFS_PROP_VOLSIZE) {
+ clp->cl_prop = ZFS_PROP_MOUNTPOINT;
+ } else {
+ clp->cl_prop = prop;
+ }
+ clp->cl_realprop = prop;
+
+ if (clp->cl_prop != ZFS_PROP_MOUNTPOINT &&
+ clp->cl_prop != ZFS_PROP_SHARENFS &&
+ clp->cl_prop != ZFS_PROP_SHARESMB)
+ return (clp);
+
+ /*
+ * If watching SHARENFS or SHARESMB then
+ * also watch its companion property.
+ */
+ if (clp->cl_prop == ZFS_PROP_SHARENFS)
+ clp->cl_shareprop = ZFS_PROP_SHARESMB;
+ else if (clp->cl_prop == ZFS_PROP_SHARESMB)
+ clp->cl_shareprop = ZFS_PROP_SHARENFS;
+
+ if (clp->cl_prop == ZFS_PROP_MOUNTPOINT &&
+ (clp->cl_gflags & CL_GATHER_ITER_MOUNTED)) {
+ /*
+ * Instead of iterating through all of the dataset children we
+ * gather mounted dataset children from MNTTAB
+ */
+ if (zfs_iter_mounted(zhp, changelist_add_mounted, clp) != 0) {
+ changelist_free(clp);
+ return (NULL);
+ }
+ } else if (clp->cl_alldependents) {
+ if (zfs_iter_dependents(zhp, B_TRUE, change_one, clp) != 0) {
+ changelist_free(clp);
+ return (NULL);
+ }
+ } else if (zfs_iter_children(zhp, change_one, clp) != 0) {
+ changelist_free(clp);
+ return (NULL);
+ }
+
+ /*
+ * We have to re-open ourselves because we auto-close all the handles
+ * and can't tell the difference.
+ */
+ if ((temp = zfs_open(zhp->zfs_hdl, zfs_get_name(zhp),
+ ZFS_TYPE_DATASET)) == NULL) {
+ changelist_free(clp);
+ return (NULL);
+ }
+
+ /*
+ * Always add ourself to the list. We add ourselves to the end so that
+ * we're the last to be unmounted.
+ */
+ if ((cn = zfs_alloc(zhp->zfs_hdl,
+ sizeof (prop_changenode_t))) == NULL) {
+ zfs_close(temp);
+ changelist_free(clp);
+ return (NULL);
+ }
+
+ cn->cn_handle = temp;
+ cn->cn_mounted = (clp->cl_gflags & CL_GATHER_MOUNT_ALWAYS) ||
+ zfs_is_mounted(temp, NULL);
+ cn->cn_shared = zfs_is_shared(temp);
+ cn->cn_zoned = zfs_prop_get_int(zhp, ZFS_PROP_ZONED);
+ cn->cn_needpost = B_TRUE;
+
+ uu_avl_node_init(cn, &cn->cn_treenode, clp->cl_pool);
+ uu_avl_index_t idx;
+ if (uu_avl_find(clp->cl_tree, cn, NULL, &idx) == NULL) {
+ uu_avl_insert(clp->cl_tree, cn, idx);
+ } else {
+ free(cn);
+ zfs_close(temp);
+ }
+
+ /*
+ * If the mountpoint property was previously 'legacy', or 'none',
+ * record it as the behavior of changelist_postfix() will be different.
+ */
+ if ((clp->cl_prop == ZFS_PROP_MOUNTPOINT) && legacy) {
+ /*
+ * do not automatically mount ex-legacy datasets if
+ * we specifically set canmount to noauto
+ */
+ if (zfs_prop_get_int(zhp, ZFS_PROP_CANMOUNT) !=
+ ZFS_CANMOUNT_NOAUTO)
+ clp->cl_waslegacy = B_TRUE;
+ }
+
+ return (clp);
+}
diff --git a/lib/libzfs/libzfs_config.c b/lib/libzfs/libzfs_config.c
new file mode 100644
index 000000000000..a3ecc4a327dc
--- /dev/null
+++ b/lib/libzfs/libzfs_config.c
@@ -0,0 +1,457 @@
+/*
+ * 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 2009 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+/*
+ * Copyright (c) 2012 by Delphix. All rights reserved.
+ * Copyright (c) 2015 by Syneto S.R.L. All rights reserved.
+ * Copyright 2016 Nexenta Systems, Inc.
+ */
+
+/*
+ * The pool configuration repository is stored in /etc/zfs/zpool.cache as a
+ * single packed nvlist. While it would be nice to just read in this
+ * file from userland, this wouldn't work from a local zone. So we have to have
+ * a zpool ioctl to return the complete configuration for all pools. In the
+ * global zone, this will be identical to reading the file and unpacking it in
+ * userland.
+ */
+
+#include <errno.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stddef.h>
+#include <string.h>
+#include <unistd.h>
+#include <libintl.h>
+#include <libuutil.h>
+
+#include "libzfs_impl.h"
+
+typedef struct config_node {
+ char *cn_name;
+ nvlist_t *cn_config;
+ uu_avl_node_t cn_avl;
+} config_node_t;
+
+/* ARGSUSED */
+static int
+config_node_compare(const void *a, const void *b, void *unused)
+{
+ int ret;
+
+ const config_node_t *ca = (config_node_t *)a;
+ const config_node_t *cb = (config_node_t *)b;
+
+ ret = strcmp(ca->cn_name, cb->cn_name);
+
+ if (ret < 0)
+ return (-1);
+ else if (ret > 0)
+ return (1);
+ else
+ return (0);
+}
+
+void
+namespace_clear(libzfs_handle_t *hdl)
+{
+ if (hdl->libzfs_ns_avl) {
+ config_node_t *cn;
+ void *cookie = NULL;
+
+ while ((cn = uu_avl_teardown(hdl->libzfs_ns_avl,
+ &cookie)) != NULL) {
+ nvlist_free(cn->cn_config);
+ free(cn->cn_name);
+ free(cn);
+ }
+
+ uu_avl_destroy(hdl->libzfs_ns_avl);
+ hdl->libzfs_ns_avl = NULL;
+ }
+
+ if (hdl->libzfs_ns_avlpool) {
+ uu_avl_pool_destroy(hdl->libzfs_ns_avlpool);
+ hdl->libzfs_ns_avlpool = NULL;
+ }
+}
+
+/*
+ * Loads the pool namespace, or re-loads it if the cache has changed.
+ */
+static int
+namespace_reload(libzfs_handle_t *hdl)
+{
+ nvlist_t *config;
+ config_node_t *cn;
+ nvpair_t *elem;
+ zfs_cmd_t zc = {"\0"};
+ void *cookie;
+
+ if (hdl->libzfs_ns_gen == 0) {
+ /*
+ * This is the first time we've accessed the configuration
+ * cache. Initialize the AVL tree and then fall through to the
+ * common code.
+ */
+ if ((hdl->libzfs_ns_avlpool = uu_avl_pool_create("config_pool",
+ sizeof (config_node_t),
+ offsetof(config_node_t, cn_avl),
+ config_node_compare, UU_DEFAULT)) == NULL)
+ return (no_memory(hdl));
+
+ if ((hdl->libzfs_ns_avl = uu_avl_create(hdl->libzfs_ns_avlpool,
+ NULL, UU_DEFAULT)) == NULL)
+ return (no_memory(hdl));
+ }
+
+ if (zcmd_alloc_dst_nvlist(hdl, &zc, 0) != 0)
+ return (-1);
+
+ for (;;) {
+ zc.zc_cookie = hdl->libzfs_ns_gen;
+ if (zfs_ioctl(hdl, ZFS_IOC_POOL_CONFIGS, &zc) != 0) {
+ switch (errno) {
+ case EEXIST:
+ /*
+ * The namespace hasn't changed.
+ */
+ zcmd_free_nvlists(&zc);
+ return (0);
+
+ case ENOMEM:
+ if (zcmd_expand_dst_nvlist(hdl, &zc) != 0) {
+ zcmd_free_nvlists(&zc);
+ return (-1);
+ }
+ break;
+
+ default:
+ zcmd_free_nvlists(&zc);
+ return (zfs_standard_error(hdl, errno,
+ dgettext(TEXT_DOMAIN, "failed to read "
+ "pool configuration")));
+ }
+ } else {
+ hdl->libzfs_ns_gen = zc.zc_cookie;
+ break;
+ }
+ }
+
+ if (zcmd_read_dst_nvlist(hdl, &zc, &config) != 0) {
+ zcmd_free_nvlists(&zc);
+ return (-1);
+ }
+
+ zcmd_free_nvlists(&zc);
+
+ /*
+ * Clear out any existing configuration information.
+ */
+ cookie = NULL;
+ while ((cn = uu_avl_teardown(hdl->libzfs_ns_avl, &cookie)) != NULL) {
+ nvlist_free(cn->cn_config);
+ free(cn->cn_name);
+ free(cn);
+ }
+
+ elem = NULL;
+ while ((elem = nvlist_next_nvpair(config, elem)) != NULL) {
+ nvlist_t *child;
+ uu_avl_index_t where;
+
+ if ((cn = zfs_alloc(hdl, sizeof (config_node_t))) == NULL) {
+ nvlist_free(config);
+ return (-1);
+ }
+
+ if ((cn->cn_name = zfs_strdup(hdl,
+ nvpair_name(elem))) == NULL) {
+ free(cn);
+ nvlist_free(config);
+ return (-1);
+ }
+
+ verify(nvpair_value_nvlist(elem, &child) == 0);
+ if (nvlist_dup(child, &cn->cn_config, 0) != 0) {
+ free(cn->cn_name);
+ free(cn);
+ nvlist_free(config);
+ return (no_memory(hdl));
+ }
+ verify(uu_avl_find(hdl->libzfs_ns_avl, cn, NULL, &where)
+ == NULL);
+
+ uu_avl_insert(hdl->libzfs_ns_avl, cn, where);
+ }
+
+ nvlist_free(config);
+ return (0);
+}
+
+/*
+ * Retrieve the configuration for the given pool. The configuration is an nvlist
+ * describing the vdevs, as well as the statistics associated with each one.
+ */
+nvlist_t *
+zpool_get_config(zpool_handle_t *zhp, nvlist_t **oldconfig)
+{
+ if (oldconfig)
+ *oldconfig = zhp->zpool_old_config;
+ return (zhp->zpool_config);
+}
+
+/*
+ * Retrieves a list of enabled features and their refcounts and caches it in
+ * the pool handle.
+ */
+nvlist_t *
+zpool_get_features(zpool_handle_t *zhp)
+{
+ nvlist_t *config, *features;
+
+ config = zpool_get_config(zhp, NULL);
+
+ if (config == NULL || !nvlist_exists(config,
+ ZPOOL_CONFIG_FEATURE_STATS)) {
+ int error;
+ boolean_t missing = B_FALSE;
+
+ error = zpool_refresh_stats(zhp, &missing);
+
+ if (error != 0 || missing)
+ return (NULL);
+
+ config = zpool_get_config(zhp, NULL);
+ }
+
+ if (nvlist_lookup_nvlist(config, ZPOOL_CONFIG_FEATURE_STATS,
+ &features) != 0)
+ return (NULL);
+
+ return (features);
+}
+
+/*
+ * Refresh the vdev statistics associated with the given pool. This is used in
+ * iostat to show configuration changes and determine the delta from the last
+ * time the function was called. This function can fail, in case the pool has
+ * been destroyed.
+ */
+int
+zpool_refresh_stats(zpool_handle_t *zhp, boolean_t *missing)
+{
+ zfs_cmd_t zc = {"\0"};
+ int error;
+ nvlist_t *config;
+ libzfs_handle_t *hdl = zhp->zpool_hdl;
+
+ *missing = B_FALSE;
+ (void) strcpy(zc.zc_name, zhp->zpool_name);
+
+ if (zhp->zpool_config_size == 0)
+ zhp->zpool_config_size = 1 << 16;
+
+ if (zcmd_alloc_dst_nvlist(hdl, &zc, zhp->zpool_config_size) != 0)
+ return (-1);
+
+ for (;;) {
+ if (zfs_ioctl(zhp->zpool_hdl, ZFS_IOC_POOL_STATS,
+ &zc) == 0) {
+ /*
+ * The real error is returned in the zc_cookie field.
+ */
+ error = zc.zc_cookie;
+ break;
+ }
+
+ if (errno == ENOMEM) {
+ if (zcmd_expand_dst_nvlist(hdl, &zc) != 0) {
+ zcmd_free_nvlists(&zc);
+ return (-1);
+ }
+ } else {
+ zcmd_free_nvlists(&zc);
+ if (errno == ENOENT || errno == EINVAL)
+ *missing = B_TRUE;
+ zhp->zpool_state = POOL_STATE_UNAVAIL;
+ return (0);
+ }
+ }
+
+ if (zcmd_read_dst_nvlist(hdl, &zc, &config) != 0) {
+ zcmd_free_nvlists(&zc);
+ return (-1);
+ }
+
+ zcmd_free_nvlists(&zc);
+
+ zhp->zpool_config_size = zc.zc_nvlist_dst_size;
+
+ if (zhp->zpool_config != NULL) {
+ nvlist_free(zhp->zpool_old_config);
+
+ zhp->zpool_old_config = zhp->zpool_config;
+ }
+
+ zhp->zpool_config = config;
+ if (error)
+ zhp->zpool_state = POOL_STATE_UNAVAIL;
+ else
+ zhp->zpool_state = POOL_STATE_ACTIVE;
+
+ return (0);
+}
+
+/*
+ * The following environment variables are undocumented
+ * and should be used for testing purposes only:
+ *
+ * __ZFS_POOL_EXCLUDE - don't iterate over the pools it lists
+ * __ZFS_POOL_RESTRICT - iterate only over the pools it lists
+ *
+ * This function returns B_TRUE if the pool should be skipped
+ * during iteration.
+ */
+boolean_t
+zpool_skip_pool(const char *poolname)
+{
+ static boolean_t initialized = B_FALSE;
+ static const char *exclude = NULL;
+ static const char *restricted = NULL;
+
+ const char *cur, *end;
+ int len;
+ int namelen = strlen(poolname);
+
+ if (!initialized) {
+ initialized = B_TRUE;
+ exclude = getenv("__ZFS_POOL_EXCLUDE");
+ restricted = getenv("__ZFS_POOL_RESTRICT");
+ }
+
+ if (exclude != NULL) {
+ cur = exclude;
+ do {
+ end = strchr(cur, ' ');
+ len = (NULL == end) ? strlen(cur) : (end - cur);
+ if (len == namelen && 0 == strncmp(cur, poolname, len))
+ return (B_TRUE);
+ cur += (len + 1);
+ } while (NULL != end);
+ }
+
+ if (NULL == restricted)
+ return (B_FALSE);
+
+ cur = restricted;
+ do {
+ end = strchr(cur, ' ');
+ len = (NULL == end) ? strlen(cur) : (end - cur);
+
+ if (len == namelen && 0 == strncmp(cur, poolname, len)) {
+ return (B_FALSE);
+ }
+
+ cur += (len + 1);
+ } while (NULL != end);
+
+ return (B_TRUE);
+}
+
+/*
+ * Iterate over all pools in the system.
+ */
+int
+zpool_iter(libzfs_handle_t *hdl, zpool_iter_f func, void *data)
+{
+ config_node_t *cn;
+ zpool_handle_t *zhp;
+ int ret;
+
+ /*
+ * If someone makes a recursive call to zpool_iter(), we want to avoid
+ * refreshing the namespace because that will invalidate the parent
+ * context. We allow recursive calls, but simply re-use the same
+ * namespace AVL tree.
+ */
+ if (!hdl->libzfs_pool_iter && namespace_reload(hdl) != 0)
+ return (-1);
+
+ hdl->libzfs_pool_iter++;
+ for (cn = uu_avl_first(hdl->libzfs_ns_avl); cn != NULL;
+ cn = uu_avl_next(hdl->libzfs_ns_avl, cn)) {
+
+ if (zpool_skip_pool(cn->cn_name))
+ continue;
+
+ if (zpool_open_silent(hdl, cn->cn_name, &zhp) != 0) {
+ hdl->libzfs_pool_iter--;
+ return (-1);
+ }
+
+ if (zhp == NULL)
+ continue;
+
+ if ((ret = func(zhp, data)) != 0) {
+ hdl->libzfs_pool_iter--;
+ return (ret);
+ }
+ }
+ hdl->libzfs_pool_iter--;
+
+ return (0);
+}
+
+/*
+ * Iterate over root datasets, calling the given function for each. The zfs
+ * handle passed each time must be explicitly closed by the callback.
+ */
+int
+zfs_iter_root(libzfs_handle_t *hdl, zfs_iter_f func, void *data)
+{
+ config_node_t *cn;
+ zfs_handle_t *zhp;
+ int ret;
+
+ if (namespace_reload(hdl) != 0)
+ return (-1);
+
+ for (cn = uu_avl_first(hdl->libzfs_ns_avl); cn != NULL;
+ cn = uu_avl_next(hdl->libzfs_ns_avl, cn)) {
+
+ if (zpool_skip_pool(cn->cn_name))
+ continue;
+
+ if ((zhp = make_dataset_handle(hdl, cn->cn_name)) == NULL)
+ continue;
+
+ if ((ret = func(zhp, data)) != 0)
+ return (ret);
+ }
+
+ return (0);
+}
diff --git a/lib/libzfs/libzfs_crypto.c b/lib/libzfs/libzfs_crypto.c
new file mode 100644
index 000000000000..1a2ee638aef3
--- /dev/null
+++ b/lib/libzfs/libzfs_crypto.c
@@ -0,0 +1,1621 @@
+/*
+ * CDDL HEADER START
+ *
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright (c) 2017, Datto, Inc. All rights reserved.
+ * Copyright 2020 Joyent, Inc.
+ */
+
+#include <sys/zfs_context.h>
+#include <sys/fs/zfs.h>
+#include <sys/dsl_crypt.h>
+#include <libintl.h>
+#include <termios.h>
+#include <signal.h>
+#include <errno.h>
+#include <openssl/evp.h>
+#include <libzfs.h>
+#include "libzfs_impl.h"
+#include "zfeature_common.h"
+
+/*
+ * User keys are used to decrypt the master encryption keys of a dataset. This
+ * indirection allows a user to change his / her access key without having to
+ * re-encrypt the entire dataset. User keys can be provided in one of several
+ * ways. Raw keys are simply given to the kernel as is. Similarly, hex keys
+ * are converted to binary and passed into the kernel. Password based keys are
+ * a bit more complicated. Passwords alone do not provide suitable entropy for
+ * encryption and may be too short or too long to be used. In order to derive
+ * a more appropriate key we use a PBKDF2 function. This function is designed
+ * to take a (relatively) long time to calculate in order to discourage
+ * attackers from guessing from a list of common passwords. PBKDF2 requires
+ * 2 additional parameters. The first is the number of iterations to run, which
+ * will ultimately determine how long it takes to derive the resulting key from
+ * the password. The second parameter is a salt that is randomly generated for
+ * each dataset. The salt is used to "tweak" PBKDF2 such that a group of
+ * attackers cannot reasonably generate a table of commonly known passwords to
+ * their output keys and expect it work for all past and future PBKDF2 users.
+ * We store the salt as a hidden property of the dataset (although it is
+ * technically ok if the salt is known to the attacker).
+ */
+
+typedef enum key_locator {
+ KEY_LOCATOR_NONE,
+ KEY_LOCATOR_PROMPT,
+ KEY_LOCATOR_URI
+} key_locator_t;
+
+#define MIN_PASSPHRASE_LEN 8
+#define MAX_PASSPHRASE_LEN 512
+#define MAX_KEY_PROMPT_ATTEMPTS 3
+
+static int caught_interrupt;
+
+static int get_key_material_file(libzfs_handle_t *, const char *, const char *,
+ zfs_keyformat_t, boolean_t, uint8_t **, size_t *);
+
+static zfs_uri_handler_t uri_handlers[] = {
+ { "file", get_key_material_file },
+ { NULL, NULL }
+};
+
+static int
+pkcs11_get_urandom(uint8_t *buf, size_t bytes)
+{
+ int rand;
+ ssize_t bytes_read = 0;
+
+ rand = open("/dev/urandom", O_RDONLY);
+
+ if (rand < 0)
+ return (rand);
+
+ while (bytes_read < bytes) {
+ ssize_t rc = read(rand, buf + bytes_read, bytes - bytes_read);
+ if (rc < 0)
+ break;
+ bytes_read += rc;
+ }
+
+ (void) close(rand);
+
+ return (bytes_read);
+}
+
+static int
+zfs_prop_parse_keylocation(libzfs_handle_t *restrict hdl, const char *str,
+ zfs_keylocation_t *restrict locp, char **restrict schemep)
+{
+ *locp = ZFS_KEYLOCATION_NONE;
+ *schemep = NULL;
+
+ if (strcmp("prompt", str) == 0) {
+ *locp = ZFS_KEYLOCATION_PROMPT;
+ return (0);
+ }
+
+ regmatch_t pmatch[2];
+
+ if (regexec(&hdl->libzfs_urire, str, ARRAY_SIZE(pmatch),
+ pmatch, 0) == 0) {
+ size_t scheme_len;
+
+ if (pmatch[1].rm_so == -1) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Invalid URI"));
+ return (EINVAL);
+ }
+
+ scheme_len = pmatch[1].rm_eo - pmatch[1].rm_so;
+
+ *schemep = calloc(1, scheme_len + 1);
+ if (*schemep == NULL) {
+ int ret = errno;
+
+ errno = 0;
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Invalid URI"));
+ return (ret);
+ }
+
+ (void) memcpy(*schemep, str + pmatch[1].rm_so, scheme_len);
+ *locp = ZFS_KEYLOCATION_URI;
+ return (0);
+ }
+
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "Invalid keylocation"));
+ return (EINVAL);
+}
+
+static int
+hex_key_to_raw(char *hex, int hexlen, uint8_t *out)
+{
+ int ret, i;
+ unsigned int c;
+
+ for (i = 0; i < hexlen; i += 2) {
+ if (!isxdigit(hex[i]) || !isxdigit(hex[i + 1])) {
+ ret = EINVAL;
+ goto error;
+ }
+
+ ret = sscanf(&hex[i], "%02x", &c);
+ if (ret != 1) {
+ ret = EINVAL;
+ goto error;
+ }
+
+ out[i / 2] = c;
+ }
+
+ return (0);
+
+error:
+ return (ret);
+}
+
+
+static void
+catch_signal(int sig)
+{
+ caught_interrupt = sig;
+}
+
+static const char *
+get_format_prompt_string(zfs_keyformat_t format)
+{
+ switch (format) {
+ case ZFS_KEYFORMAT_RAW:
+ return ("raw key");
+ case ZFS_KEYFORMAT_HEX:
+ return ("hex key");
+ case ZFS_KEYFORMAT_PASSPHRASE:
+ return ("passphrase");
+ default:
+ /* shouldn't happen */
+ return (NULL);
+ }
+}
+
+/* do basic validation of the key material */
+static int
+validate_key(libzfs_handle_t *hdl, zfs_keyformat_t keyformat,
+ const char *key, size_t keylen)
+{
+ switch (keyformat) {
+ case ZFS_KEYFORMAT_RAW:
+ /* verify the key length is correct */
+ if (keylen < WRAPPING_KEY_LEN) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Raw key too short (expected %u)."),
+ WRAPPING_KEY_LEN);
+ return (EINVAL);
+ }
+
+ if (keylen > WRAPPING_KEY_LEN) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Raw key too long (expected %u)."),
+ WRAPPING_KEY_LEN);
+ return (EINVAL);
+ }
+ break;
+ case ZFS_KEYFORMAT_HEX:
+ /* verify the key length is correct */
+ if (keylen < WRAPPING_KEY_LEN * 2) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Hex key too short (expected %u)."),
+ WRAPPING_KEY_LEN * 2);
+ return (EINVAL);
+ }
+
+ if (keylen > WRAPPING_KEY_LEN * 2) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Hex key too long (expected %u)."),
+ WRAPPING_KEY_LEN * 2);
+ return (EINVAL);
+ }
+
+ /* check for invalid hex digits */
+ for (size_t i = 0; i < WRAPPING_KEY_LEN * 2; i++) {
+ if (!isxdigit(key[i])) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Invalid hex character detected."));
+ return (EINVAL);
+ }
+ }
+ break;
+ case ZFS_KEYFORMAT_PASSPHRASE:
+ /* verify the length is within bounds */
+ if (keylen > MAX_PASSPHRASE_LEN) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Passphrase too long (max %u)."),
+ MAX_PASSPHRASE_LEN);
+ return (EINVAL);
+ }
+
+ if (keylen < MIN_PASSPHRASE_LEN) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Passphrase too short (min %u)."),
+ MIN_PASSPHRASE_LEN);
+ return (EINVAL);
+ }
+ break;
+ default:
+ /* can't happen, checked above */
+ break;
+ }
+
+ return (0);
+}
+
+static int
+libzfs_getpassphrase(zfs_keyformat_t keyformat, boolean_t is_reenter,
+ boolean_t new_key, const char *fsname,
+ char **restrict res, size_t *restrict reslen)
+{
+ FILE *f = stdin;
+ size_t buflen = 0;
+ ssize_t bytes;
+ int ret = 0;
+ struct termios old_term, new_term;
+ struct sigaction act, osigint, osigtstp;
+
+ *res = NULL;
+ *reslen = 0;
+
+ /*
+ * handle SIGINT and ignore SIGSTP. This is necessary to
+ * restore the state of the terminal.
+ */
+ caught_interrupt = 0;
+ act.sa_flags = 0;
+ (void) sigemptyset(&act.sa_mask);
+ act.sa_handler = catch_signal;
+
+ (void) sigaction(SIGINT, &act, &osigint);
+ act.sa_handler = SIG_IGN;
+ (void) sigaction(SIGTSTP, &act, &osigtstp);
+
+ (void) printf("%s %s%s",
+ is_reenter ? "Re-enter" : "Enter",
+ new_key ? "new " : "",
+ get_format_prompt_string(keyformat));
+ if (fsname != NULL)
+ (void) printf(" for '%s'", fsname);
+ (void) fputc(':', stdout);
+ (void) fflush(stdout);
+
+ /* disable the terminal echo for key input */
+ (void) tcgetattr(fileno(f), &old_term);
+
+ new_term = old_term;
+ new_term.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
+
+ ret = tcsetattr(fileno(f), TCSAFLUSH, &new_term);
+ if (ret != 0) {
+ ret = errno;
+ errno = 0;
+ goto out;
+ }
+
+ bytes = getline(res, &buflen, f);
+ if (bytes < 0) {
+ ret = errno;
+ errno = 0;
+ goto out;
+ }
+
+ /* trim the ending newline if it exists */
+ if (bytes > 0 && (*res)[bytes - 1] == '\n') {
+ (*res)[bytes - 1] = '\0';
+ bytes--;
+ }
+
+ *reslen = bytes;
+
+out:
+ /* reset the terminal */
+ (void) tcsetattr(fileno(f), TCSAFLUSH, &old_term);
+ (void) sigaction(SIGINT, &osigint, NULL);
+ (void) sigaction(SIGTSTP, &osigtstp, NULL);
+
+ /* if we caught a signal, re-throw it now */
+ if (caught_interrupt != 0)
+ (void) kill(getpid(), caught_interrupt);
+
+ /* print the newline that was not echo'd */
+ (void) printf("\n");
+
+ return (ret);
+}
+
+static int
+get_key_interactive(libzfs_handle_t *restrict hdl, const char *fsname,
+ zfs_keyformat_t keyformat, boolean_t confirm_key, boolean_t newkey,
+ uint8_t **restrict outbuf, size_t *restrict len_out)
+{
+ char *buf = NULL, *buf2 = NULL;
+ size_t buflen = 0, buf2len = 0;
+ int ret = 0;
+
+ ASSERT(isatty(fileno(stdin)));
+
+ /* raw keys cannot be entered on the terminal */
+ if (keyformat == ZFS_KEYFORMAT_RAW) {
+ ret = EINVAL;
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Cannot enter raw keys on the terminal"));
+ goto out;
+ }
+
+ /* prompt for the key */
+ if ((ret = libzfs_getpassphrase(keyformat, B_FALSE, newkey, fsname,
+ &buf, &buflen)) != 0) {
+ free(buf);
+ buf = NULL;
+ buflen = 0;
+ goto out;
+ }
+
+ if (!confirm_key)
+ goto out;
+
+ if ((ret = validate_key(hdl, keyformat, buf, buflen)) != 0) {
+ free(buf);
+ return (ret);
+ }
+
+ ret = libzfs_getpassphrase(keyformat, B_TRUE, newkey, fsname, &buf2,
+ &buf2len);
+ if (ret != 0) {
+ free(buf);
+ free(buf2);
+ buf = buf2 = NULL;
+ buflen = buf2len = 0;
+ goto out;
+ }
+
+ if (buflen != buf2len || strcmp(buf, buf2) != 0) {
+ free(buf);
+ buf = NULL;
+ buflen = 0;
+
+ ret = EINVAL;
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Provided keys do not match."));
+ }
+
+ free(buf2);
+
+out:
+ *outbuf = (uint8_t *)buf;
+ *len_out = buflen;
+ return (ret);
+}
+
+static int
+get_key_material_raw(FILE *fd, zfs_keyformat_t keyformat,
+ uint8_t **buf, size_t *len_out)
+{
+ int ret = 0;
+ size_t buflen = 0;
+
+ *len_out = 0;
+
+ /* read the key material */
+ if (keyformat != ZFS_KEYFORMAT_RAW) {
+ ssize_t bytes;
+
+ bytes = getline((char **)buf, &buflen, fd);
+ if (bytes < 0) {
+ ret = errno;
+ errno = 0;
+ goto out;
+ }
+
+ /* trim the ending newline if it exists */
+ if (bytes > 0 && (*buf)[bytes - 1] == '\n') {
+ (*buf)[bytes - 1] = '\0';
+ bytes--;
+ }
+
+ *len_out = bytes;
+ } else {
+ size_t n;
+
+ /*
+ * Raw keys may have newline characters in them and so can't
+ * use getline(). Here we attempt to read 33 bytes so that we
+ * can properly check the key length (the file should only have
+ * 32 bytes).
+ */
+ *buf = malloc((WRAPPING_KEY_LEN + 1) * sizeof (uint8_t));
+ if (*buf == NULL) {
+ ret = ENOMEM;
+ goto out;
+ }
+
+ n = fread(*buf, 1, WRAPPING_KEY_LEN + 1, fd);
+ if (n == 0 || ferror(fd)) {
+ /* size errors are handled by the calling function */
+ free(*buf);
+ *buf = NULL;
+ ret = errno;
+ errno = 0;
+ goto out;
+ }
+
+ *len_out = n;
+ }
+out:
+ return (ret);
+}
+
+static int
+get_key_material_file(libzfs_handle_t *hdl, const char *uri,
+ const char *fsname, zfs_keyformat_t keyformat, boolean_t newkey,
+ uint8_t **restrict buf, size_t *restrict len_out)
+{
+ FILE *f = NULL;
+ int ret = 0;
+
+ if (strlen(uri) < 7)
+ return (EINVAL);
+
+ if ((f = fopen(uri + 7, "r")) == NULL) {
+ ret = errno;
+ errno = 0;
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Failed to open key material file"));
+ return (ret);
+ }
+
+ ret = get_key_material_raw(f, keyformat, buf, len_out);
+
+ (void) fclose(f);
+
+ return (ret);
+}
+
+/*
+ * Attempts to fetch key material, no matter where it might live. The key
+ * material is allocated and returned in km_out. *can_retry_out will be set
+ * to B_TRUE if the user is providing the key material interactively, allowing
+ * for re-entry attempts.
+ */
+static int
+get_key_material(libzfs_handle_t *hdl, boolean_t do_verify, boolean_t newkey,
+ zfs_keyformat_t keyformat, char *keylocation, const char *fsname,
+ uint8_t **km_out, size_t *kmlen_out, boolean_t *can_retry_out)
+{
+ int ret;
+ zfs_keylocation_t keyloc = ZFS_KEYLOCATION_NONE;
+ uint8_t *km = NULL;
+ size_t kmlen = 0;
+ char *uri_scheme = NULL;
+ zfs_uri_handler_t *handler = NULL;
+ boolean_t can_retry = B_FALSE;
+
+ /* verify and parse the keylocation */
+ ret = zfs_prop_parse_keylocation(hdl, keylocation, &keyloc,
+ &uri_scheme);
+ if (ret != 0)
+ goto error;
+
+ /* open the appropriate file descriptor */
+ switch (keyloc) {
+ case ZFS_KEYLOCATION_PROMPT:
+ if (isatty(fileno(stdin))) {
+ can_retry = B_TRUE;
+ ret = get_key_interactive(hdl, fsname, keyformat,
+ do_verify, newkey, &km, &kmlen);
+ } else {
+ /* fetch the key material into the buffer */
+ ret = get_key_material_raw(stdin, keyformat, &km,
+ &kmlen);
+ }
+
+ if (ret != 0)
+ goto error;
+
+ break;
+ case ZFS_KEYLOCATION_URI:
+ for (handler = uri_handlers; handler->zuh_scheme != NULL;
+ handler++) {
+ if (strcmp(handler->zuh_scheme, uri_scheme) != 0)
+ continue;
+
+ if ((ret = handler->zuh_handler(hdl, keylocation,
+ fsname, keyformat, newkey, &km, &kmlen)) != 0)
+ goto error;
+
+ break;
+ }
+
+ ret = ENOTSUP;
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "URI scheme is not supported"));
+
+ break;
+ default:
+ ret = EINVAL;
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Invalid keylocation."));
+ goto error;
+ }
+
+ if ((ret = validate_key(hdl, keyformat, (const char *)km, kmlen)) != 0)
+ goto error;
+
+ *km_out = km;
+ *kmlen_out = kmlen;
+ if (can_retry_out != NULL)
+ *can_retry_out = can_retry;
+
+ free(uri_scheme);
+ return (0);
+
+error:
+ free(km);
+
+ *km_out = NULL;
+ *kmlen_out = 0;
+
+ if (can_retry_out != NULL)
+ *can_retry_out = can_retry;
+
+ free(uri_scheme);
+ return (ret);
+}
+
+static int
+derive_key(libzfs_handle_t *hdl, zfs_keyformat_t format, uint64_t iters,
+ uint8_t *key_material, size_t key_material_len, uint64_t salt,
+ uint8_t **key_out)
+{
+ int ret;
+ uint8_t *key;
+
+ *key_out = NULL;
+
+ key = zfs_alloc(hdl, WRAPPING_KEY_LEN);
+ if (!key)
+ return (ENOMEM);
+
+ switch (format) {
+ case ZFS_KEYFORMAT_RAW:
+ bcopy(key_material, key, WRAPPING_KEY_LEN);
+ break;
+ case ZFS_KEYFORMAT_HEX:
+ ret = hex_key_to_raw((char *)key_material,
+ WRAPPING_KEY_LEN * 2, key);
+ if (ret != 0) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Invalid hex key provided."));
+ goto error;
+ }
+ break;
+ case ZFS_KEYFORMAT_PASSPHRASE:
+ salt = LE_64(salt);
+
+ ret = PKCS5_PBKDF2_HMAC_SHA1((char *)key_material,
+ strlen((char *)key_material), ((uint8_t *)&salt),
+ sizeof (uint64_t), iters, WRAPPING_KEY_LEN, key);
+ if (ret != 1) {
+ ret = EIO;
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Failed to generate key from passphrase."));
+ goto error;
+ }
+ break;
+ default:
+ ret = EINVAL;
+ goto error;
+ }
+
+ *key_out = key;
+ return (0);
+
+error:
+ free(key);
+
+ *key_out = NULL;
+ return (ret);
+}
+
+static boolean_t
+encryption_feature_is_enabled(zpool_handle_t *zph)
+{
+ nvlist_t *features;
+ uint64_t feat_refcount;
+
+ /* check that features can be enabled */
+ if (zpool_get_prop_int(zph, ZPOOL_PROP_VERSION, NULL)
+ < SPA_VERSION_FEATURES)
+ return (B_FALSE);
+
+ /* check for crypto feature */
+ features = zpool_get_features(zph);
+ if (!features || nvlist_lookup_uint64(features,
+ spa_feature_table[SPA_FEATURE_ENCRYPTION].fi_guid,
+ &feat_refcount) != 0)
+ return (B_FALSE);
+
+ return (B_TRUE);
+}
+
+static int
+populate_create_encryption_params_nvlists(libzfs_handle_t *hdl,
+ zfs_handle_t *zhp, boolean_t newkey, zfs_keyformat_t keyformat,
+ char *keylocation, nvlist_t *props, uint8_t **wkeydata, uint_t *wkeylen)
+{
+ int ret;
+ uint64_t iters = 0, salt = 0;
+ uint8_t *key_material = NULL;
+ size_t key_material_len = 0;
+ uint8_t *key_data = NULL;
+ const char *fsname = (zhp) ? zfs_get_name(zhp) : NULL;
+
+ /* get key material from keyformat and keylocation */
+ ret = get_key_material(hdl, B_TRUE, newkey, keyformat, keylocation,
+ fsname, &key_material, &key_material_len, NULL);
+ if (ret != 0)
+ goto error;
+
+ /* passphrase formats require a salt and pbkdf2 iters property */
+ if (keyformat == ZFS_KEYFORMAT_PASSPHRASE) {
+ /* always generate a new salt */
+ ret = pkcs11_get_urandom((uint8_t *)&salt, sizeof (uint64_t));
+ if (ret != sizeof (uint64_t)) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Failed to generate salt."));
+ goto error;
+ }
+
+ ret = nvlist_add_uint64(props,
+ zfs_prop_to_name(ZFS_PROP_PBKDF2_SALT), salt);
+ if (ret != 0) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Failed to add salt to properties."));
+ goto error;
+ }
+
+ /*
+ * If not otherwise specified, use the default number of
+ * pbkdf2 iterations. If specified, we have already checked
+ * that the given value is greater than MIN_PBKDF2_ITERATIONS
+ * during zfs_valid_proplist().
+ */
+ ret = nvlist_lookup_uint64(props,
+ zfs_prop_to_name(ZFS_PROP_PBKDF2_ITERS), &iters);
+ if (ret == ENOENT) {
+ iters = DEFAULT_PBKDF2_ITERATIONS;
+ ret = nvlist_add_uint64(props,
+ zfs_prop_to_name(ZFS_PROP_PBKDF2_ITERS), iters);
+ if (ret != 0)
+ goto error;
+ } else if (ret != 0) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Failed to get pbkdf2 iterations."));
+ goto error;
+ }
+ } else {
+ /* check that pbkdf2iters was not specified by the user */
+ ret = nvlist_lookup_uint64(props,
+ zfs_prop_to_name(ZFS_PROP_PBKDF2_ITERS), &iters);
+ if (ret == 0) {
+ ret = EINVAL;
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Cannot specify pbkdf2iters with a non-passphrase "
+ "keyformat."));
+ goto error;
+ }
+ }
+
+ /* derive a key from the key material */
+ ret = derive_key(hdl, keyformat, iters, key_material, key_material_len,
+ salt, &key_data);
+ if (ret != 0)
+ goto error;
+
+ free(key_material);
+
+ *wkeydata = key_data;
+ *wkeylen = WRAPPING_KEY_LEN;
+ return (0);
+
+error:
+ if (key_material != NULL)
+ free(key_material);
+ if (key_data != NULL)
+ free(key_data);
+
+ *wkeydata = NULL;
+ *wkeylen = 0;
+ return (ret);
+}
+
+static boolean_t
+proplist_has_encryption_props(nvlist_t *props)
+{
+ int ret;
+ uint64_t intval;
+ char *strval;
+
+ ret = nvlist_lookup_uint64(props,
+ zfs_prop_to_name(ZFS_PROP_ENCRYPTION), &intval);
+ if (ret == 0 && intval != ZIO_CRYPT_OFF)
+ return (B_TRUE);
+
+ ret = nvlist_lookup_string(props,
+ zfs_prop_to_name(ZFS_PROP_KEYLOCATION), &strval);
+ if (ret == 0 && strcmp(strval, "none") != 0)
+ return (B_TRUE);
+
+ ret = nvlist_lookup_uint64(props,
+ zfs_prop_to_name(ZFS_PROP_KEYFORMAT), &intval);
+ if (ret == 0)
+ return (B_TRUE);
+
+ ret = nvlist_lookup_uint64(props,
+ zfs_prop_to_name(ZFS_PROP_PBKDF2_ITERS), &intval);
+ if (ret == 0)
+ return (B_TRUE);
+
+ return (B_FALSE);
+}
+
+int
+zfs_crypto_get_encryption_root(zfs_handle_t *zhp, boolean_t *is_encroot,
+ char *buf)
+{
+ int ret;
+ char prop_encroot[MAXNAMELEN];
+
+ /* if the dataset isn't encrypted, just return */
+ if (zfs_prop_get_int(zhp, ZFS_PROP_ENCRYPTION) == ZIO_CRYPT_OFF) {
+ *is_encroot = B_FALSE;
+ if (buf != NULL)
+ buf[0] = '\0';
+ return (0);
+ }
+
+ ret = zfs_prop_get(zhp, ZFS_PROP_ENCRYPTION_ROOT, prop_encroot,
+ sizeof (prop_encroot), NULL, NULL, 0, B_TRUE);
+ if (ret != 0) {
+ *is_encroot = B_FALSE;
+ if (buf != NULL)
+ buf[0] = '\0';
+ return (ret);
+ }
+
+ *is_encroot = strcmp(prop_encroot, zfs_get_name(zhp)) == 0;
+ if (buf != NULL)
+ strcpy(buf, prop_encroot);
+
+ return (0);
+}
+
+int
+zfs_crypto_create(libzfs_handle_t *hdl, char *parent_name, nvlist_t *props,
+ nvlist_t *pool_props, boolean_t stdin_available, uint8_t **wkeydata_out,
+ uint_t *wkeylen_out)
+{
+ int ret;
+ char errbuf[1024];
+ uint64_t crypt = ZIO_CRYPT_INHERIT, pcrypt = ZIO_CRYPT_INHERIT;
+ uint64_t keyformat = ZFS_KEYFORMAT_NONE;
+ char *keylocation = NULL;
+ zfs_handle_t *pzhp = NULL;
+ uint8_t *wkeydata = NULL;
+ uint_t wkeylen = 0;
+ boolean_t local_crypt = B_TRUE;
+
+ (void) snprintf(errbuf, sizeof (errbuf),
+ dgettext(TEXT_DOMAIN, "Encryption create error"));
+
+ /* lookup crypt from props */
+ ret = nvlist_lookup_uint64(props,
+ zfs_prop_to_name(ZFS_PROP_ENCRYPTION), &crypt);
+ if (ret != 0)
+ local_crypt = B_FALSE;
+
+ /* lookup key location and format from props */
+ (void) nvlist_lookup_uint64(props,
+ zfs_prop_to_name(ZFS_PROP_KEYFORMAT), &keyformat);
+ (void) nvlist_lookup_string(props,
+ zfs_prop_to_name(ZFS_PROP_KEYLOCATION), &keylocation);
+
+ if (parent_name != NULL) {
+ /* get a reference to parent dataset */
+ pzhp = make_dataset_handle(hdl, parent_name);
+ if (pzhp == NULL) {
+ ret = ENOENT;
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Failed to lookup parent."));
+ goto out;
+ }
+
+ /* Lookup parent's crypt */
+ pcrypt = zfs_prop_get_int(pzhp, ZFS_PROP_ENCRYPTION);
+
+ /* Params require the encryption feature */
+ if (!encryption_feature_is_enabled(pzhp->zpool_hdl)) {
+ if (proplist_has_encryption_props(props)) {
+ ret = EINVAL;
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Encryption feature not enabled."));
+ goto out;
+ }
+
+ ret = 0;
+ goto out;
+ }
+ } else {
+ /*
+ * special case for root dataset where encryption feature
+ * feature won't be on disk yet
+ */
+ if (!nvlist_exists(pool_props, "feature@encryption")) {
+ if (proplist_has_encryption_props(props)) {
+ ret = EINVAL;
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Encryption feature not enabled."));
+ goto out;
+ }
+
+ ret = 0;
+ goto out;
+ }
+
+ pcrypt = ZIO_CRYPT_OFF;
+ }
+
+ /* Get the inherited encryption property if we don't have it locally */
+ if (!local_crypt)
+ crypt = pcrypt;
+
+ /*
+ * At this point crypt should be the actual encryption value. If
+ * encryption is off just verify that no encryption properties have
+ * been specified and return.
+ */
+ if (crypt == ZIO_CRYPT_OFF) {
+ if (proplist_has_encryption_props(props)) {
+ ret = EINVAL;
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Encryption must be turned on to set encryption "
+ "properties."));
+ goto out;
+ }
+
+ ret = 0;
+ goto out;
+ }
+
+ /*
+ * If we have a parent crypt it is valid to specify encryption alone.
+ * This will result in a child that is encrypted with the chosen
+ * encryption suite that will also inherit the parent's key. If
+ * the parent is not encrypted we need an encryption suite provided.
+ */
+ if (pcrypt == ZIO_CRYPT_OFF && keylocation == NULL &&
+ keyformat == ZFS_KEYFORMAT_NONE) {
+ ret = EINVAL;
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Keyformat required for new encryption root."));
+ goto out;
+ }
+
+ /*
+ * Specifying a keylocation implies this will be a new encryption root.
+ * Check that a keyformat is also specified.
+ */
+ if (keylocation != NULL && keyformat == ZFS_KEYFORMAT_NONE) {
+ ret = EINVAL;
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Keyformat required for new encryption root."));
+ goto out;
+ }
+
+ /* default to prompt if no keylocation is specified */
+ if (keyformat != ZFS_KEYFORMAT_NONE && keylocation == NULL) {
+ keylocation = "prompt";
+ ret = nvlist_add_string(props,
+ zfs_prop_to_name(ZFS_PROP_KEYLOCATION), keylocation);
+ if (ret != 0)
+ goto out;
+ }
+
+ /*
+ * If a local key is provided, this dataset will be a new
+ * encryption root. Populate the encryption params.
+ */
+ if (keylocation != NULL) {
+ /*
+ * 'zfs recv -o keylocation=prompt' won't work because stdin
+ * is being used by the send stream, so we disallow it.
+ */
+ if (!stdin_available && strcmp(keylocation, "prompt") == 0) {
+ ret = EINVAL;
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "Cannot use "
+ "'prompt' keylocation because stdin is in use."));
+ goto out;
+ }
+
+ ret = populate_create_encryption_params_nvlists(hdl, NULL,
+ B_FALSE, keyformat, keylocation, props, &wkeydata,
+ &wkeylen);
+ if (ret != 0)
+ goto out;
+ }
+
+ if (pzhp != NULL)
+ zfs_close(pzhp);
+
+ *wkeydata_out = wkeydata;
+ *wkeylen_out = wkeylen;
+ return (0);
+
+out:
+ if (pzhp != NULL)
+ zfs_close(pzhp);
+ if (wkeydata != NULL)
+ free(wkeydata);
+
+ *wkeydata_out = NULL;
+ *wkeylen_out = 0;
+ return (ret);
+}
+
+int
+zfs_crypto_clone_check(libzfs_handle_t *hdl, zfs_handle_t *origin_zhp,
+ char *parent_name, nvlist_t *props)
+{
+ char errbuf[1024];
+
+ (void) snprintf(errbuf, sizeof (errbuf),
+ dgettext(TEXT_DOMAIN, "Encryption clone error"));
+
+ /*
+ * No encryption properties should be specified. They will all be
+ * inherited from the origin dataset.
+ */
+ if (nvlist_exists(props, zfs_prop_to_name(ZFS_PROP_KEYFORMAT)) ||
+ nvlist_exists(props, zfs_prop_to_name(ZFS_PROP_KEYLOCATION)) ||
+ nvlist_exists(props, zfs_prop_to_name(ZFS_PROP_ENCRYPTION)) ||
+ nvlist_exists(props, zfs_prop_to_name(ZFS_PROP_PBKDF2_ITERS))) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Encryption properties must inherit from origin dataset."));
+ return (EINVAL);
+ }
+
+ return (0);
+}
+
+typedef struct loadkeys_cbdata {
+ uint64_t cb_numfailed;
+ uint64_t cb_numattempted;
+} loadkey_cbdata_t;
+
+static int
+load_keys_cb(zfs_handle_t *zhp, void *arg)
+{
+ int ret;
+ boolean_t is_encroot;
+ loadkey_cbdata_t *cb = arg;
+ uint64_t keystatus = zfs_prop_get_int(zhp, ZFS_PROP_KEYSTATUS);
+
+ /* only attempt to load keys for encryption roots */
+ ret = zfs_crypto_get_encryption_root(zhp, &is_encroot, NULL);
+ if (ret != 0 || !is_encroot)
+ goto out;
+
+ /* don't attempt to load already loaded keys */
+ if (keystatus == ZFS_KEYSTATUS_AVAILABLE)
+ goto out;
+
+ /* Attempt to load the key. Record status in cb. */
+ cb->cb_numattempted++;
+
+ ret = zfs_crypto_load_key(zhp, B_FALSE, NULL);
+ if (ret)
+ cb->cb_numfailed++;
+
+out:
+ (void) zfs_iter_filesystems(zhp, load_keys_cb, cb);
+ zfs_close(zhp);
+
+ /* always return 0, since this function is best effort */
+ return (0);
+}
+
+/*
+ * This function is best effort. It attempts to load all the keys for the given
+ * filesystem and all of its children.
+ */
+int
+zfs_crypto_attempt_load_keys(libzfs_handle_t *hdl, char *fsname)
+{
+ int ret;
+ zfs_handle_t *zhp = NULL;
+ loadkey_cbdata_t cb = { 0 };
+
+ zhp = zfs_open(hdl, fsname, ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME);
+ if (zhp == NULL) {
+ ret = ENOENT;
+ goto error;
+ }
+
+ ret = load_keys_cb(zfs_handle_dup(zhp), &cb);
+ if (ret)
+ goto error;
+
+ (void) printf(gettext("%llu / %llu keys successfully loaded\n"),
+ (u_longlong_t)(cb.cb_numattempted - cb.cb_numfailed),
+ (u_longlong_t)cb.cb_numattempted);
+
+ if (cb.cb_numfailed != 0) {
+ ret = -1;
+ goto error;
+ }
+
+ zfs_close(zhp);
+ return (0);
+
+error:
+ if (zhp != NULL)
+ zfs_close(zhp);
+ return (ret);
+}
+
+int
+zfs_crypto_load_key(zfs_handle_t *zhp, boolean_t noop, char *alt_keylocation)
+{
+ int ret, attempts = 0;
+ char errbuf[1024];
+ uint64_t keystatus, iters = 0, salt = 0;
+ uint64_t keyformat = ZFS_KEYFORMAT_NONE;
+ char prop_keylocation[MAXNAMELEN];
+ char prop_encroot[MAXNAMELEN];
+ char *keylocation = NULL;
+ uint8_t *key_material = NULL, *key_data = NULL;
+ size_t key_material_len;
+ boolean_t is_encroot, can_retry = B_FALSE, correctible = B_FALSE;
+
+ (void) snprintf(errbuf, sizeof (errbuf),
+ dgettext(TEXT_DOMAIN, "Key load error"));
+
+ /* check that encryption is enabled for the pool */
+ if (!encryption_feature_is_enabled(zhp->zpool_hdl)) {
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "Encryption feature not enabled."));
+ ret = EINVAL;
+ goto error;
+ }
+
+ /* Fetch the keyformat. Check that the dataset is encrypted. */
+ keyformat = zfs_prop_get_int(zhp, ZFS_PROP_KEYFORMAT);
+ if (keyformat == ZFS_KEYFORMAT_NONE) {
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "'%s' is not encrypted."), zfs_get_name(zhp));
+ ret = EINVAL;
+ goto error;
+ }
+
+ /*
+ * Fetch the key location. Check that we are working with an
+ * encryption root.
+ */
+ ret = zfs_crypto_get_encryption_root(zhp, &is_encroot, prop_encroot);
+ if (ret != 0) {
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "Failed to get encryption root for '%s'."),
+ zfs_get_name(zhp));
+ goto error;
+ } else if (!is_encroot) {
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "Keys must be loaded for encryption root of '%s' (%s)."),
+ zfs_get_name(zhp), prop_encroot);
+ ret = EINVAL;
+ goto error;
+ }
+
+ /*
+ * if the caller has elected to override the keylocation property
+ * use that instead
+ */
+ if (alt_keylocation != NULL) {
+ keylocation = alt_keylocation;
+ } else {
+ ret = zfs_prop_get(zhp, ZFS_PROP_KEYLOCATION, prop_keylocation,
+ sizeof (prop_keylocation), NULL, NULL, 0, B_TRUE);
+ if (ret != 0) {
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "Failed to get keylocation for '%s'."),
+ zfs_get_name(zhp));
+ goto error;
+ }
+
+ keylocation = prop_keylocation;
+ }
+
+ /* check that the key is unloaded unless this is a noop */
+ if (!noop) {
+ keystatus = zfs_prop_get_int(zhp, ZFS_PROP_KEYSTATUS);
+ if (keystatus == ZFS_KEYSTATUS_AVAILABLE) {
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "Key already loaded for '%s'."), zfs_get_name(zhp));
+ ret = EEXIST;
+ goto error;
+ }
+ }
+
+ /* passphrase formats require a salt and pbkdf2_iters property */
+ if (keyformat == ZFS_KEYFORMAT_PASSPHRASE) {
+ salt = zfs_prop_get_int(zhp, ZFS_PROP_PBKDF2_SALT);
+ iters = zfs_prop_get_int(zhp, ZFS_PROP_PBKDF2_ITERS);
+ }
+
+try_again:
+ /* fetching and deriving the key are correctable errors. set the flag */
+ correctible = B_TRUE;
+
+ /* get key material from key format and location */
+ ret = get_key_material(zhp->zfs_hdl, B_FALSE, B_FALSE, keyformat,
+ keylocation, zfs_get_name(zhp), &key_material, &key_material_len,
+ &can_retry);
+ if (ret != 0)
+ goto error;
+
+ /* derive a key from the key material */
+ ret = derive_key(zhp->zfs_hdl, keyformat, iters, key_material,
+ key_material_len, salt, &key_data);
+ if (ret != 0)
+ goto error;
+
+ correctible = B_FALSE;
+
+ /* pass the wrapping key and noop flag to the ioctl */
+ ret = lzc_load_key(zhp->zfs_name, noop, key_data, WRAPPING_KEY_LEN);
+ if (ret != 0) {
+ switch (ret) {
+ case EPERM:
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "Permission denied."));
+ break;
+ case EINVAL:
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "Invalid parameters provided for dataset %s."),
+ zfs_get_name(zhp));
+ break;
+ case EEXIST:
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "Key already loaded for '%s'."), zfs_get_name(zhp));
+ break;
+ case EBUSY:
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "'%s' is busy."), zfs_get_name(zhp));
+ break;
+ case EACCES:
+ correctible = B_TRUE;
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "Incorrect key provided for '%s'."),
+ zfs_get_name(zhp));
+ break;
+ }
+ goto error;
+ }
+
+ free(key_material);
+ free(key_data);
+
+ return (0);
+
+error:
+ zfs_error(zhp->zfs_hdl, EZFS_CRYPTOFAILED, errbuf);
+ if (key_material != NULL) {
+ free(key_material);
+ key_material = NULL;
+ }
+ if (key_data != NULL) {
+ free(key_data);
+ key_data = NULL;
+ }
+
+ /*
+ * Here we decide if it is ok to allow the user to retry entering their
+ * key. The can_retry flag will be set if the user is entering their
+ * key from an interactive prompt. The correctable flag will only be
+ * set if an error that occurred could be corrected by retrying. Both
+ * flags are needed to allow the user to attempt key entry again
+ */
+ attempts++;
+ if (can_retry && correctible && attempts < MAX_KEY_PROMPT_ATTEMPTS)
+ goto try_again;
+
+ return (ret);
+}
+
+int
+zfs_crypto_unload_key(zfs_handle_t *zhp)
+{
+ int ret;
+ char errbuf[1024];
+ char prop_encroot[MAXNAMELEN];
+ uint64_t keystatus, keyformat;
+ boolean_t is_encroot;
+
+ (void) snprintf(errbuf, sizeof (errbuf),
+ dgettext(TEXT_DOMAIN, "Key unload error"));
+
+ /* check that encryption is enabled for the pool */
+ if (!encryption_feature_is_enabled(zhp->zpool_hdl)) {
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "Encryption feature not enabled."));
+ ret = EINVAL;
+ goto error;
+ }
+
+ /* Fetch the keyformat. Check that the dataset is encrypted. */
+ keyformat = zfs_prop_get_int(zhp, ZFS_PROP_KEYFORMAT);
+ if (keyformat == ZFS_KEYFORMAT_NONE) {
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "'%s' is not encrypted."), zfs_get_name(zhp));
+ ret = EINVAL;
+ goto error;
+ }
+
+ /*
+ * Fetch the key location. Check that we are working with an
+ * encryption root.
+ */
+ ret = zfs_crypto_get_encryption_root(zhp, &is_encroot, prop_encroot);
+ if (ret != 0) {
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "Failed to get encryption root for '%s'."),
+ zfs_get_name(zhp));
+ goto error;
+ } else if (!is_encroot) {
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "Keys must be unloaded for encryption root of '%s' (%s)."),
+ zfs_get_name(zhp), prop_encroot);
+ ret = EINVAL;
+ goto error;
+ }
+
+ /* check that the key is loaded */
+ keystatus = zfs_prop_get_int(zhp, ZFS_PROP_KEYSTATUS);
+ if (keystatus == ZFS_KEYSTATUS_UNAVAILABLE) {
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "Key already unloaded for '%s'."), zfs_get_name(zhp));
+ ret = EACCES;
+ goto error;
+ }
+
+ /* call the ioctl */
+ ret = lzc_unload_key(zhp->zfs_name);
+
+ if (ret != 0) {
+ switch (ret) {
+ case EPERM:
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "Permission denied."));
+ break;
+ case EACCES:
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "Key already unloaded for '%s'."),
+ zfs_get_name(zhp));
+ break;
+ case EBUSY:
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "'%s' is busy."), zfs_get_name(zhp));
+ break;
+ }
+ zfs_error(zhp->zfs_hdl, EZFS_CRYPTOFAILED, errbuf);
+ }
+
+ return (ret);
+
+error:
+ zfs_error(zhp->zfs_hdl, EZFS_CRYPTOFAILED, errbuf);
+ return (ret);
+}
+
+static int
+zfs_crypto_verify_rewrap_nvlist(zfs_handle_t *zhp, nvlist_t *props,
+ nvlist_t **props_out, char *errbuf)
+{
+ int ret;
+ nvpair_t *elem = NULL;
+ zfs_prop_t prop;
+ nvlist_t *new_props = NULL;
+
+ new_props = fnvlist_alloc();
+
+ /*
+ * loop through all provided properties, we should only have
+ * keyformat, keylocation and pbkdf2iters. The actual validation of
+ * values is done by zfs_valid_proplist().
+ */
+ while ((elem = nvlist_next_nvpair(props, elem)) != NULL) {
+ const char *propname = nvpair_name(elem);
+ prop = zfs_name_to_prop(propname);
+
+ switch (prop) {
+ case ZFS_PROP_PBKDF2_ITERS:
+ case ZFS_PROP_KEYFORMAT:
+ case ZFS_PROP_KEYLOCATION:
+ break;
+ default:
+ ret = EINVAL;
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "Only keyformat, keylocation and pbkdf2iters may "
+ "be set with this command."));
+ goto error;
+ }
+ }
+
+ new_props = zfs_valid_proplist(zhp->zfs_hdl, zhp->zfs_type, props,
+ zfs_prop_get_int(zhp, ZFS_PROP_ZONED), NULL, zhp->zpool_hdl,
+ B_TRUE, errbuf);
+ if (new_props == NULL) {
+ ret = EINVAL;
+ goto error;
+ }
+
+ *props_out = new_props;
+ return (0);
+
+error:
+ nvlist_free(new_props);
+ *props_out = NULL;
+ return (ret);
+}
+
+int
+zfs_crypto_rewrap(zfs_handle_t *zhp, nvlist_t *raw_props, boolean_t inheritkey)
+{
+ int ret;
+ char errbuf[1024];
+ boolean_t is_encroot;
+ nvlist_t *props = NULL;
+ uint8_t *wkeydata = NULL;
+ uint_t wkeylen = 0;
+ dcp_cmd_t cmd = (inheritkey) ? DCP_CMD_INHERIT : DCP_CMD_NEW_KEY;
+ uint64_t crypt, pcrypt, keystatus, pkeystatus;
+ uint64_t keyformat = ZFS_KEYFORMAT_NONE;
+ zfs_handle_t *pzhp = NULL;
+ char *keylocation = NULL;
+ char origin_name[MAXNAMELEN];
+ char prop_keylocation[MAXNAMELEN];
+ char parent_name[ZFS_MAX_DATASET_NAME_LEN];
+
+ (void) snprintf(errbuf, sizeof (errbuf),
+ dgettext(TEXT_DOMAIN, "Key change error"));
+
+ /* check that encryption is enabled for the pool */
+ if (!encryption_feature_is_enabled(zhp->zpool_hdl)) {
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "Encryption feature not enabled."));
+ ret = EINVAL;
+ goto error;
+ }
+
+ /* get crypt from dataset */
+ crypt = zfs_prop_get_int(zhp, ZFS_PROP_ENCRYPTION);
+ if (crypt == ZIO_CRYPT_OFF) {
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "Dataset not encrypted."));
+ ret = EINVAL;
+ goto error;
+ }
+
+ /* get the encryption root of the dataset */
+ ret = zfs_crypto_get_encryption_root(zhp, &is_encroot, NULL);
+ if (ret != 0) {
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "Failed to get encryption root for '%s'."),
+ zfs_get_name(zhp));
+ goto error;
+ }
+
+ /* Clones use their origin's key and cannot rewrap it */
+ ret = zfs_prop_get(zhp, ZFS_PROP_ORIGIN, origin_name,
+ sizeof (origin_name), NULL, NULL, 0, B_TRUE);
+ if (ret == 0 && strcmp(origin_name, "") != 0) {
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "Keys cannot be changed on clones."));
+ ret = EINVAL;
+ goto error;
+ }
+
+ /*
+ * If the user wants to use the inheritkey variant of this function
+ * we don't need to collect any crypto arguments.
+ */
+ if (!inheritkey) {
+ /* validate the provided properties */
+ ret = zfs_crypto_verify_rewrap_nvlist(zhp, raw_props, &props,
+ errbuf);
+ if (ret != 0)
+ goto error;
+
+ /*
+ * Load keyformat and keylocation from the nvlist. Fetch from
+ * the dataset properties if not specified.
+ */
+ (void) nvlist_lookup_uint64(props,
+ zfs_prop_to_name(ZFS_PROP_KEYFORMAT), &keyformat);
+ (void) nvlist_lookup_string(props,
+ zfs_prop_to_name(ZFS_PROP_KEYLOCATION), &keylocation);
+
+ if (is_encroot) {
+ /*
+ * If this is already an encryption root, just keep
+ * any properties not set by the user.
+ */
+ if (keyformat == ZFS_KEYFORMAT_NONE) {
+ keyformat = zfs_prop_get_int(zhp,
+ ZFS_PROP_KEYFORMAT);
+ ret = nvlist_add_uint64(props,
+ zfs_prop_to_name(ZFS_PROP_KEYFORMAT),
+ keyformat);
+ if (ret != 0) {
+ zfs_error_aux(zhp->zfs_hdl,
+ dgettext(TEXT_DOMAIN, "Failed to "
+ "get existing keyformat "
+ "property."));
+ goto error;
+ }
+ }
+
+ if (keylocation == NULL) {
+ ret = zfs_prop_get(zhp, ZFS_PROP_KEYLOCATION,
+ prop_keylocation, sizeof (prop_keylocation),
+ NULL, NULL, 0, B_TRUE);
+ if (ret != 0) {
+ zfs_error_aux(zhp->zfs_hdl,
+ dgettext(TEXT_DOMAIN, "Failed to "
+ "get existing keylocation "
+ "property."));
+ goto error;
+ }
+
+ keylocation = prop_keylocation;
+ }
+ } else {
+ /* need a new key for non-encryption roots */
+ if (keyformat == ZFS_KEYFORMAT_NONE) {
+ ret = EINVAL;
+ zfs_error_aux(zhp->zfs_hdl,
+ dgettext(TEXT_DOMAIN, "Keyformat required "
+ "for new encryption root."));
+ goto error;
+ }
+
+ /* default to prompt if no keylocation is specified */
+ if (keylocation == NULL) {
+ keylocation = "prompt";
+ ret = nvlist_add_string(props,
+ zfs_prop_to_name(ZFS_PROP_KEYLOCATION),
+ keylocation);
+ if (ret != 0)
+ goto error;
+ }
+ }
+
+ /* fetch the new wrapping key and associated properties */
+ ret = populate_create_encryption_params_nvlists(zhp->zfs_hdl,
+ zhp, B_TRUE, keyformat, keylocation, props, &wkeydata,
+ &wkeylen);
+ if (ret != 0)
+ goto error;
+ } else {
+ /* check that zhp is an encryption root */
+ if (!is_encroot) {
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "Key inheritting can only be performed on "
+ "encryption roots."));
+ ret = EINVAL;
+ goto error;
+ }
+
+ /* get the parent's name */
+ ret = zfs_parent_name(zhp, parent_name, sizeof (parent_name));
+ if (ret != 0) {
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "Root dataset cannot inherit key."));
+ ret = EINVAL;
+ goto error;
+ }
+
+ /* get a handle to the parent */
+ pzhp = make_dataset_handle(zhp->zfs_hdl, parent_name);
+ if (pzhp == NULL) {
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "Failed to lookup parent."));
+ ret = ENOENT;
+ goto error;
+ }
+
+ /* parent must be encrypted */
+ pcrypt = zfs_prop_get_int(pzhp, ZFS_PROP_ENCRYPTION);
+ if (pcrypt == ZIO_CRYPT_OFF) {
+ zfs_error_aux(pzhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "Parent must be encrypted."));
+ ret = EINVAL;
+ goto error;
+ }
+
+ /* check that the parent's key is loaded */
+ pkeystatus = zfs_prop_get_int(pzhp, ZFS_PROP_KEYSTATUS);
+ if (pkeystatus == ZFS_KEYSTATUS_UNAVAILABLE) {
+ zfs_error_aux(pzhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "Parent key must be loaded."));
+ ret = EACCES;
+ goto error;
+ }
+ }
+
+ /* check that the key is loaded */
+ keystatus = zfs_prop_get_int(zhp, ZFS_PROP_KEYSTATUS);
+ if (keystatus == ZFS_KEYSTATUS_UNAVAILABLE) {
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "Key must be loaded."));
+ ret = EACCES;
+ goto error;
+ }
+
+ /* call the ioctl */
+ ret = lzc_change_key(zhp->zfs_name, cmd, props, wkeydata, wkeylen);
+ if (ret != 0) {
+ switch (ret) {
+ case EPERM:
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "Permission denied."));
+ break;
+ case EINVAL:
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "Invalid properties for key change."));
+ break;
+ case EACCES:
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "Key is not currently loaded."));
+ break;
+ }
+ zfs_error(zhp->zfs_hdl, EZFS_CRYPTOFAILED, errbuf);
+ }
+
+ if (pzhp != NULL)
+ zfs_close(pzhp);
+ if (props != NULL)
+ nvlist_free(props);
+ if (wkeydata != NULL)
+ free(wkeydata);
+
+ return (ret);
+
+error:
+ if (pzhp != NULL)
+ zfs_close(pzhp);
+ if (props != NULL)
+ nvlist_free(props);
+ if (wkeydata != NULL)
+ free(wkeydata);
+
+ zfs_error(zhp->zfs_hdl, EZFS_CRYPTOFAILED, errbuf);
+ return (ret);
+}
diff --git a/lib/libzfs/libzfs_dataset.c b/lib/libzfs/libzfs_dataset.c
new file mode 100644
index 000000000000..d548cc0ecabc
--- /dev/null
+++ b/lib/libzfs/libzfs_dataset.c
@@ -0,0 +1,5492 @@
+/*
+ * 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) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright 2019 Joyent, Inc.
+ * Copyright (c) 2011, 2020 by Delphix. All rights reserved.
+ * Copyright (c) 2012 DEY Storage Systems, Inc. All rights reserved.
+ * Copyright (c) 2012 Pawel Jakub Dawidek <pawel@dawidek.net>.
+ * Copyright (c) 2013 Martin Matuska. All rights reserved.
+ * Copyright (c) 2013 Steven Hartland. All rights reserved.
+ * Copyright 2017 Nexenta Systems, Inc.
+ * Copyright 2016 Igor Kozhukhov <ikozhukhov@gmail.com>
+ * Copyright 2017-2018 RackTop Systems.
+ * Copyright (c) 2019 Datto Inc.
+ * Copyright (c) 2019, loli10K <ezomori.nozomu@gmail.com>
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <libintl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <unistd.h>
+#include <stddef.h>
+#include <zone.h>
+#include <fcntl.h>
+#include <sys/mntent.h>
+#include <sys/mount.h>
+#include <pwd.h>
+#include <grp.h>
+#include <stddef.h>
+#include <ucred.h>
+#ifdef HAVE_IDMAP
+#include <idmap.h>
+#include <aclutils.h>
+#include <directory.h>
+#endif /* HAVE_IDMAP */
+
+#include <sys/dnode.h>
+#include <sys/spa.h>
+#include <sys/zap.h>
+#include <sys/dsl_crypt.h>
+#include <libzfs.h>
+#include <libzutil.h>
+
+#include "zfs_namecheck.h"
+#include "zfs_prop.h"
+#include "libzfs_impl.h"
+#include "libzfs.h"
+#include "zfs_deleg.h"
+
+static int userquota_propname_decode(const char *propname, boolean_t zoned,
+ zfs_userquota_prop_t *typep, char *domain, int domainlen, uint64_t *ridp);
+
+/*
+ * Given a single type (not a mask of types), return the type in a human
+ * readable form.
+ */
+const char *
+zfs_type_to_name(zfs_type_t type)
+{
+ switch (type) {
+ case ZFS_TYPE_FILESYSTEM:
+ return (dgettext(TEXT_DOMAIN, "filesystem"));
+ case ZFS_TYPE_SNAPSHOT:
+ return (dgettext(TEXT_DOMAIN, "snapshot"));
+ case ZFS_TYPE_VOLUME:
+ return (dgettext(TEXT_DOMAIN, "volume"));
+ case ZFS_TYPE_POOL:
+ return (dgettext(TEXT_DOMAIN, "pool"));
+ case ZFS_TYPE_BOOKMARK:
+ return (dgettext(TEXT_DOMAIN, "bookmark"));
+ default:
+ assert(!"unhandled zfs_type_t");
+ }
+
+ return (NULL);
+}
+
+/*
+ * Validate a ZFS path. This is used even before trying to open the dataset, to
+ * provide a more meaningful error message. We call zfs_error_aux() to
+ * explain exactly why the name was not valid.
+ */
+int
+zfs_validate_name(libzfs_handle_t *hdl, const char *path, int type,
+ boolean_t modifying)
+{
+ namecheck_err_t why;
+ char what;
+
+ if (!(type & ZFS_TYPE_SNAPSHOT) && strchr(path, '@') != NULL) {
+ if (hdl != NULL)
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "snapshot delimiter '@' is not expected here"));
+ return (0);
+ }
+
+ if (type == ZFS_TYPE_SNAPSHOT && strchr(path, '@') == NULL) {
+ if (hdl != NULL)
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "missing '@' delimiter in snapshot name"));
+ return (0);
+ }
+
+ if (!(type & ZFS_TYPE_BOOKMARK) && strchr(path, '#') != NULL) {
+ if (hdl != NULL)
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "bookmark delimiter '#' is not expected here"));
+ return (0);
+ }
+
+ if (type == ZFS_TYPE_BOOKMARK && strchr(path, '#') == NULL) {
+ if (hdl != NULL)
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "missing '#' delimiter in bookmark name"));
+ return (0);
+ }
+
+ if (modifying && strchr(path, '%') != NULL) {
+ if (hdl != NULL)
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "invalid character %c in name"), '%');
+ return (0);
+ }
+
+ if (entity_namecheck(path, &why, &what) != 0) {
+ if (hdl != NULL) {
+ switch (why) {
+ case NAME_ERR_TOOLONG:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "name is too long"));
+ break;
+
+ case NAME_ERR_LEADING_SLASH:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "leading slash in name"));
+ break;
+
+ case NAME_ERR_EMPTY_COMPONENT:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "empty component or misplaced '@'"
+ " or '#' delimiter in name"));
+ break;
+
+ case NAME_ERR_TRAILING_SLASH:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "trailing slash in name"));
+ break;
+
+ case NAME_ERR_INVALCHAR:
+ zfs_error_aux(hdl,
+ dgettext(TEXT_DOMAIN, "invalid character "
+ "'%c' in name"), what);
+ break;
+
+ case NAME_ERR_MULTIPLE_DELIMITERS:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "multiple '@' and/or '#' delimiters in "
+ "name"));
+ break;
+
+ case NAME_ERR_NOLETTER:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "pool doesn't begin with a letter"));
+ break;
+
+ case NAME_ERR_RESERVED:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "name is reserved"));
+ break;
+
+ case NAME_ERR_DISKLIKE:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "reserved disk name"));
+ break;
+
+ case NAME_ERR_SELF_REF:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "self reference, '.' is found in name"));
+ break;
+
+ case NAME_ERR_PARENT_REF:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "parent reference, '..' is found in name"));
+ break;
+
+ default:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "(%d) not defined"), why);
+ break;
+ }
+ }
+
+ return (0);
+ }
+
+ return (-1);
+}
+
+int
+zfs_name_valid(const char *name, zfs_type_t type)
+{
+ if (type == ZFS_TYPE_POOL)
+ return (zpool_name_valid(NULL, B_FALSE, name));
+ return (zfs_validate_name(NULL, name, type, B_FALSE));
+}
+
+/*
+ * This function takes the raw DSL properties, and filters out the user-defined
+ * properties into a separate nvlist.
+ */
+static nvlist_t *
+process_user_props(zfs_handle_t *zhp, nvlist_t *props)
+{
+ libzfs_handle_t *hdl = zhp->zfs_hdl;
+ nvpair_t *elem;
+ nvlist_t *propval;
+ nvlist_t *nvl;
+
+ if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0) != 0) {
+ (void) no_memory(hdl);
+ return (NULL);
+ }
+
+ elem = NULL;
+ while ((elem = nvlist_next_nvpair(props, elem)) != NULL) {
+ if (!zfs_prop_user(nvpair_name(elem)))
+ continue;
+
+ verify(nvpair_value_nvlist(elem, &propval) == 0);
+ if (nvlist_add_nvlist(nvl, nvpair_name(elem), propval) != 0) {
+ nvlist_free(nvl);
+ (void) no_memory(hdl);
+ return (NULL);
+ }
+ }
+
+ return (nvl);
+}
+
+static zpool_handle_t *
+zpool_add_handle(zfs_handle_t *zhp, const char *pool_name)
+{
+ libzfs_handle_t *hdl = zhp->zfs_hdl;
+ zpool_handle_t *zph;
+
+ if ((zph = zpool_open_canfail(hdl, pool_name)) != NULL) {
+ if (hdl->libzfs_pool_handles != NULL)
+ zph->zpool_next = hdl->libzfs_pool_handles;
+ hdl->libzfs_pool_handles = zph;
+ }
+ return (zph);
+}
+
+static zpool_handle_t *
+zpool_find_handle(zfs_handle_t *zhp, const char *pool_name, int len)
+{
+ libzfs_handle_t *hdl = zhp->zfs_hdl;
+ zpool_handle_t *zph = hdl->libzfs_pool_handles;
+
+ while ((zph != NULL) &&
+ (strncmp(pool_name, zpool_get_name(zph), len) != 0))
+ zph = zph->zpool_next;
+ return (zph);
+}
+
+/*
+ * Returns a handle to the pool that contains the provided dataset.
+ * If a handle to that pool already exists then that handle is returned.
+ * Otherwise, a new handle is created and added to the list of handles.
+ */
+static zpool_handle_t *
+zpool_handle(zfs_handle_t *zhp)
+{
+ char *pool_name;
+ int len;
+ zpool_handle_t *zph;
+
+ len = strcspn(zhp->zfs_name, "/@#") + 1;
+ pool_name = zfs_alloc(zhp->zfs_hdl, len);
+ (void) strlcpy(pool_name, zhp->zfs_name, len);
+
+ zph = zpool_find_handle(zhp, pool_name, len);
+ if (zph == NULL)
+ zph = zpool_add_handle(zhp, pool_name);
+
+ free(pool_name);
+ return (zph);
+}
+
+void
+zpool_free_handles(libzfs_handle_t *hdl)
+{
+ zpool_handle_t *next, *zph = hdl->libzfs_pool_handles;
+
+ while (zph != NULL) {
+ next = zph->zpool_next;
+ zpool_close(zph);
+ zph = next;
+ }
+ hdl->libzfs_pool_handles = NULL;
+}
+
+/*
+ * Utility function to gather stats (objset and zpl) for the given object.
+ */
+static int
+get_stats_ioctl(zfs_handle_t *zhp, zfs_cmd_t *zc)
+{
+ libzfs_handle_t *hdl = zhp->zfs_hdl;
+
+ (void) strlcpy(zc->zc_name, zhp->zfs_name, sizeof (zc->zc_name));
+
+ while (zfs_ioctl(hdl, ZFS_IOC_OBJSET_STATS, zc) != 0) {
+ if (errno == ENOMEM) {
+ if (zcmd_expand_dst_nvlist(hdl, zc) != 0) {
+ return (-1);
+ }
+ } else {
+ return (-1);
+ }
+ }
+ return (0);
+}
+
+/*
+ * Utility function to get the received properties of the given object.
+ */
+static int
+get_recvd_props_ioctl(zfs_handle_t *zhp)
+{
+ libzfs_handle_t *hdl = zhp->zfs_hdl;
+ nvlist_t *recvdprops;
+ zfs_cmd_t zc = {"\0"};
+ int err;
+
+ if (zcmd_alloc_dst_nvlist(hdl, &zc, 0) != 0)
+ return (-1);
+
+ (void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name));
+
+ while (zfs_ioctl(hdl, ZFS_IOC_OBJSET_RECVD_PROPS, &zc) != 0) {
+ if (errno == ENOMEM) {
+ if (zcmd_expand_dst_nvlist(hdl, &zc) != 0) {
+ return (-1);
+ }
+ } else {
+ zcmd_free_nvlists(&zc);
+ return (-1);
+ }
+ }
+
+ err = zcmd_read_dst_nvlist(zhp->zfs_hdl, &zc, &recvdprops);
+ zcmd_free_nvlists(&zc);
+ if (err != 0)
+ return (-1);
+
+ nvlist_free(zhp->zfs_recvd_props);
+ zhp->zfs_recvd_props = recvdprops;
+
+ return (0);
+}
+
+static int
+put_stats_zhdl(zfs_handle_t *zhp, zfs_cmd_t *zc)
+{
+ nvlist_t *allprops, *userprops;
+
+ zhp->zfs_dmustats = zc->zc_objset_stats; /* structure assignment */
+
+ if (zcmd_read_dst_nvlist(zhp->zfs_hdl, zc, &allprops) != 0) {
+ return (-1);
+ }
+
+ /*
+ * XXX Why do we store the user props separately, in addition to
+ * storing them in zfs_props?
+ */
+ if ((userprops = process_user_props(zhp, allprops)) == NULL) {
+ nvlist_free(allprops);
+ return (-1);
+ }
+
+ nvlist_free(zhp->zfs_props);
+ nvlist_free(zhp->zfs_user_props);
+
+ zhp->zfs_props = allprops;
+ zhp->zfs_user_props = userprops;
+
+ return (0);
+}
+
+static int
+get_stats(zfs_handle_t *zhp)
+{
+ int rc = 0;
+ zfs_cmd_t zc = {"\0"};
+
+ if (zcmd_alloc_dst_nvlist(zhp->zfs_hdl, &zc, 0) != 0)
+ return (-1);
+ if (get_stats_ioctl(zhp, &zc) != 0)
+ rc = -1;
+ else if (put_stats_zhdl(zhp, &zc) != 0)
+ rc = -1;
+ zcmd_free_nvlists(&zc);
+ return (rc);
+}
+
+/*
+ * Refresh the properties currently stored in the handle.
+ */
+void
+zfs_refresh_properties(zfs_handle_t *zhp)
+{
+ (void) get_stats(zhp);
+}
+
+/*
+ * Makes a handle from the given dataset name. Used by zfs_open() and
+ * zfs_iter_* to create child handles on the fly.
+ */
+static int
+make_dataset_handle_common(zfs_handle_t *zhp, zfs_cmd_t *zc)
+{
+ if (put_stats_zhdl(zhp, zc) != 0)
+ return (-1);
+
+ /*
+ * We've managed to open the dataset and gather statistics. Determine
+ * the high-level type.
+ */
+ if (zhp->zfs_dmustats.dds_type == DMU_OST_ZVOL)
+ zhp->zfs_head_type = ZFS_TYPE_VOLUME;
+ else if (zhp->zfs_dmustats.dds_type == DMU_OST_ZFS)
+ zhp->zfs_head_type = ZFS_TYPE_FILESYSTEM;
+ else if (zhp->zfs_dmustats.dds_type == DMU_OST_OTHER)
+ return (-1);
+ else
+ abort();
+
+ if (zhp->zfs_dmustats.dds_is_snapshot)
+ zhp->zfs_type = ZFS_TYPE_SNAPSHOT;
+ else if (zhp->zfs_dmustats.dds_type == DMU_OST_ZVOL)
+ zhp->zfs_type = ZFS_TYPE_VOLUME;
+ else if (zhp->zfs_dmustats.dds_type == DMU_OST_ZFS)
+ zhp->zfs_type = ZFS_TYPE_FILESYSTEM;
+ else
+ abort(); /* we should never see any other types */
+
+ if ((zhp->zpool_hdl = zpool_handle(zhp)) == NULL)
+ return (-1);
+
+ return (0);
+}
+
+zfs_handle_t *
+make_dataset_handle(libzfs_handle_t *hdl, const char *path)
+{
+ zfs_cmd_t zc = {"\0"};
+
+ zfs_handle_t *zhp = calloc(1, sizeof (zfs_handle_t));
+
+ if (zhp == NULL)
+ return (NULL);
+
+ zhp->zfs_hdl = hdl;
+ (void) strlcpy(zhp->zfs_name, path, sizeof (zhp->zfs_name));
+ if (zcmd_alloc_dst_nvlist(hdl, &zc, 0) != 0) {
+ free(zhp);
+ return (NULL);
+ }
+ if (get_stats_ioctl(zhp, &zc) == -1) {
+ zcmd_free_nvlists(&zc);
+ free(zhp);
+ return (NULL);
+ }
+ if (make_dataset_handle_common(zhp, &zc) == -1) {
+ free(zhp);
+ zhp = NULL;
+ }
+ zcmd_free_nvlists(&zc);
+ return (zhp);
+}
+
+zfs_handle_t *
+make_dataset_handle_zc(libzfs_handle_t *hdl, zfs_cmd_t *zc)
+{
+ zfs_handle_t *zhp = calloc(1, sizeof (zfs_handle_t));
+
+ if (zhp == NULL)
+ return (NULL);
+
+ zhp->zfs_hdl = hdl;
+ (void) strlcpy(zhp->zfs_name, zc->zc_name, sizeof (zhp->zfs_name));
+ if (make_dataset_handle_common(zhp, zc) == -1) {
+ free(zhp);
+ return (NULL);
+ }
+ return (zhp);
+}
+
+zfs_handle_t *
+make_dataset_simple_handle_zc(zfs_handle_t *pzhp, zfs_cmd_t *zc)
+{
+ zfs_handle_t *zhp = calloc(1, sizeof (zfs_handle_t));
+
+ if (zhp == NULL)
+ return (NULL);
+
+ zhp->zfs_hdl = pzhp->zfs_hdl;
+ (void) strlcpy(zhp->zfs_name, zc->zc_name, sizeof (zhp->zfs_name));
+ zhp->zfs_head_type = pzhp->zfs_type;
+ zhp->zfs_type = ZFS_TYPE_SNAPSHOT;
+ zhp->zpool_hdl = zpool_handle(zhp);
+
+ return (zhp);
+}
+
+zfs_handle_t *
+zfs_handle_dup(zfs_handle_t *zhp_orig)
+{
+ zfs_handle_t *zhp = calloc(1, sizeof (zfs_handle_t));
+
+ if (zhp == NULL)
+ return (NULL);
+
+ zhp->zfs_hdl = zhp_orig->zfs_hdl;
+ zhp->zpool_hdl = zhp_orig->zpool_hdl;
+ (void) strlcpy(zhp->zfs_name, zhp_orig->zfs_name,
+ sizeof (zhp->zfs_name));
+ zhp->zfs_type = zhp_orig->zfs_type;
+ zhp->zfs_head_type = zhp_orig->zfs_head_type;
+ zhp->zfs_dmustats = zhp_orig->zfs_dmustats;
+ if (zhp_orig->zfs_props != NULL) {
+ if (nvlist_dup(zhp_orig->zfs_props, &zhp->zfs_props, 0) != 0) {
+ (void) no_memory(zhp->zfs_hdl);
+ zfs_close(zhp);
+ return (NULL);
+ }
+ }
+ if (zhp_orig->zfs_user_props != NULL) {
+ if (nvlist_dup(zhp_orig->zfs_user_props,
+ &zhp->zfs_user_props, 0) != 0) {
+ (void) no_memory(zhp->zfs_hdl);
+ zfs_close(zhp);
+ return (NULL);
+ }
+ }
+ if (zhp_orig->zfs_recvd_props != NULL) {
+ if (nvlist_dup(zhp_orig->zfs_recvd_props,
+ &zhp->zfs_recvd_props, 0)) {
+ (void) no_memory(zhp->zfs_hdl);
+ zfs_close(zhp);
+ return (NULL);
+ }
+ }
+ zhp->zfs_mntcheck = zhp_orig->zfs_mntcheck;
+ if (zhp_orig->zfs_mntopts != NULL) {
+ zhp->zfs_mntopts = zfs_strdup(zhp_orig->zfs_hdl,
+ zhp_orig->zfs_mntopts);
+ }
+ zhp->zfs_props_table = zhp_orig->zfs_props_table;
+ return (zhp);
+}
+
+boolean_t
+zfs_bookmark_exists(const char *path)
+{
+ nvlist_t *bmarks;
+ nvlist_t *props;
+ char fsname[ZFS_MAX_DATASET_NAME_LEN];
+ char *bmark_name;
+ char *pound;
+ int err;
+ boolean_t rv;
+
+ (void) strlcpy(fsname, path, sizeof (fsname));
+ pound = strchr(fsname, '#');
+ if (pound == NULL)
+ return (B_FALSE);
+
+ *pound = '\0';
+ bmark_name = pound + 1;
+ props = fnvlist_alloc();
+ err = lzc_get_bookmarks(fsname, props, &bmarks);
+ nvlist_free(props);
+ if (err != 0) {
+ nvlist_free(bmarks);
+ return (B_FALSE);
+ }
+
+ rv = nvlist_exists(bmarks, bmark_name);
+ nvlist_free(bmarks);
+ return (rv);
+}
+
+zfs_handle_t *
+make_bookmark_handle(zfs_handle_t *parent, const char *path,
+ nvlist_t *bmark_props)
+{
+ zfs_handle_t *zhp = calloc(1, sizeof (zfs_handle_t));
+
+ if (zhp == NULL)
+ return (NULL);
+
+ /* Fill in the name. */
+ zhp->zfs_hdl = parent->zfs_hdl;
+ (void) strlcpy(zhp->zfs_name, path, sizeof (zhp->zfs_name));
+
+ /* Set the property lists. */
+ if (nvlist_dup(bmark_props, &zhp->zfs_props, 0) != 0) {
+ free(zhp);
+ return (NULL);
+ }
+
+ /* Set the types. */
+ zhp->zfs_head_type = parent->zfs_head_type;
+ zhp->zfs_type = ZFS_TYPE_BOOKMARK;
+
+ if ((zhp->zpool_hdl = zpool_handle(zhp)) == NULL) {
+ nvlist_free(zhp->zfs_props);
+ free(zhp);
+ return (NULL);
+ }
+
+ return (zhp);
+}
+
+struct zfs_open_bookmarks_cb_data {
+ const char *path;
+ zfs_handle_t *zhp;
+};
+
+static int
+zfs_open_bookmarks_cb(zfs_handle_t *zhp, void *data)
+{
+ struct zfs_open_bookmarks_cb_data *dp = data;
+
+ /*
+ * Is it the one we are looking for?
+ */
+ if (strcmp(dp->path, zfs_get_name(zhp)) == 0) {
+ /*
+ * We found it. Save it and let the caller know we are done.
+ */
+ dp->zhp = zhp;
+ return (EEXIST);
+ }
+
+ /*
+ * Not found. Close the handle and ask for another one.
+ */
+ zfs_close(zhp);
+ return (0);
+}
+
+/*
+ * Opens the given snapshot, bookmark, filesystem, or volume. The 'types'
+ * argument is a mask of acceptable types. The function will print an
+ * appropriate error message and return NULL if it can't be opened.
+ */
+zfs_handle_t *
+zfs_open(libzfs_handle_t *hdl, const char *path, int types)
+{
+ zfs_handle_t *zhp;
+ char errbuf[1024];
+ char *bookp;
+
+ (void) snprintf(errbuf, sizeof (errbuf),
+ dgettext(TEXT_DOMAIN, "cannot open '%s'"), path);
+
+ /*
+ * Validate the name before we even try to open it.
+ */
+ if (!zfs_validate_name(hdl, path, types, B_FALSE)) {
+ (void) zfs_error(hdl, EZFS_INVALIDNAME, errbuf);
+ return (NULL);
+ }
+
+ /*
+ * Bookmarks needs to be handled separately.
+ */
+ bookp = strchr(path, '#');
+ if (bookp == NULL) {
+ /*
+ * Try to get stats for the dataset, which will tell us if it
+ * exists.
+ */
+ errno = 0;
+ if ((zhp = make_dataset_handle(hdl, path)) == NULL) {
+ (void) zfs_standard_error(hdl, errno, errbuf);
+ return (NULL);
+ }
+ } else {
+ char dsname[ZFS_MAX_DATASET_NAME_LEN];
+ zfs_handle_t *pzhp;
+ struct zfs_open_bookmarks_cb_data cb_data = {path, NULL};
+
+ /*
+ * We need to cut out '#' and everything after '#'
+ * to get the parent dataset name only.
+ */
+ assert(bookp - path < sizeof (dsname));
+ (void) strncpy(dsname, path, bookp - path);
+ dsname[bookp - path] = '\0';
+
+ /*
+ * Create handle for the parent dataset.
+ */
+ errno = 0;
+ if ((pzhp = make_dataset_handle(hdl, dsname)) == NULL) {
+ (void) zfs_standard_error(hdl, errno, errbuf);
+ return (NULL);
+ }
+
+ /*
+ * Iterate bookmarks to find the right one.
+ */
+ errno = 0;
+ if ((zfs_iter_bookmarks(pzhp, zfs_open_bookmarks_cb,
+ &cb_data) == 0) && (cb_data.zhp == NULL)) {
+ (void) zfs_error(hdl, EZFS_NOENT, errbuf);
+ zfs_close(pzhp);
+ return (NULL);
+ }
+ if (cb_data.zhp == NULL) {
+ (void) zfs_standard_error(hdl, errno, errbuf);
+ zfs_close(pzhp);
+ return (NULL);
+ }
+ zhp = cb_data.zhp;
+
+ /*
+ * Cleanup.
+ */
+ zfs_close(pzhp);
+ }
+
+ if (!(types & zhp->zfs_type)) {
+ (void) zfs_error(hdl, EZFS_BADTYPE, errbuf);
+ zfs_close(zhp);
+ return (NULL);
+ }
+
+ return (zhp);
+}
+
+/*
+ * Release a ZFS handle. Nothing to do but free the associated memory.
+ */
+void
+zfs_close(zfs_handle_t *zhp)
+{
+ if (zhp->zfs_mntopts)
+ free(zhp->zfs_mntopts);
+ nvlist_free(zhp->zfs_props);
+ nvlist_free(zhp->zfs_user_props);
+ nvlist_free(zhp->zfs_recvd_props);
+ free(zhp);
+}
+
+typedef struct mnttab_node {
+ struct mnttab mtn_mt;
+ avl_node_t mtn_node;
+} mnttab_node_t;
+
+static int
+libzfs_mnttab_cache_compare(const void *arg1, const void *arg2)
+{
+ const mnttab_node_t *mtn1 = (const mnttab_node_t *)arg1;
+ const mnttab_node_t *mtn2 = (const mnttab_node_t *)arg2;
+ int rv;
+
+ rv = strcmp(mtn1->mtn_mt.mnt_special, mtn2->mtn_mt.mnt_special);
+
+ return (TREE_ISIGN(rv));
+}
+
+void
+libzfs_mnttab_init(libzfs_handle_t *hdl)
+{
+ pthread_mutex_init(&hdl->libzfs_mnttab_cache_lock, NULL);
+ assert(avl_numnodes(&hdl->libzfs_mnttab_cache) == 0);
+ avl_create(&hdl->libzfs_mnttab_cache, libzfs_mnttab_cache_compare,
+ sizeof (mnttab_node_t), offsetof(mnttab_node_t, mtn_node));
+}
+
+static int
+libzfs_mnttab_update(libzfs_handle_t *hdl)
+{
+ struct mnttab entry;
+
+ /* Reopen MNTTAB to prevent reading stale data from open file */
+ if (freopen(MNTTAB, "r", hdl->libzfs_mnttab) == NULL)
+ return (ENOENT);
+
+ while (getmntent(hdl->libzfs_mnttab, &entry) == 0) {
+ mnttab_node_t *mtn;
+ avl_index_t where;
+
+ if (strcmp(entry.mnt_fstype, MNTTYPE_ZFS) != 0)
+ continue;
+
+ mtn = zfs_alloc(hdl, sizeof (mnttab_node_t));
+ mtn->mtn_mt.mnt_special = zfs_strdup(hdl, entry.mnt_special);
+ mtn->mtn_mt.mnt_mountp = zfs_strdup(hdl, entry.mnt_mountp);
+ mtn->mtn_mt.mnt_fstype = zfs_strdup(hdl, entry.mnt_fstype);
+ mtn->mtn_mt.mnt_mntopts = zfs_strdup(hdl, entry.mnt_mntopts);
+
+ /* Exclude duplicate mounts */
+ if (avl_find(&hdl->libzfs_mnttab_cache, mtn, &where) != NULL) {
+ free(mtn->mtn_mt.mnt_special);
+ free(mtn->mtn_mt.mnt_mountp);
+ free(mtn->mtn_mt.mnt_fstype);
+ free(mtn->mtn_mt.mnt_mntopts);
+ free(mtn);
+ continue;
+ }
+
+ avl_add(&hdl->libzfs_mnttab_cache, mtn);
+ }
+
+ return (0);
+}
+
+void
+libzfs_mnttab_fini(libzfs_handle_t *hdl)
+{
+ void *cookie = NULL;
+ mnttab_node_t *mtn;
+
+ while ((mtn = avl_destroy_nodes(&hdl->libzfs_mnttab_cache, &cookie))
+ != NULL) {
+ free(mtn->mtn_mt.mnt_special);
+ free(mtn->mtn_mt.mnt_mountp);
+ free(mtn->mtn_mt.mnt_fstype);
+ free(mtn->mtn_mt.mnt_mntopts);
+ free(mtn);
+ }
+ avl_destroy(&hdl->libzfs_mnttab_cache);
+ (void) pthread_mutex_destroy(&hdl->libzfs_mnttab_cache_lock);
+}
+
+void
+libzfs_mnttab_cache(libzfs_handle_t *hdl, boolean_t enable)
+{
+ hdl->libzfs_mnttab_enable = enable;
+}
+
+int
+libzfs_mnttab_find(libzfs_handle_t *hdl, const char *fsname,
+ struct mnttab *entry)
+{
+ mnttab_node_t find;
+ mnttab_node_t *mtn;
+ int ret = ENOENT;
+
+ if (!hdl->libzfs_mnttab_enable) {
+ struct mnttab srch = { 0 };
+
+ if (avl_numnodes(&hdl->libzfs_mnttab_cache))
+ libzfs_mnttab_fini(hdl);
+
+ /* Reopen MNTTAB to prevent reading stale data from open file */
+ if (freopen(MNTTAB, "r", hdl->libzfs_mnttab) == NULL)
+ return (ENOENT);
+
+ srch.mnt_special = (char *)fsname;
+ srch.mnt_fstype = MNTTYPE_ZFS;
+ if (getmntany(hdl->libzfs_mnttab, entry, &srch) == 0)
+ return (0);
+ else
+ return (ENOENT);
+ }
+
+ pthread_mutex_lock(&hdl->libzfs_mnttab_cache_lock);
+ if (avl_numnodes(&hdl->libzfs_mnttab_cache) == 0) {
+ int error;
+
+ if ((error = libzfs_mnttab_update(hdl)) != 0) {
+ pthread_mutex_unlock(&hdl->libzfs_mnttab_cache_lock);
+ return (error);
+ }
+ }
+
+ find.mtn_mt.mnt_special = (char *)fsname;
+ mtn = avl_find(&hdl->libzfs_mnttab_cache, &find, NULL);
+ if (mtn) {
+ *entry = mtn->mtn_mt;
+ ret = 0;
+ }
+ pthread_mutex_unlock(&hdl->libzfs_mnttab_cache_lock);
+ return (ret);
+}
+
+void
+libzfs_mnttab_add(libzfs_handle_t *hdl, const char *special,
+ const char *mountp, const char *mntopts)
+{
+ mnttab_node_t *mtn;
+
+ pthread_mutex_lock(&hdl->libzfs_mnttab_cache_lock);
+ if (avl_numnodes(&hdl->libzfs_mnttab_cache) != 0) {
+ mtn = zfs_alloc(hdl, sizeof (mnttab_node_t));
+ mtn->mtn_mt.mnt_special = zfs_strdup(hdl, special);
+ mtn->mtn_mt.mnt_mountp = zfs_strdup(hdl, mountp);
+ mtn->mtn_mt.mnt_fstype = zfs_strdup(hdl, MNTTYPE_ZFS);
+ mtn->mtn_mt.mnt_mntopts = zfs_strdup(hdl, mntopts);
+ /*
+ * Another thread may have already added this entry
+ * via libzfs_mnttab_update. If so we should skip it.
+ */
+ if (avl_find(&hdl->libzfs_mnttab_cache, mtn, NULL) != NULL) {
+ free(mtn->mtn_mt.mnt_special);
+ free(mtn->mtn_mt.mnt_mountp);
+ free(mtn->mtn_mt.mnt_fstype);
+ free(mtn->mtn_mt.mnt_mntopts);
+ free(mtn);
+ } else {
+ avl_add(&hdl->libzfs_mnttab_cache, mtn);
+ }
+ }
+ pthread_mutex_unlock(&hdl->libzfs_mnttab_cache_lock);
+}
+
+void
+libzfs_mnttab_remove(libzfs_handle_t *hdl, const char *fsname)
+{
+ mnttab_node_t find;
+ mnttab_node_t *ret;
+
+ pthread_mutex_lock(&hdl->libzfs_mnttab_cache_lock);
+ find.mtn_mt.mnt_special = (char *)fsname;
+ if ((ret = avl_find(&hdl->libzfs_mnttab_cache, (void *)&find, NULL))
+ != NULL) {
+ avl_remove(&hdl->libzfs_mnttab_cache, ret);
+ free(ret->mtn_mt.mnt_special);
+ free(ret->mtn_mt.mnt_mountp);
+ free(ret->mtn_mt.mnt_fstype);
+ free(ret->mtn_mt.mnt_mntopts);
+ free(ret);
+ }
+ pthread_mutex_unlock(&hdl->libzfs_mnttab_cache_lock);
+}
+
+int
+zfs_spa_version(zfs_handle_t *zhp, int *spa_version)
+{
+ zpool_handle_t *zpool_handle = zhp->zpool_hdl;
+
+ if (zpool_handle == NULL)
+ return (-1);
+
+ *spa_version = zpool_get_prop_int(zpool_handle,
+ ZPOOL_PROP_VERSION, NULL);
+ return (0);
+}
+
+/*
+ * The choice of reservation property depends on the SPA version.
+ */
+static int
+zfs_which_resv_prop(zfs_handle_t *zhp, zfs_prop_t *resv_prop)
+{
+ int spa_version;
+
+ if (zfs_spa_version(zhp, &spa_version) < 0)
+ return (-1);
+
+ if (spa_version >= SPA_VERSION_REFRESERVATION)
+ *resv_prop = ZFS_PROP_REFRESERVATION;
+ else
+ *resv_prop = ZFS_PROP_RESERVATION;
+
+ return (0);
+}
+
+/*
+ * Given an nvlist of properties to set, validates that they are correct, and
+ * parses any numeric properties (index, boolean, etc) if they are specified as
+ * strings.
+ */
+nvlist_t *
+zfs_valid_proplist(libzfs_handle_t *hdl, zfs_type_t type, nvlist_t *nvl,
+ uint64_t zoned, zfs_handle_t *zhp, zpool_handle_t *zpool_hdl,
+ boolean_t key_params_ok, const char *errbuf)
+{
+ nvpair_t *elem;
+ uint64_t intval;
+ char *strval;
+ zfs_prop_t prop;
+ nvlist_t *ret;
+ int chosen_normal = -1;
+ int chosen_utf = -1;
+
+ if (nvlist_alloc(&ret, NV_UNIQUE_NAME, 0) != 0) {
+ (void) no_memory(hdl);
+ return (NULL);
+ }
+
+ /*
+ * Make sure this property is valid and applies to this type.
+ */
+
+ elem = NULL;
+ while ((elem = nvlist_next_nvpair(nvl, elem)) != NULL) {
+ const char *propname = nvpair_name(elem);
+
+ prop = zfs_name_to_prop(propname);
+ if (prop == ZPROP_INVAL && zfs_prop_user(propname)) {
+ /*
+ * This is a user property: make sure it's a
+ * string, and that it's less than ZAP_MAXNAMELEN.
+ */
+ if (nvpair_type(elem) != DATA_TYPE_STRING) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "'%s' must be a string"), propname);
+ (void) zfs_error(hdl, EZFS_BADPROP, errbuf);
+ goto error;
+ }
+
+ if (strlen(nvpair_name(elem)) >= ZAP_MAXNAMELEN) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "property name '%s' is too long"),
+ propname);
+ (void) zfs_error(hdl, EZFS_BADPROP, errbuf);
+ goto error;
+ }
+
+ (void) nvpair_value_string(elem, &strval);
+ if (nvlist_add_string(ret, propname, strval) != 0) {
+ (void) no_memory(hdl);
+ goto error;
+ }
+ continue;
+ }
+
+ /*
+ * Currently, only user properties can be modified on
+ * snapshots.
+ */
+ if (type == ZFS_TYPE_SNAPSHOT) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "this property can not be modified for snapshots"));
+ (void) zfs_error(hdl, EZFS_PROPTYPE, errbuf);
+ goto error;
+ }
+
+ if (prop == ZPROP_INVAL && zfs_prop_userquota(propname)) {
+ zfs_userquota_prop_t uqtype;
+ char *newpropname = NULL;
+ char domain[128];
+ uint64_t rid;
+ uint64_t valary[3];
+ int rc;
+
+ if (userquota_propname_decode(propname, zoned,
+ &uqtype, domain, sizeof (domain), &rid) != 0) {
+ zfs_error_aux(hdl,
+ dgettext(TEXT_DOMAIN,
+ "'%s' has an invalid user/group name"),
+ propname);
+ (void) zfs_error(hdl, EZFS_BADPROP, errbuf);
+ goto error;
+ }
+
+ if (uqtype != ZFS_PROP_USERQUOTA &&
+ uqtype != ZFS_PROP_GROUPQUOTA &&
+ uqtype != ZFS_PROP_USEROBJQUOTA &&
+ uqtype != ZFS_PROP_GROUPOBJQUOTA &&
+ uqtype != ZFS_PROP_PROJECTQUOTA &&
+ uqtype != ZFS_PROP_PROJECTOBJQUOTA) {
+ zfs_error_aux(hdl,
+ dgettext(TEXT_DOMAIN, "'%s' is readonly"),
+ propname);
+ (void) zfs_error(hdl, EZFS_PROPREADONLY,
+ errbuf);
+ goto error;
+ }
+
+ if (nvpair_type(elem) == DATA_TYPE_STRING) {
+ (void) nvpair_value_string(elem, &strval);
+ if (strcmp(strval, "none") == 0) {
+ intval = 0;
+ } else if (zfs_nicestrtonum(hdl,
+ strval, &intval) != 0) {
+ (void) zfs_error(hdl,
+ EZFS_BADPROP, errbuf);
+ goto error;
+ }
+ } else if (nvpair_type(elem) ==
+ DATA_TYPE_UINT64) {
+ (void) nvpair_value_uint64(elem, &intval);
+ if (intval == 0) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "use 'none' to disable "
+ "{user|group|project}quota"));
+ goto error;
+ }
+ } else {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "'%s' must be a number"), propname);
+ (void) zfs_error(hdl, EZFS_BADPROP, errbuf);
+ goto error;
+ }
+
+ /*
+ * Encode the prop name as
+ * userquota@<hex-rid>-domain, to make it easy
+ * for the kernel to decode.
+ */
+ rc = asprintf(&newpropname, "%s%llx-%s",
+ zfs_userquota_prop_prefixes[uqtype],
+ (longlong_t)rid, domain);
+ if (rc == -1 || newpropname == NULL) {
+ (void) no_memory(hdl);
+ goto error;
+ }
+
+ valary[0] = uqtype;
+ valary[1] = rid;
+ valary[2] = intval;
+ if (nvlist_add_uint64_array(ret, newpropname,
+ valary, 3) != 0) {
+ free(newpropname);
+ (void) no_memory(hdl);
+ goto error;
+ }
+ free(newpropname);
+ continue;
+ } else if (prop == ZPROP_INVAL && zfs_prop_written(propname)) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "'%s' is readonly"),
+ propname);
+ (void) zfs_error(hdl, EZFS_PROPREADONLY, errbuf);
+ goto error;
+ }
+
+ if (prop == ZPROP_INVAL) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "invalid property '%s'"), propname);
+ (void) zfs_error(hdl, EZFS_BADPROP, errbuf);
+ goto error;
+ }
+
+ if (!zfs_prop_valid_for_type(prop, type, B_FALSE)) {
+ zfs_error_aux(hdl,
+ dgettext(TEXT_DOMAIN, "'%s' does not "
+ "apply to datasets of this type"), propname);
+ (void) zfs_error(hdl, EZFS_PROPTYPE, errbuf);
+ goto error;
+ }
+
+ if (zfs_prop_readonly(prop) &&
+ !(zfs_prop_setonce(prop) && zhp == NULL) &&
+ !(zfs_prop_encryption_key_param(prop) && key_params_ok)) {
+ zfs_error_aux(hdl,
+ dgettext(TEXT_DOMAIN, "'%s' is readonly"),
+ propname);
+ (void) zfs_error(hdl, EZFS_PROPREADONLY, errbuf);
+ goto error;
+ }
+
+ if (zprop_parse_value(hdl, elem, prop, type, ret,
+ &strval, &intval, errbuf) != 0)
+ goto error;
+
+ /*
+ * Perform some additional checks for specific properties.
+ */
+ switch (prop) {
+ case ZFS_PROP_VERSION:
+ {
+ int version;
+
+ if (zhp == NULL)
+ break;
+ version = zfs_prop_get_int(zhp, ZFS_PROP_VERSION);
+ if (intval < version) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Can not downgrade; already at version %u"),
+ version);
+ (void) zfs_error(hdl, EZFS_BADPROP, errbuf);
+ goto error;
+ }
+ break;
+ }
+
+ case ZFS_PROP_VOLBLOCKSIZE:
+ case ZFS_PROP_RECORDSIZE:
+ {
+ int maxbs = SPA_MAXBLOCKSIZE;
+ char buf[64];
+
+ if (zpool_hdl != NULL) {
+ maxbs = zpool_get_prop_int(zpool_hdl,
+ ZPOOL_PROP_MAXBLOCKSIZE, NULL);
+ }
+ /*
+ * The value must be a power of two between
+ * SPA_MINBLOCKSIZE and maxbs.
+ */
+ if (intval < SPA_MINBLOCKSIZE ||
+ intval > maxbs || !ISP2(intval)) {
+ zfs_nicebytes(maxbs, buf, sizeof (buf));
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "'%s' must be power of 2 from 512B "
+ "to %s"), propname, buf);
+ (void) zfs_error(hdl, EZFS_BADPROP, errbuf);
+ goto error;
+ }
+ break;
+ }
+
+ case ZFS_PROP_SPECIAL_SMALL_BLOCKS:
+ {
+ int maxbs = SPA_OLD_MAXBLOCKSIZE;
+ char buf[64];
+
+ if (zpool_hdl != NULL) {
+ char state[64] = "";
+
+ maxbs = zpool_get_prop_int(zpool_hdl,
+ ZPOOL_PROP_MAXBLOCKSIZE, NULL);
+
+ /*
+ * Issue a warning but do not fail so that
+ * tests for settable properties succeed.
+ */
+ if (zpool_prop_get_feature(zpool_hdl,
+ "feature@allocation_classes", state,
+ sizeof (state)) != 0 ||
+ strcmp(state, ZFS_FEATURE_ACTIVE) != 0) {
+ (void) fprintf(stderr, gettext(
+ "%s: property requires a special "
+ "device in the pool\n"), propname);
+ }
+ }
+ if (intval != 0 &&
+ (intval < SPA_MINBLOCKSIZE ||
+ intval > maxbs || !ISP2(intval))) {
+ zfs_nicebytes(maxbs, buf, sizeof (buf));
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "invalid '%s=%d' property: must be zero or "
+ "a power of 2 from 512B to %s"), propname,
+ intval, buf);
+ (void) zfs_error(hdl, EZFS_BADPROP, errbuf);
+ goto error;
+ }
+ break;
+ }
+
+ case ZFS_PROP_MLSLABEL:
+ {
+#ifdef HAVE_MLSLABEL
+ /*
+ * Verify the mlslabel string and convert to
+ * internal hex label string.
+ */
+
+ m_label_t *new_sl;
+ char *hex = NULL; /* internal label string */
+
+ /* Default value is already OK. */
+ if (strcasecmp(strval, ZFS_MLSLABEL_DEFAULT) == 0)
+ break;
+
+ /* Verify the label can be converted to binary form */
+ if (((new_sl = m_label_alloc(MAC_LABEL)) == NULL) ||
+ (str_to_label(strval, &new_sl, MAC_LABEL,
+ L_NO_CORRECTION, NULL) == -1)) {
+ goto badlabel;
+ }
+
+ /* Now translate to hex internal label string */
+ if (label_to_str(new_sl, &hex, M_INTERNAL,
+ DEF_NAMES) != 0) {
+ if (hex)
+ free(hex);
+ goto badlabel;
+ }
+ m_label_free(new_sl);
+
+ /* If string is already in internal form, we're done. */
+ if (strcmp(strval, hex) == 0) {
+ free(hex);
+ break;
+ }
+
+ /* Replace the label string with the internal form. */
+ (void) nvlist_remove(ret, zfs_prop_to_name(prop),
+ DATA_TYPE_STRING);
+ verify(nvlist_add_string(ret, zfs_prop_to_name(prop),
+ hex) == 0);
+ free(hex);
+
+ break;
+
+badlabel:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "invalid mlslabel '%s'"), strval);
+ (void) zfs_error(hdl, EZFS_BADPROP, errbuf);
+ m_label_free(new_sl); /* OK if null */
+ goto error;
+#else
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "mlslabels are unsupported"));
+ (void) zfs_error(hdl, EZFS_BADPROP, errbuf);
+ goto error;
+#endif /* HAVE_MLSLABEL */
+ }
+
+ case ZFS_PROP_MOUNTPOINT:
+ {
+ namecheck_err_t why;
+
+ if (strcmp(strval, ZFS_MOUNTPOINT_NONE) == 0 ||
+ strcmp(strval, ZFS_MOUNTPOINT_LEGACY) == 0)
+ break;
+
+ if (mountpoint_namecheck(strval, &why)) {
+ switch (why) {
+ case NAME_ERR_LEADING_SLASH:
+ zfs_error_aux(hdl,
+ dgettext(TEXT_DOMAIN,
+ "'%s' must be an absolute path, "
+ "'none', or 'legacy'"), propname);
+ break;
+ case NAME_ERR_TOOLONG:
+ zfs_error_aux(hdl,
+ dgettext(TEXT_DOMAIN,
+ "component of '%s' is too long"),
+ propname);
+ break;
+
+ default:
+ zfs_error_aux(hdl,
+ dgettext(TEXT_DOMAIN,
+ "(%d) not defined"),
+ why);
+ break;
+ }
+ (void) zfs_error(hdl, EZFS_BADPROP, errbuf);
+ goto error;
+ }
+ }
+
+ /*FALLTHRU*/
+
+ case ZFS_PROP_SHARESMB:
+ case ZFS_PROP_SHARENFS:
+ /*
+ * For the mountpoint and sharenfs or sharesmb
+ * properties, check if it can be set in a
+ * global/non-global zone based on
+ * the zoned property value:
+ *
+ * global zone non-global zone
+ * --------------------------------------------------
+ * zoned=on mountpoint (no) mountpoint (yes)
+ * sharenfs (no) sharenfs (no)
+ * sharesmb (no) sharesmb (no)
+ *
+ * zoned=off mountpoint (yes) N/A
+ * sharenfs (yes)
+ * sharesmb (yes)
+ */
+ if (zoned) {
+ if (getzoneid() == GLOBAL_ZONEID) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "'%s' cannot be set on "
+ "dataset in a non-global zone"),
+ propname);
+ (void) zfs_error(hdl, EZFS_ZONED,
+ errbuf);
+ goto error;
+ } else if (prop == ZFS_PROP_SHARENFS ||
+ prop == ZFS_PROP_SHARESMB) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "'%s' cannot be set in "
+ "a non-global zone"), propname);
+ (void) zfs_error(hdl, EZFS_ZONED,
+ errbuf);
+ goto error;
+ }
+ } else if (getzoneid() != GLOBAL_ZONEID) {
+ /*
+ * If zoned property is 'off', this must be in
+ * a global zone. If not, something is wrong.
+ */
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "'%s' cannot be set while dataset "
+ "'zoned' property is set"), propname);
+ (void) zfs_error(hdl, EZFS_ZONED, errbuf);
+ goto error;
+ }
+
+ /*
+ * At this point, it is legitimate to set the
+ * property. Now we want to make sure that the
+ * property value is valid if it is sharenfs.
+ */
+ if ((prop == ZFS_PROP_SHARENFS ||
+ prop == ZFS_PROP_SHARESMB) &&
+ strcmp(strval, "on") != 0 &&
+ strcmp(strval, "off") != 0) {
+ zfs_share_proto_t proto;
+
+ if (prop == ZFS_PROP_SHARESMB)
+ proto = PROTO_SMB;
+ else
+ proto = PROTO_NFS;
+
+ if (zfs_parse_options(strval, proto) != SA_OK) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "'%s' cannot be set to invalid "
+ "options"), propname);
+ (void) zfs_error(hdl, EZFS_BADPROP,
+ errbuf);
+ goto error;
+ }
+ }
+
+ break;
+
+ case ZFS_PROP_KEYLOCATION:
+ if (!zfs_prop_valid_keylocation(strval, B_FALSE)) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "invalid keylocation"));
+ (void) zfs_error(hdl, EZFS_BADPROP, errbuf);
+ goto error;
+ }
+
+ if (zhp != NULL) {
+ uint64_t crypt =
+ zfs_prop_get_int(zhp, ZFS_PROP_ENCRYPTION);
+
+ if (crypt == ZIO_CRYPT_OFF &&
+ strcmp(strval, "none") != 0) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "keylocation must be 'none' "
+ "for unencrypted datasets"));
+ (void) zfs_error(hdl, EZFS_BADPROP,
+ errbuf);
+ goto error;
+ } else if (crypt != ZIO_CRYPT_OFF &&
+ strcmp(strval, "none") == 0) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "keylocation must not be 'none' "
+ "for encrypted datasets"));
+ (void) zfs_error(hdl, EZFS_BADPROP,
+ errbuf);
+ goto error;
+ }
+ }
+ break;
+
+ case ZFS_PROP_PBKDF2_ITERS:
+ if (intval < MIN_PBKDF2_ITERATIONS) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "minimum pbkdf2 iterations is %u"),
+ MIN_PBKDF2_ITERATIONS);
+ (void) zfs_error(hdl, EZFS_BADPROP, errbuf);
+ goto error;
+ }
+ break;
+
+ case ZFS_PROP_UTF8ONLY:
+ chosen_utf = (int)intval;
+ break;
+
+ case ZFS_PROP_NORMALIZE:
+ chosen_normal = (int)intval;
+ break;
+
+ default:
+ break;
+ }
+
+ /*
+ * For changes to existing volumes, we have some additional
+ * checks to enforce.
+ */
+ if (type == ZFS_TYPE_VOLUME && zhp != NULL) {
+ uint64_t blocksize = zfs_prop_get_int(zhp,
+ ZFS_PROP_VOLBLOCKSIZE);
+ char buf[64];
+
+ switch (prop) {
+ case ZFS_PROP_VOLSIZE:
+ if (intval % blocksize != 0) {
+ zfs_nicebytes(blocksize, buf,
+ sizeof (buf));
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "'%s' must be a multiple of "
+ "volume block size (%s)"),
+ propname, buf);
+ (void) zfs_error(hdl, EZFS_BADPROP,
+ errbuf);
+ goto error;
+ }
+
+ if (intval == 0) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "'%s' cannot be zero"),
+ propname);
+ (void) zfs_error(hdl, EZFS_BADPROP,
+ errbuf);
+ goto error;
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ /* check encryption properties */
+ if (zhp != NULL) {
+ int64_t crypt = zfs_prop_get_int(zhp,
+ ZFS_PROP_ENCRYPTION);
+
+ switch (prop) {
+ case ZFS_PROP_COPIES:
+ if (crypt != ZIO_CRYPT_OFF && intval > 2) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "encrypted datasets cannot have "
+ "3 copies"));
+ (void) zfs_error(hdl, EZFS_BADPROP,
+ errbuf);
+ goto error;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ /*
+ * If normalization was chosen, but no UTF8 choice was made,
+ * enforce rejection of non-UTF8 names.
+ *
+ * If normalization was chosen, but rejecting non-UTF8 names
+ * was explicitly not chosen, it is an error.
+ */
+ if (chosen_normal > 0 && chosen_utf < 0) {
+ if (nvlist_add_uint64(ret,
+ zfs_prop_to_name(ZFS_PROP_UTF8ONLY), 1) != 0) {
+ (void) no_memory(hdl);
+ goto error;
+ }
+ } else if (chosen_normal > 0 && chosen_utf == 0) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "'%s' must be set 'on' if normalization chosen"),
+ zfs_prop_to_name(ZFS_PROP_UTF8ONLY));
+ (void) zfs_error(hdl, EZFS_BADPROP, errbuf);
+ goto error;
+ }
+ return (ret);
+
+error:
+ nvlist_free(ret);
+ return (NULL);
+}
+
+static int
+zfs_add_synthetic_resv(zfs_handle_t *zhp, nvlist_t *nvl)
+{
+ uint64_t old_volsize;
+ uint64_t new_volsize;
+ uint64_t old_reservation;
+ uint64_t new_reservation;
+ zfs_prop_t resv_prop;
+ nvlist_t *props;
+ zpool_handle_t *zph = zpool_handle(zhp);
+
+ /*
+ * If this is an existing volume, and someone is setting the volsize,
+ * make sure that it matches the reservation, or add it if necessary.
+ */
+ old_volsize = zfs_prop_get_int(zhp, ZFS_PROP_VOLSIZE);
+ if (zfs_which_resv_prop(zhp, &resv_prop) < 0)
+ return (-1);
+ old_reservation = zfs_prop_get_int(zhp, resv_prop);
+
+ props = fnvlist_alloc();
+ fnvlist_add_uint64(props, zfs_prop_to_name(ZFS_PROP_VOLBLOCKSIZE),
+ zfs_prop_get_int(zhp, ZFS_PROP_VOLBLOCKSIZE));
+
+ if ((zvol_volsize_to_reservation(zph, old_volsize, props) !=
+ old_reservation) || nvlist_exists(nvl,
+ zfs_prop_to_name(resv_prop))) {
+ fnvlist_free(props);
+ return (0);
+ }
+ if (nvlist_lookup_uint64(nvl, zfs_prop_to_name(ZFS_PROP_VOLSIZE),
+ &new_volsize) != 0) {
+ fnvlist_free(props);
+ return (-1);
+ }
+ new_reservation = zvol_volsize_to_reservation(zph, new_volsize, props);
+ fnvlist_free(props);
+
+ if (nvlist_add_uint64(nvl, zfs_prop_to_name(resv_prop),
+ new_reservation) != 0) {
+ (void) no_memory(zhp->zfs_hdl);
+ return (-1);
+ }
+ return (1);
+}
+
+/*
+ * Helper for 'zfs {set|clone} refreservation=auto'. Must be called after
+ * zfs_valid_proplist(), as it is what sets the UINT64_MAX sentinel value.
+ * Return codes must match zfs_add_synthetic_resv().
+ */
+static int
+zfs_fix_auto_resv(zfs_handle_t *zhp, nvlist_t *nvl)
+{
+ uint64_t volsize;
+ uint64_t resvsize;
+ zfs_prop_t prop;
+ nvlist_t *props;
+
+ if (!ZFS_IS_VOLUME(zhp)) {
+ return (0);
+ }
+
+ if (zfs_which_resv_prop(zhp, &prop) != 0) {
+ return (-1);
+ }
+
+ if (prop != ZFS_PROP_REFRESERVATION) {
+ return (0);
+ }
+
+ if (nvlist_lookup_uint64(nvl, zfs_prop_to_name(prop), &resvsize) != 0) {
+ /* No value being set, so it can't be "auto" */
+ return (0);
+ }
+ if (resvsize != UINT64_MAX) {
+ /* Being set to a value other than "auto" */
+ return (0);
+ }
+
+ props = fnvlist_alloc();
+
+ fnvlist_add_uint64(props, zfs_prop_to_name(ZFS_PROP_VOLBLOCKSIZE),
+ zfs_prop_get_int(zhp, ZFS_PROP_VOLBLOCKSIZE));
+
+ if (nvlist_lookup_uint64(nvl, zfs_prop_to_name(ZFS_PROP_VOLSIZE),
+ &volsize) != 0) {
+ volsize = zfs_prop_get_int(zhp, ZFS_PROP_VOLSIZE);
+ }
+
+ resvsize = zvol_volsize_to_reservation(zpool_handle(zhp), volsize,
+ props);
+ fnvlist_free(props);
+
+ (void) nvlist_remove_all(nvl, zfs_prop_to_name(prop));
+ if (nvlist_add_uint64(nvl, zfs_prop_to_name(prop), resvsize) != 0) {
+ (void) no_memory(zhp->zfs_hdl);
+ return (-1);
+ }
+ return (1);
+}
+
+static boolean_t
+zfs_is_namespace_prop(zfs_prop_t prop)
+{
+ switch (prop) {
+
+ case ZFS_PROP_ATIME:
+ case ZFS_PROP_RELATIME:
+ case ZFS_PROP_DEVICES:
+ case ZFS_PROP_EXEC:
+ case ZFS_PROP_SETUID:
+ case ZFS_PROP_READONLY:
+ case ZFS_PROP_XATTR:
+ case ZFS_PROP_NBMAND:
+ return (B_TRUE);
+
+ default:
+ return (B_FALSE);
+ }
+}
+
+/*
+ * Given a property name and value, set the property for the given dataset.
+ */
+int
+zfs_prop_set(zfs_handle_t *zhp, const char *propname, const char *propval)
+{
+ int ret = -1;
+ char errbuf[1024];
+ libzfs_handle_t *hdl = zhp->zfs_hdl;
+ nvlist_t *nvl = NULL;
+
+ (void) snprintf(errbuf, sizeof (errbuf),
+ dgettext(TEXT_DOMAIN, "cannot set property for '%s'"),
+ zhp->zfs_name);
+
+ if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0) != 0 ||
+ nvlist_add_string(nvl, propname, propval) != 0) {
+ (void) no_memory(hdl);
+ goto error;
+ }
+
+ ret = zfs_prop_set_list(zhp, nvl);
+
+error:
+ nvlist_free(nvl);
+ return (ret);
+}
+
+
+
+/*
+ * Given an nvlist of property names and values, set the properties for the
+ * given dataset.
+ */
+int
+zfs_prop_set_list(zfs_handle_t *zhp, nvlist_t *props)
+{
+ zfs_cmd_t zc = {"\0"};
+ int ret = -1;
+ prop_changelist_t **cls = NULL;
+ int cl_idx;
+ char errbuf[1024];
+ libzfs_handle_t *hdl = zhp->zfs_hdl;
+ nvlist_t *nvl;
+ int nvl_len = 0;
+ int added_resv = 0;
+ zfs_prop_t prop = 0;
+ nvpair_t *elem;
+
+ (void) snprintf(errbuf, sizeof (errbuf),
+ dgettext(TEXT_DOMAIN, "cannot set property for '%s'"),
+ zhp->zfs_name);
+
+ if ((nvl = zfs_valid_proplist(hdl, zhp->zfs_type, props,
+ zfs_prop_get_int(zhp, ZFS_PROP_ZONED), zhp, zhp->zpool_hdl,
+ B_FALSE, errbuf)) == NULL)
+ goto error;
+
+ /*
+ * We have to check for any extra properties which need to be added
+ * before computing the length of the nvlist.
+ */
+ for (elem = nvlist_next_nvpair(nvl, NULL);
+ elem != NULL;
+ elem = nvlist_next_nvpair(nvl, elem)) {
+ if (zfs_name_to_prop(nvpair_name(elem)) == ZFS_PROP_VOLSIZE &&
+ (added_resv = zfs_add_synthetic_resv(zhp, nvl)) == -1) {
+ goto error;
+ }
+ }
+
+ if (added_resv != 1 &&
+ (added_resv = zfs_fix_auto_resv(zhp, nvl)) == -1) {
+ goto error;
+ }
+
+ /*
+ * Check how many properties we're setting and allocate an array to
+ * store changelist pointers for postfix().
+ */
+ for (elem = nvlist_next_nvpair(nvl, NULL);
+ elem != NULL;
+ elem = nvlist_next_nvpair(nvl, elem))
+ nvl_len++;
+ if ((cls = calloc(nvl_len, sizeof (prop_changelist_t *))) == NULL)
+ goto error;
+
+ cl_idx = 0;
+ for (elem = nvlist_next_nvpair(nvl, NULL);
+ elem != NULL;
+ elem = nvlist_next_nvpair(nvl, elem)) {
+
+ prop = zfs_name_to_prop(nvpair_name(elem));
+
+ assert(cl_idx < nvl_len);
+ /*
+ * We don't want to unmount & remount the dataset when changing
+ * its canmount property to 'on' or 'noauto'. We only use
+ * the changelist logic to unmount when setting canmount=off.
+ */
+ if (prop != ZFS_PROP_CANMOUNT ||
+ (fnvpair_value_uint64(elem) == ZFS_CANMOUNT_OFF &&
+ zfs_is_mounted(zhp, NULL))) {
+ cls[cl_idx] = changelist_gather(zhp, prop, 0, 0);
+ if (cls[cl_idx] == NULL)
+ goto error;
+ }
+
+ if (prop == ZFS_PROP_MOUNTPOINT &&
+ changelist_haszonedchild(cls[cl_idx])) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "child dataset with inherited mountpoint is used "
+ "in a non-global zone"));
+ ret = zfs_error(hdl, EZFS_ZONED, errbuf);
+ goto error;
+ }
+
+ if (cls[cl_idx] != NULL &&
+ (ret = changelist_prefix(cls[cl_idx])) != 0)
+ goto error;
+
+ cl_idx++;
+ }
+ assert(cl_idx == nvl_len);
+
+ /*
+ * Execute the corresponding ioctl() to set this list of properties.
+ */
+ (void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name));
+
+ if ((ret = zcmd_write_src_nvlist(hdl, &zc, nvl)) != 0 ||
+ (ret = zcmd_alloc_dst_nvlist(hdl, &zc, 0)) != 0)
+ goto error;
+
+ ret = zfs_ioctl(hdl, ZFS_IOC_SET_PROP, &zc);
+
+ if (ret != 0) {
+ if (zc.zc_nvlist_dst_filled == B_FALSE) {
+ (void) zfs_standard_error(hdl, errno, errbuf);
+ goto error;
+ }
+
+ /* Get the list of unset properties back and report them. */
+ nvlist_t *errorprops = NULL;
+ if (zcmd_read_dst_nvlist(hdl, &zc, &errorprops) != 0)
+ goto error;
+ for (nvpair_t *elem = nvlist_next_nvpair(errorprops, NULL);
+ elem != NULL;
+ elem = nvlist_next_nvpair(errorprops, elem)) {
+ prop = zfs_name_to_prop(nvpair_name(elem));
+ zfs_setprop_error(hdl, prop, errno, errbuf);
+ }
+ nvlist_free(errorprops);
+
+ if (added_resv && errno == ENOSPC) {
+ /* clean up the volsize property we tried to set */
+ uint64_t old_volsize = zfs_prop_get_int(zhp,
+ ZFS_PROP_VOLSIZE);
+ nvlist_free(nvl);
+ nvl = NULL;
+ zcmd_free_nvlists(&zc);
+
+ if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0) != 0)
+ goto error;
+ if (nvlist_add_uint64(nvl,
+ zfs_prop_to_name(ZFS_PROP_VOLSIZE),
+ old_volsize) != 0)
+ goto error;
+ if (zcmd_write_src_nvlist(hdl, &zc, nvl) != 0)
+ goto error;
+ (void) zfs_ioctl(hdl, ZFS_IOC_SET_PROP, &zc);
+ }
+ } else {
+ for (cl_idx = 0; cl_idx < nvl_len; cl_idx++) {
+ if (cls[cl_idx] != NULL) {
+ int clp_err = changelist_postfix(cls[cl_idx]);
+ if (clp_err != 0)
+ ret = clp_err;
+ }
+ }
+
+ if (ret == 0) {
+ /*
+ * Refresh the statistics so the new property
+ * value is reflected.
+ */
+ (void) get_stats(zhp);
+
+ /*
+ * Remount the filesystem to propagate the change
+ * if one of the options handled by the generic
+ * Linux namespace layer has been modified.
+ */
+ if (zfs_is_namespace_prop(prop) &&
+ zfs_is_mounted(zhp, NULL))
+ ret = zfs_mount(zhp, MNTOPT_REMOUNT, 0);
+ }
+ }
+
+error:
+ nvlist_free(nvl);
+ zcmd_free_nvlists(&zc);
+ if (cls != NULL) {
+ for (cl_idx = 0; cl_idx < nvl_len; cl_idx++) {
+ if (cls[cl_idx] != NULL)
+ changelist_free(cls[cl_idx]);
+ }
+ free(cls);
+ }
+ return (ret);
+}
+
+/*
+ * Given a property, inherit the value from the parent dataset, or if received
+ * is TRUE, revert to the received value, if any.
+ */
+int
+zfs_prop_inherit(zfs_handle_t *zhp, const char *propname, boolean_t received)
+{
+ zfs_cmd_t zc = {"\0"};
+ int ret;
+ prop_changelist_t *cl;
+ libzfs_handle_t *hdl = zhp->zfs_hdl;
+ char errbuf[1024];
+ zfs_prop_t prop;
+
+ (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
+ "cannot inherit %s for '%s'"), propname, zhp->zfs_name);
+
+ zc.zc_cookie = received;
+ if ((prop = zfs_name_to_prop(propname)) == ZPROP_INVAL) {
+ /*
+ * For user properties, the amount of work we have to do is very
+ * small, so just do it here.
+ */
+ if (!zfs_prop_user(propname)) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "invalid property"));
+ return (zfs_error(hdl, EZFS_BADPROP, errbuf));
+ }
+
+ (void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name));
+ (void) strlcpy(zc.zc_value, propname, sizeof (zc.zc_value));
+
+ if (zfs_ioctl(zhp->zfs_hdl, ZFS_IOC_INHERIT_PROP, &zc) != 0)
+ return (zfs_standard_error(hdl, errno, errbuf));
+
+ return (0);
+ }
+
+ /*
+ * Verify that this property is inheritable.
+ */
+ if (zfs_prop_readonly(prop))
+ return (zfs_error(hdl, EZFS_PROPREADONLY, errbuf));
+
+ if (!zfs_prop_inheritable(prop) && !received)
+ return (zfs_error(hdl, EZFS_PROPNONINHERIT, errbuf));
+
+ /*
+ * Check to see if the value applies to this type
+ */
+ if (!zfs_prop_valid_for_type(prop, zhp->zfs_type, B_FALSE))
+ return (zfs_error(hdl, EZFS_PROPTYPE, errbuf));
+
+ /*
+ * Normalize the name, to get rid of shorthand abbreviations.
+ */
+ propname = zfs_prop_to_name(prop);
+ (void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name));
+ (void) strlcpy(zc.zc_value, propname, sizeof (zc.zc_value));
+
+ if (prop == ZFS_PROP_MOUNTPOINT && getzoneid() == GLOBAL_ZONEID &&
+ zfs_prop_get_int(zhp, ZFS_PROP_ZONED)) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "dataset is used in a non-global zone"));
+ return (zfs_error(hdl, EZFS_ZONED, errbuf));
+ }
+
+ /*
+ * Determine datasets which will be affected by this change, if any.
+ */
+ if ((cl = changelist_gather(zhp, prop, 0, 0)) == NULL)
+ return (-1);
+
+ if (prop == ZFS_PROP_MOUNTPOINT && changelist_haszonedchild(cl)) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "child dataset with inherited mountpoint is used "
+ "in a non-global zone"));
+ ret = zfs_error(hdl, EZFS_ZONED, errbuf);
+ goto error;
+ }
+
+ if ((ret = changelist_prefix(cl)) != 0)
+ goto error;
+
+ if ((ret = zfs_ioctl(zhp->zfs_hdl, ZFS_IOC_INHERIT_PROP, &zc)) != 0) {
+ return (zfs_standard_error(hdl, errno, errbuf));
+ } else {
+
+ if ((ret = changelist_postfix(cl)) != 0)
+ goto error;
+
+ /*
+ * Refresh the statistics so the new property is reflected.
+ */
+ (void) get_stats(zhp);
+
+ /*
+ * Remount the filesystem to propagate the change
+ * if one of the options handled by the generic
+ * Linux namespace layer has been modified.
+ */
+ if (zfs_is_namespace_prop(prop) &&
+ zfs_is_mounted(zhp, NULL))
+ ret = zfs_mount(zhp, MNTOPT_REMOUNT, 0);
+ }
+
+error:
+ changelist_free(cl);
+ return (ret);
+}
+
+/*
+ * True DSL properties are stored in an nvlist. The following two functions
+ * extract them appropriately.
+ */
+uint64_t
+getprop_uint64(zfs_handle_t *zhp, zfs_prop_t prop, char **source)
+{
+ nvlist_t *nv;
+ uint64_t value;
+
+ *source = NULL;
+ if (nvlist_lookup_nvlist(zhp->zfs_props,
+ zfs_prop_to_name(prop), &nv) == 0) {
+ verify(nvlist_lookup_uint64(nv, ZPROP_VALUE, &value) == 0);
+ (void) nvlist_lookup_string(nv, ZPROP_SOURCE, source);
+ } else {
+ verify(!zhp->zfs_props_table ||
+ zhp->zfs_props_table[prop] == B_TRUE);
+ value = zfs_prop_default_numeric(prop);
+ *source = "";
+ }
+
+ return (value);
+}
+
+static const char *
+getprop_string(zfs_handle_t *zhp, zfs_prop_t prop, char **source)
+{
+ nvlist_t *nv;
+ const char *value;
+
+ *source = NULL;
+ if (nvlist_lookup_nvlist(zhp->zfs_props,
+ zfs_prop_to_name(prop), &nv) == 0) {
+ value = fnvlist_lookup_string(nv, ZPROP_VALUE);
+ (void) nvlist_lookup_string(nv, ZPROP_SOURCE, source);
+ } else {
+ verify(!zhp->zfs_props_table ||
+ zhp->zfs_props_table[prop] == B_TRUE);
+ value = zfs_prop_default_string(prop);
+ *source = "";
+ }
+
+ return (value);
+}
+
+static boolean_t
+zfs_is_recvd_props_mode(zfs_handle_t *zhp)
+{
+ return (zhp->zfs_props == zhp->zfs_recvd_props);
+}
+
+static void
+zfs_set_recvd_props_mode(zfs_handle_t *zhp, uint64_t *cookie)
+{
+ *cookie = (uint64_t)(uintptr_t)zhp->zfs_props;
+ zhp->zfs_props = zhp->zfs_recvd_props;
+}
+
+static void
+zfs_unset_recvd_props_mode(zfs_handle_t *zhp, uint64_t *cookie)
+{
+ zhp->zfs_props = (nvlist_t *)(uintptr_t)*cookie;
+ *cookie = 0;
+}
+
+/*
+ * Internal function for getting a numeric property. Both zfs_prop_get() and
+ * zfs_prop_get_int() are built using this interface.
+ *
+ * Certain properties can be overridden using 'mount -o'. In this case, scan
+ * the contents of the /proc/self/mounts entry, searching for the
+ * appropriate options. If they differ from the on-disk values, report the
+ * current values and mark the source "temporary".
+ */
+static int
+get_numeric_property(zfs_handle_t *zhp, zfs_prop_t prop, zprop_source_t *src,
+ char **source, uint64_t *val)
+{
+ zfs_cmd_t zc = {"\0"};
+ nvlist_t *zplprops = NULL;
+ struct mnttab mnt;
+ char *mntopt_on = NULL;
+ char *mntopt_off = NULL;
+ boolean_t received = zfs_is_recvd_props_mode(zhp);
+
+ *source = NULL;
+
+ /*
+ * If the property is being fetched for a snapshot, check whether
+ * the property is valid for the snapshot's head dataset type.
+ */
+ if (zhp->zfs_type == ZFS_TYPE_SNAPSHOT &&
+ !zfs_prop_valid_for_type(prop, zhp->zfs_head_type, B_TRUE)) {
+ *val = zfs_prop_default_numeric(prop);
+ return (-1);
+ }
+
+ switch (prop) {
+ case ZFS_PROP_ATIME:
+ mntopt_on = MNTOPT_ATIME;
+ mntopt_off = MNTOPT_NOATIME;
+ break;
+
+ case ZFS_PROP_RELATIME:
+ mntopt_on = MNTOPT_RELATIME;
+ mntopt_off = MNTOPT_NORELATIME;
+ break;
+
+ case ZFS_PROP_DEVICES:
+ mntopt_on = MNTOPT_DEVICES;
+ mntopt_off = MNTOPT_NODEVICES;
+ break;
+
+ case ZFS_PROP_EXEC:
+ mntopt_on = MNTOPT_EXEC;
+ mntopt_off = MNTOPT_NOEXEC;
+ break;
+
+ case ZFS_PROP_READONLY:
+ mntopt_on = MNTOPT_RO;
+ mntopt_off = MNTOPT_RW;
+ break;
+
+ case ZFS_PROP_SETUID:
+ mntopt_on = MNTOPT_SETUID;
+ mntopt_off = MNTOPT_NOSETUID;
+ break;
+
+ case ZFS_PROP_XATTR:
+ mntopt_on = MNTOPT_XATTR;
+ mntopt_off = MNTOPT_NOXATTR;
+ break;
+
+ case ZFS_PROP_NBMAND:
+ mntopt_on = MNTOPT_NBMAND;
+ mntopt_off = MNTOPT_NONBMAND;
+ break;
+
+ default:
+ break;
+ }
+
+ /*
+ * Because looking up the mount options is potentially expensive
+ * (iterating over all of /proc/self/mounts), we defer its
+ * calculation until we're looking up a property which requires
+ * its presence.
+ */
+ if (!zhp->zfs_mntcheck &&
+ (mntopt_on != NULL || prop == ZFS_PROP_MOUNTED)) {
+ libzfs_handle_t *hdl = zhp->zfs_hdl;
+ struct mnttab entry;
+
+ if (libzfs_mnttab_find(hdl, zhp->zfs_name, &entry) == 0) {
+ zhp->zfs_mntopts = zfs_strdup(hdl,
+ entry.mnt_mntopts);
+ if (zhp->zfs_mntopts == NULL)
+ return (-1);
+ }
+
+ zhp->zfs_mntcheck = B_TRUE;
+ }
+
+ if (zhp->zfs_mntopts == NULL)
+ mnt.mnt_mntopts = "";
+ else
+ mnt.mnt_mntopts = zhp->zfs_mntopts;
+
+ switch (prop) {
+ case ZFS_PROP_ATIME:
+ case ZFS_PROP_RELATIME:
+ case ZFS_PROP_DEVICES:
+ case ZFS_PROP_EXEC:
+ case ZFS_PROP_READONLY:
+ case ZFS_PROP_SETUID:
+#ifndef __FreeBSD__
+ case ZFS_PROP_XATTR:
+#endif
+ case ZFS_PROP_NBMAND:
+ *val = getprop_uint64(zhp, prop, source);
+
+ if (received)
+ break;
+
+ if (hasmntopt(&mnt, mntopt_on) && !*val) {
+ *val = B_TRUE;
+ if (src)
+ *src = ZPROP_SRC_TEMPORARY;
+ } else if (hasmntopt(&mnt, mntopt_off) && *val) {
+ *val = B_FALSE;
+ if (src)
+ *src = ZPROP_SRC_TEMPORARY;
+ }
+ break;
+
+ case ZFS_PROP_CANMOUNT:
+ case ZFS_PROP_VOLSIZE:
+ case ZFS_PROP_QUOTA:
+ case ZFS_PROP_REFQUOTA:
+ case ZFS_PROP_RESERVATION:
+ case ZFS_PROP_REFRESERVATION:
+ case ZFS_PROP_FILESYSTEM_LIMIT:
+ case ZFS_PROP_SNAPSHOT_LIMIT:
+ case ZFS_PROP_FILESYSTEM_COUNT:
+ case ZFS_PROP_SNAPSHOT_COUNT:
+ *val = getprop_uint64(zhp, prop, source);
+
+ if (*source == NULL) {
+ /* not default, must be local */
+ *source = zhp->zfs_name;
+ }
+ break;
+
+ case ZFS_PROP_MOUNTED:
+ *val = (zhp->zfs_mntopts != NULL);
+ break;
+
+ case ZFS_PROP_NUMCLONES:
+ *val = zhp->zfs_dmustats.dds_num_clones;
+ break;
+
+ case ZFS_PROP_VERSION:
+ case ZFS_PROP_NORMALIZE:
+ case ZFS_PROP_UTF8ONLY:
+ case ZFS_PROP_CASE:
+ if (zcmd_alloc_dst_nvlist(zhp->zfs_hdl, &zc, 0) != 0)
+ return (-1);
+ (void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name));
+ if (zfs_ioctl(zhp->zfs_hdl, ZFS_IOC_OBJSET_ZPLPROPS, &zc)) {
+ zcmd_free_nvlists(&zc);
+ if (prop == ZFS_PROP_VERSION &&
+ zhp->zfs_type == ZFS_TYPE_VOLUME)
+ *val = zfs_prop_default_numeric(prop);
+ return (-1);
+ }
+ if (zcmd_read_dst_nvlist(zhp->zfs_hdl, &zc, &zplprops) != 0 ||
+ nvlist_lookup_uint64(zplprops, zfs_prop_to_name(prop),
+ val) != 0) {
+ zcmd_free_nvlists(&zc);
+ return (-1);
+ }
+ nvlist_free(zplprops);
+ zcmd_free_nvlists(&zc);
+ break;
+
+ case ZFS_PROP_INCONSISTENT:
+ *val = zhp->zfs_dmustats.dds_inconsistent;
+ break;
+
+ case ZFS_PROP_REDACTED:
+ *val = zhp->zfs_dmustats.dds_redacted;
+ break;
+
+ default:
+ switch (zfs_prop_get_type(prop)) {
+ case PROP_TYPE_NUMBER:
+ case PROP_TYPE_INDEX:
+ *val = getprop_uint64(zhp, prop, source);
+ /*
+ * If we tried to use a default value for a
+ * readonly property, it means that it was not
+ * present. Note this only applies to "truly"
+ * readonly properties, not set-once properties
+ * like volblocksize.
+ */
+ if (zfs_prop_readonly(prop) &&
+ !zfs_prop_setonce(prop) &&
+ *source != NULL && (*source)[0] == '\0') {
+ *source = NULL;
+ return (-1);
+ }
+ break;
+
+ case PROP_TYPE_STRING:
+ default:
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "cannot get non-numeric property"));
+ return (zfs_error(zhp->zfs_hdl, EZFS_BADPROP,
+ dgettext(TEXT_DOMAIN, "internal error")));
+ }
+ }
+
+ return (0);
+}
+
+/*
+ * Calculate the source type, given the raw source string.
+ */
+static void
+get_source(zfs_handle_t *zhp, zprop_source_t *srctype, char *source,
+ char *statbuf, size_t statlen)
+{
+ if (statbuf == NULL ||
+ srctype == NULL || *srctype == ZPROP_SRC_TEMPORARY) {
+ return;
+ }
+
+ if (source == NULL) {
+ *srctype = ZPROP_SRC_NONE;
+ } else if (source[0] == '\0') {
+ *srctype = ZPROP_SRC_DEFAULT;
+ } else if (strstr(source, ZPROP_SOURCE_VAL_RECVD) != NULL) {
+ *srctype = ZPROP_SRC_RECEIVED;
+ } else {
+ if (strcmp(source, zhp->zfs_name) == 0) {
+ *srctype = ZPROP_SRC_LOCAL;
+ } else {
+ (void) strlcpy(statbuf, source, statlen);
+ *srctype = ZPROP_SRC_INHERITED;
+ }
+ }
+
+}
+
+int
+zfs_prop_get_recvd(zfs_handle_t *zhp, const char *propname, char *propbuf,
+ size_t proplen, boolean_t literal)
+{
+ zfs_prop_t prop;
+ int err = 0;
+
+ if (zhp->zfs_recvd_props == NULL)
+ if (get_recvd_props_ioctl(zhp) != 0)
+ return (-1);
+
+ prop = zfs_name_to_prop(propname);
+
+ if (prop != ZPROP_INVAL) {
+ uint64_t cookie;
+ if (!nvlist_exists(zhp->zfs_recvd_props, propname))
+ return (-1);
+ zfs_set_recvd_props_mode(zhp, &cookie);
+ err = zfs_prop_get(zhp, prop, propbuf, proplen,
+ NULL, NULL, 0, literal);
+ zfs_unset_recvd_props_mode(zhp, &cookie);
+ } else {
+ nvlist_t *propval;
+ char *recvdval;
+ if (nvlist_lookup_nvlist(zhp->zfs_recvd_props,
+ propname, &propval) != 0)
+ return (-1);
+ verify(nvlist_lookup_string(propval, ZPROP_VALUE,
+ &recvdval) == 0);
+ (void) strlcpy(propbuf, recvdval, proplen);
+ }
+
+ return (err == 0 ? 0 : -1);
+}
+
+static int
+get_clones_string(zfs_handle_t *zhp, char *propbuf, size_t proplen)
+{
+ nvlist_t *value;
+ nvpair_t *pair;
+
+ value = zfs_get_clones_nvl(zhp);
+ if (value == NULL)
+ return (-1);
+
+ propbuf[0] = '\0';
+ for (pair = nvlist_next_nvpair(value, NULL); pair != NULL;
+ pair = nvlist_next_nvpair(value, pair)) {
+ if (propbuf[0] != '\0')
+ (void) strlcat(propbuf, ",", proplen);
+ (void) strlcat(propbuf, nvpair_name(pair), proplen);
+ }
+
+ return (0);
+}
+
+struct get_clones_arg {
+ uint64_t numclones;
+ nvlist_t *value;
+ const char *origin;
+ char buf[ZFS_MAX_DATASET_NAME_LEN];
+};
+
+static int
+get_clones_cb(zfs_handle_t *zhp, void *arg)
+{
+ struct get_clones_arg *gca = arg;
+
+ if (gca->numclones == 0) {
+ zfs_close(zhp);
+ return (0);
+ }
+
+ if (zfs_prop_get(zhp, ZFS_PROP_ORIGIN, gca->buf, sizeof (gca->buf),
+ NULL, NULL, 0, B_TRUE) != 0)
+ goto out;
+ if (strcmp(gca->buf, gca->origin) == 0) {
+ fnvlist_add_boolean(gca->value, zfs_get_name(zhp));
+ gca->numclones--;
+ }
+
+out:
+ (void) zfs_iter_children(zhp, get_clones_cb, gca);
+ zfs_close(zhp);
+ return (0);
+}
+
+nvlist_t *
+zfs_get_clones_nvl(zfs_handle_t *zhp)
+{
+ nvlist_t *nv, *value;
+
+ if (nvlist_lookup_nvlist(zhp->zfs_props,
+ zfs_prop_to_name(ZFS_PROP_CLONES), &nv) != 0) {
+ struct get_clones_arg gca;
+
+ /*
+ * if this is a snapshot, then the kernel wasn't able
+ * to get the clones. Do it by slowly iterating.
+ */
+ if (zhp->zfs_type != ZFS_TYPE_SNAPSHOT)
+ return (NULL);
+ if (nvlist_alloc(&nv, NV_UNIQUE_NAME, 0) != 0)
+ return (NULL);
+ if (nvlist_alloc(&value, NV_UNIQUE_NAME, 0) != 0) {
+ nvlist_free(nv);
+ return (NULL);
+ }
+
+ gca.numclones = zfs_prop_get_int(zhp, ZFS_PROP_NUMCLONES);
+ gca.value = value;
+ gca.origin = zhp->zfs_name;
+
+ if (gca.numclones != 0) {
+ zfs_handle_t *root;
+ char pool[ZFS_MAX_DATASET_NAME_LEN];
+ char *cp = pool;
+
+ /* get the pool name */
+ (void) strlcpy(pool, zhp->zfs_name, sizeof (pool));
+ (void) strsep(&cp, "/@");
+ root = zfs_open(zhp->zfs_hdl, pool,
+ ZFS_TYPE_FILESYSTEM);
+ if (root == NULL) {
+ nvlist_free(nv);
+ nvlist_free(value);
+ return (NULL);
+ }
+
+ (void) get_clones_cb(root, &gca);
+ }
+
+ if (gca.numclones != 0 ||
+ nvlist_add_nvlist(nv, ZPROP_VALUE, value) != 0 ||
+ nvlist_add_nvlist(zhp->zfs_props,
+ zfs_prop_to_name(ZFS_PROP_CLONES), nv) != 0) {
+ nvlist_free(nv);
+ nvlist_free(value);
+ return (NULL);
+ }
+ nvlist_free(nv);
+ nvlist_free(value);
+ verify(0 == nvlist_lookup_nvlist(zhp->zfs_props,
+ zfs_prop_to_name(ZFS_PROP_CLONES), &nv));
+ }
+
+ verify(nvlist_lookup_nvlist(nv, ZPROP_VALUE, &value) == 0);
+
+ return (value);
+}
+
+static int
+get_rsnaps_string(zfs_handle_t *zhp, char *propbuf, size_t proplen)
+{
+ nvlist_t *value;
+ uint64_t *snaps;
+ uint_t nsnaps;
+
+ if (nvlist_lookup_nvlist(zhp->zfs_props,
+ zfs_prop_to_name(ZFS_PROP_REDACT_SNAPS), &value) != 0)
+ return (-1);
+ if (nvlist_lookup_uint64_array(value, ZPROP_VALUE, &snaps,
+ &nsnaps) != 0)
+ return (-1);
+ if (nsnaps == 0) {
+ /* There's no redaction snapshots; pass a special value back */
+ (void) snprintf(propbuf, proplen, "none");
+ return (0);
+ }
+ propbuf[0] = '\0';
+ for (int i = 0; i < nsnaps; i++) {
+ char buf[128];
+ if (propbuf[0] != '\0')
+ (void) strlcat(propbuf, ",", proplen);
+ (void) snprintf(buf, sizeof (buf), "%llu",
+ (u_longlong_t)snaps[i]);
+ (void) strlcat(propbuf, buf, proplen);
+ }
+
+ return (0);
+}
+
+/*
+ * Accepts a property and value and checks that the value
+ * matches the one found by the channel program. If they are
+ * not equal, print both of them.
+ */
+static void
+zcp_check(zfs_handle_t *zhp, zfs_prop_t prop, uint64_t intval,
+ const char *strval)
+{
+ if (!zhp->zfs_hdl->libzfs_prop_debug)
+ return;
+ int error;
+ char *poolname = zhp->zpool_hdl->zpool_name;
+ const char *prop_name = zfs_prop_to_name(prop);
+ const char *program =
+ "args = ...\n"
+ "ds = args['dataset']\n"
+ "prop = args['property']\n"
+ "value, setpoint = zfs.get_prop(ds, prop)\n"
+ "return {value=value, setpoint=setpoint}\n";
+ nvlist_t *outnvl;
+ nvlist_t *retnvl;
+ nvlist_t *argnvl = fnvlist_alloc();
+
+ fnvlist_add_string(argnvl, "dataset", zhp->zfs_name);
+ fnvlist_add_string(argnvl, "property", zfs_prop_to_name(prop));
+
+ error = lzc_channel_program_nosync(poolname, program,
+ 10 * 1000 * 1000, 10 * 1024 * 1024, argnvl, &outnvl);
+
+ if (error == 0) {
+ retnvl = fnvlist_lookup_nvlist(outnvl, "return");
+ if (zfs_prop_get_type(prop) == PROP_TYPE_NUMBER) {
+ int64_t ans;
+ error = nvlist_lookup_int64(retnvl, "value", &ans);
+ if (error != 0) {
+ (void) fprintf(stderr, "%s: zcp check error: "
+ "%u\n", prop_name, error);
+ return;
+ }
+ if (ans != intval) {
+ (void) fprintf(stderr, "%s: zfs found %llu, "
+ "but zcp found %llu\n", prop_name,
+ (u_longlong_t)intval, (u_longlong_t)ans);
+ }
+ } else {
+ char *str_ans;
+ error = nvlist_lookup_string(retnvl, "value", &str_ans);
+ if (error != 0) {
+ (void) fprintf(stderr, "%s: zcp check error: "
+ "%u\n", prop_name, error);
+ return;
+ }
+ if (strcmp(strval, str_ans) != 0) {
+ (void) fprintf(stderr,
+ "%s: zfs found '%s', but zcp found '%s'\n",
+ prop_name, strval, str_ans);
+ }
+ }
+ } else {
+ (void) fprintf(stderr, "%s: zcp check failed, channel program "
+ "error: %u\n", prop_name, error);
+ }
+ nvlist_free(argnvl);
+ nvlist_free(outnvl);
+}
+
+/*
+ * Retrieve a property from the given object. If 'literal' is specified, then
+ * numbers are left as exact values. Otherwise, numbers are converted to a
+ * human-readable form.
+ *
+ * Returns 0 on success, or -1 on error.
+ */
+int
+zfs_prop_get(zfs_handle_t *zhp, zfs_prop_t prop, char *propbuf, size_t proplen,
+ zprop_source_t *src, char *statbuf, size_t statlen, boolean_t literal)
+{
+ char *source = NULL;
+ uint64_t val;
+ const char *str;
+ const char *strval;
+ boolean_t received = zfs_is_recvd_props_mode(zhp);
+
+ /*
+ * Check to see if this property applies to our object
+ */
+ if (!zfs_prop_valid_for_type(prop, zhp->zfs_type, B_FALSE))
+ return (-1);
+
+ if (received && zfs_prop_readonly(prop))
+ return (-1);
+
+ if (src)
+ *src = ZPROP_SRC_NONE;
+
+ switch (prop) {
+ case ZFS_PROP_CREATION:
+ /*
+ * 'creation' is a time_t stored in the statistics. We convert
+ * this into a string unless 'literal' is specified.
+ */
+ {
+ val = getprop_uint64(zhp, prop, &source);
+ time_t time = (time_t)val;
+ struct tm t;
+
+ if (literal ||
+ localtime_r(&time, &t) == NULL ||
+ strftime(propbuf, proplen, "%a %b %e %k:%M %Y",
+ &t) == 0)
+ (void) snprintf(propbuf, proplen, "%llu",
+ (u_longlong_t)val);
+ }
+ zcp_check(zhp, prop, val, NULL);
+ break;
+
+ case ZFS_PROP_MOUNTPOINT:
+ /*
+ * Getting the precise mountpoint can be tricky.
+ *
+ * - for 'none' or 'legacy', return those values.
+ * - for inherited mountpoints, we want to take everything
+ * after our ancestor and append it to the inherited value.
+ *
+ * If the pool has an alternate root, we want to prepend that
+ * root to any values we return.
+ */
+
+ str = getprop_string(zhp, prop, &source);
+
+ if (str[0] == '/') {
+ char buf[MAXPATHLEN];
+ char *root = buf;
+ const char *relpath;
+
+ /*
+ * If we inherit the mountpoint, even from a dataset
+ * with a received value, the source will be the path of
+ * the dataset we inherit from. If source is
+ * ZPROP_SOURCE_VAL_RECVD, the received value is not
+ * inherited.
+ */
+ if (strcmp(source, ZPROP_SOURCE_VAL_RECVD) == 0) {
+ relpath = "";
+ } else {
+ relpath = zhp->zfs_name + strlen(source);
+ if (relpath[0] == '/')
+ relpath++;
+ }
+
+ if ((zpool_get_prop(zhp->zpool_hdl,
+ ZPOOL_PROP_ALTROOT, buf, MAXPATHLEN, NULL,
+ B_FALSE)) || (strcmp(root, "-") == 0))
+ root[0] = '\0';
+ /*
+ * Special case an alternate root of '/'. This will
+ * avoid having multiple leading slashes in the
+ * mountpoint path.
+ */
+ if (strcmp(root, "/") == 0)
+ root++;
+
+ /*
+ * If the mountpoint is '/' then skip over this
+ * if we are obtaining either an alternate root or
+ * an inherited mountpoint.
+ */
+ if (str[1] == '\0' && (root[0] != '\0' ||
+ relpath[0] != '\0'))
+ str++;
+
+ if (relpath[0] == '\0')
+ (void) snprintf(propbuf, proplen, "%s%s",
+ root, str);
+ else
+ (void) snprintf(propbuf, proplen, "%s%s%s%s",
+ root, str, relpath[0] == '@' ? "" : "/",
+ relpath);
+ } else {
+ /* 'legacy' or 'none' */
+ (void) strlcpy(propbuf, str, proplen);
+ }
+ zcp_check(zhp, prop, 0, propbuf);
+ break;
+
+ case ZFS_PROP_ORIGIN:
+ str = getprop_string(zhp, prop, &source);
+ if (str == NULL)
+ return (-1);
+ (void) strlcpy(propbuf, str, proplen);
+ zcp_check(zhp, prop, 0, str);
+ break;
+
+ case ZFS_PROP_REDACT_SNAPS:
+ if (get_rsnaps_string(zhp, propbuf, proplen) != 0)
+ return (-1);
+ break;
+
+ case ZFS_PROP_CLONES:
+ if (get_clones_string(zhp, propbuf, proplen) != 0)
+ return (-1);
+ break;
+
+ case ZFS_PROP_QUOTA:
+ case ZFS_PROP_REFQUOTA:
+ case ZFS_PROP_RESERVATION:
+ case ZFS_PROP_REFRESERVATION:
+
+ if (get_numeric_property(zhp, prop, src, &source, &val) != 0)
+ return (-1);
+ /*
+ * If quota or reservation is 0, we translate this into 'none'
+ * (unless literal is set), and indicate that it's the default
+ * value. Otherwise, we print the number nicely and indicate
+ * that its set locally.
+ */
+ if (val == 0) {
+ if (literal)
+ (void) strlcpy(propbuf, "0", proplen);
+ else
+ (void) strlcpy(propbuf, "none", proplen);
+ } else {
+ if (literal)
+ (void) snprintf(propbuf, proplen, "%llu",
+ (u_longlong_t)val);
+ else
+ zfs_nicebytes(val, propbuf, proplen);
+ }
+ zcp_check(zhp, prop, val, NULL);
+ break;
+
+ case ZFS_PROP_FILESYSTEM_LIMIT:
+ case ZFS_PROP_SNAPSHOT_LIMIT:
+ case ZFS_PROP_FILESYSTEM_COUNT:
+ case ZFS_PROP_SNAPSHOT_COUNT:
+
+ if (get_numeric_property(zhp, prop, src, &source, &val) != 0)
+ return (-1);
+
+ /*
+ * If limit is UINT64_MAX, we translate this into 'none' (unless
+ * literal is set), and indicate that it's the default value.
+ * Otherwise, we print the number nicely and indicate that it's
+ * set locally.
+ */
+ if (literal) {
+ (void) snprintf(propbuf, proplen, "%llu",
+ (u_longlong_t)val);
+ } else if (val == UINT64_MAX) {
+ (void) strlcpy(propbuf, "none", proplen);
+ } else {
+ zfs_nicenum(val, propbuf, proplen);
+ }
+
+ zcp_check(zhp, prop, val, NULL);
+ break;
+
+ case ZFS_PROP_REFRATIO:
+ case ZFS_PROP_COMPRESSRATIO:
+ if (get_numeric_property(zhp, prop, src, &source, &val) != 0)
+ return (-1);
+ if (literal)
+ (void) snprintf(propbuf, proplen, "%llu.%02llu",
+ (u_longlong_t)(val / 100),
+ (u_longlong_t)(val % 100));
+ else
+ (void) snprintf(propbuf, proplen, "%llu.%02llux",
+ (u_longlong_t)(val / 100),
+ (u_longlong_t)(val % 100));
+ zcp_check(zhp, prop, val, NULL);
+ break;
+
+ case ZFS_PROP_TYPE:
+ switch (zhp->zfs_type) {
+ case ZFS_TYPE_FILESYSTEM:
+ str = "filesystem";
+ break;
+ case ZFS_TYPE_VOLUME:
+ str = "volume";
+ break;
+ case ZFS_TYPE_SNAPSHOT:
+ str = "snapshot";
+ break;
+ case ZFS_TYPE_BOOKMARK:
+ str = "bookmark";
+ break;
+ default:
+ abort();
+ }
+ (void) snprintf(propbuf, proplen, "%s", str);
+ zcp_check(zhp, prop, 0, propbuf);
+ break;
+
+ case ZFS_PROP_MOUNTED:
+ /*
+ * The 'mounted' property is a pseudo-property that described
+ * whether the filesystem is currently mounted. Even though
+ * it's a boolean value, the typical values of "on" and "off"
+ * don't make sense, so we translate to "yes" and "no".
+ */
+ if (get_numeric_property(zhp, ZFS_PROP_MOUNTED,
+ src, &source, &val) != 0)
+ return (-1);
+ if (val)
+ (void) strlcpy(propbuf, "yes", proplen);
+ else
+ (void) strlcpy(propbuf, "no", proplen);
+ break;
+
+ case ZFS_PROP_NAME:
+ /*
+ * The 'name' property is a pseudo-property derived from the
+ * dataset name. It is presented as a real property to simplify
+ * consumers.
+ */
+ (void) strlcpy(propbuf, zhp->zfs_name, proplen);
+ zcp_check(zhp, prop, 0, propbuf);
+ break;
+
+ case ZFS_PROP_MLSLABEL:
+ {
+#ifdef HAVE_MLSLABEL
+ m_label_t *new_sl = NULL;
+ char *ascii = NULL; /* human readable label */
+
+ (void) strlcpy(propbuf,
+ getprop_string(zhp, prop, &source), proplen);
+
+ if (literal || (strcasecmp(propbuf,
+ ZFS_MLSLABEL_DEFAULT) == 0))
+ break;
+
+ /*
+ * Try to translate the internal hex string to
+ * human-readable output. If there are any
+ * problems just use the hex string.
+ */
+
+ if (str_to_label(propbuf, &new_sl, MAC_LABEL,
+ L_NO_CORRECTION, NULL) == -1) {
+ m_label_free(new_sl);
+ break;
+ }
+
+ if (label_to_str(new_sl, &ascii, M_LABEL,
+ DEF_NAMES) != 0) {
+ if (ascii)
+ free(ascii);
+ m_label_free(new_sl);
+ break;
+ }
+ m_label_free(new_sl);
+
+ (void) strlcpy(propbuf, ascii, proplen);
+ free(ascii);
+#else
+ (void) strlcpy(propbuf,
+ getprop_string(zhp, prop, &source), proplen);
+#endif /* HAVE_MLSLABEL */
+ }
+ break;
+
+ case ZFS_PROP_GUID:
+ case ZFS_PROP_CREATETXG:
+ case ZFS_PROP_OBJSETID:
+ /*
+ * These properties are stored as numbers, but they are
+ * identifiers.
+ * We don't want them to be pretty printed, because pretty
+ * printing mangles the ID into a truncated and useless value.
+ */
+ if (get_numeric_property(zhp, prop, src, &source, &val) != 0)
+ return (-1);
+ (void) snprintf(propbuf, proplen, "%llu", (u_longlong_t)val);
+ zcp_check(zhp, prop, val, NULL);
+ break;
+
+ case ZFS_PROP_REFERENCED:
+ case ZFS_PROP_AVAILABLE:
+ case ZFS_PROP_USED:
+ case ZFS_PROP_USEDSNAP:
+ case ZFS_PROP_USEDDS:
+ case ZFS_PROP_USEDREFRESERV:
+ case ZFS_PROP_USEDCHILD:
+ if (get_numeric_property(zhp, prop, src, &source, &val) != 0)
+ return (-1);
+ if (literal) {
+ (void) snprintf(propbuf, proplen, "%llu",
+ (u_longlong_t)val);
+ } else {
+ zfs_nicebytes(val, propbuf, proplen);
+ }
+ zcp_check(zhp, prop, val, NULL);
+ break;
+
+ default:
+ switch (zfs_prop_get_type(prop)) {
+ case PROP_TYPE_NUMBER:
+ if (get_numeric_property(zhp, prop, src,
+ &source, &val) != 0) {
+ return (-1);
+ }
+
+ if (literal) {
+ (void) snprintf(propbuf, proplen, "%llu",
+ (u_longlong_t)val);
+ } else {
+ zfs_nicenum(val, propbuf, proplen);
+ }
+ zcp_check(zhp, prop, val, NULL);
+ break;
+
+ case PROP_TYPE_STRING:
+ str = getprop_string(zhp, prop, &source);
+ if (str == NULL)
+ return (-1);
+
+ (void) strlcpy(propbuf, str, proplen);
+ zcp_check(zhp, prop, 0, str);
+ break;
+
+ case PROP_TYPE_INDEX:
+ if (get_numeric_property(zhp, prop, src,
+ &source, &val) != 0)
+ return (-1);
+ if (zfs_prop_index_to_string(prop, val, &strval) != 0)
+ return (-1);
+
+ (void) strlcpy(propbuf, strval, proplen);
+ zcp_check(zhp, prop, 0, strval);
+ break;
+
+ default:
+ abort();
+ }
+ }
+
+ get_source(zhp, src, source, statbuf, statlen);
+
+ return (0);
+}
+
+/*
+ * Utility function to get the given numeric property. Does no validation that
+ * the given property is the appropriate type; should only be used with
+ * hard-coded property types.
+ */
+uint64_t
+zfs_prop_get_int(zfs_handle_t *zhp, zfs_prop_t prop)
+{
+ char *source;
+ uint64_t val = 0;
+
+ (void) get_numeric_property(zhp, prop, NULL, &source, &val);
+
+ return (val);
+}
+
+static int
+zfs_prop_set_int(zfs_handle_t *zhp, zfs_prop_t prop, uint64_t val)
+{
+ char buf[64];
+
+ (void) snprintf(buf, sizeof (buf), "%llu", (longlong_t)val);
+ return (zfs_prop_set(zhp, zfs_prop_to_name(prop), buf));
+}
+
+/*
+ * Similar to zfs_prop_get(), but returns the value as an integer.
+ */
+int
+zfs_prop_get_numeric(zfs_handle_t *zhp, zfs_prop_t prop, uint64_t *value,
+ zprop_source_t *src, char *statbuf, size_t statlen)
+{
+ char *source;
+
+ /*
+ * Check to see if this property applies to our object
+ */
+ if (!zfs_prop_valid_for_type(prop, zhp->zfs_type, B_FALSE)) {
+ return (zfs_error_fmt(zhp->zfs_hdl, EZFS_PROPTYPE,
+ dgettext(TEXT_DOMAIN, "cannot get property '%s'"),
+ zfs_prop_to_name(prop)));
+ }
+
+ if (src)
+ *src = ZPROP_SRC_NONE;
+
+ if (get_numeric_property(zhp, prop, src, &source, value) != 0)
+ return (-1);
+
+ get_source(zhp, src, source, statbuf, statlen);
+
+ return (0);
+}
+
+#ifdef HAVE_IDMAP
+static int
+idmap_id_to_numeric_domain_rid(uid_t id, boolean_t isuser,
+ char **domainp, idmap_rid_t *ridp)
+{
+ idmap_get_handle_t *get_hdl = NULL;
+ idmap_stat status;
+ int err = EINVAL;
+
+ if (idmap_get_create(&get_hdl) != IDMAP_SUCCESS)
+ goto out;
+
+ if (isuser) {
+ err = idmap_get_sidbyuid(get_hdl, id,
+ IDMAP_REQ_FLG_USE_CACHE, domainp, ridp, &status);
+ } else {
+ err = idmap_get_sidbygid(get_hdl, id,
+ IDMAP_REQ_FLG_USE_CACHE, domainp, ridp, &status);
+ }
+ if (err == IDMAP_SUCCESS &&
+ idmap_get_mappings(get_hdl) == IDMAP_SUCCESS &&
+ status == IDMAP_SUCCESS)
+ err = 0;
+ else
+ err = EINVAL;
+out:
+ if (get_hdl)
+ idmap_get_destroy(get_hdl);
+ return (err);
+}
+#endif /* HAVE_IDMAP */
+
+/*
+ * convert the propname into parameters needed by kernel
+ * Eg: userquota@ahrens -> ZFS_PROP_USERQUOTA, "", 126829
+ * Eg: userused@matt@domain -> ZFS_PROP_USERUSED, "S-1-123-456", 789
+ * Eg: groupquota@staff -> ZFS_PROP_GROUPQUOTA, "", 1234
+ * Eg: groupused@staff -> ZFS_PROP_GROUPUSED, "", 1234
+ * Eg: projectquota@123 -> ZFS_PROP_PROJECTQUOTA, "", 123
+ * Eg: projectused@789 -> ZFS_PROP_PROJECTUSED, "", 789
+ */
+static int
+userquota_propname_decode(const char *propname, boolean_t zoned,
+ zfs_userquota_prop_t *typep, char *domain, int domainlen, uint64_t *ridp)
+{
+ zfs_userquota_prop_t type;
+ char *cp;
+ boolean_t isuser;
+ boolean_t isgroup;
+ boolean_t isproject;
+ struct passwd *pw;
+ struct group *gr;
+
+ domain[0] = '\0';
+
+ /* Figure out the property type ({user|group|project}{quota|space}) */
+ for (type = 0; type < ZFS_NUM_USERQUOTA_PROPS; type++) {
+ if (strncmp(propname, zfs_userquota_prop_prefixes[type],
+ strlen(zfs_userquota_prop_prefixes[type])) == 0)
+ break;
+ }
+ if (type == ZFS_NUM_USERQUOTA_PROPS)
+ return (EINVAL);
+ *typep = type;
+
+ isuser = (type == ZFS_PROP_USERQUOTA || type == ZFS_PROP_USERUSED ||
+ type == ZFS_PROP_USEROBJQUOTA ||
+ type == ZFS_PROP_USEROBJUSED);
+ isgroup = (type == ZFS_PROP_GROUPQUOTA || type == ZFS_PROP_GROUPUSED ||
+ type == ZFS_PROP_GROUPOBJQUOTA ||
+ type == ZFS_PROP_GROUPOBJUSED);
+ isproject = (type == ZFS_PROP_PROJECTQUOTA ||
+ type == ZFS_PROP_PROJECTUSED || type == ZFS_PROP_PROJECTOBJQUOTA ||
+ type == ZFS_PROP_PROJECTOBJUSED);
+
+ cp = strchr(propname, '@') + 1;
+
+ if (isuser && (pw = getpwnam(cp)) != NULL) {
+ if (zoned && getzoneid() == GLOBAL_ZONEID)
+ return (ENOENT);
+ *ridp = pw->pw_uid;
+ } else if (isgroup && (gr = getgrnam(cp)) != NULL) {
+ if (zoned && getzoneid() == GLOBAL_ZONEID)
+ return (ENOENT);
+ *ridp = gr->gr_gid;
+ } else if (!isproject && strchr(cp, '@')) {
+#ifdef HAVE_IDMAP
+ /*
+ * It's a SID name (eg "user@domain") that needs to be
+ * turned into S-1-domainID-RID.
+ */
+ directory_error_t e;
+ char *numericsid = NULL;
+ char *end;
+
+ if (zoned && getzoneid() == GLOBAL_ZONEID)
+ return (ENOENT);
+ if (isuser) {
+ e = directory_sid_from_user_name(NULL,
+ cp, &numericsid);
+ } else {
+ e = directory_sid_from_group_name(NULL,
+ cp, &numericsid);
+ }
+ if (e != NULL) {
+ directory_error_free(e);
+ return (ENOENT);
+ }
+ if (numericsid == NULL)
+ return (ENOENT);
+ cp = numericsid;
+ (void) strlcpy(domain, cp, domainlen);
+ cp = strrchr(domain, '-');
+ *cp = '\0';
+ cp++;
+
+ errno = 0;
+ *ridp = strtoull(cp, &end, 10);
+ free(numericsid);
+
+ if (errno != 0 || *end != '\0')
+ return (EINVAL);
+#else
+ return (ENOSYS);
+#endif /* HAVE_IDMAP */
+ } else {
+ /* It's a user/group/project ID (eg "12345"). */
+ uid_t id;
+ char *end;
+ id = strtoul(cp, &end, 10);
+ if (*end != '\0')
+ return (EINVAL);
+ if (id > MAXUID && !isproject) {
+#ifdef HAVE_IDMAP
+ /* It's an ephemeral ID. */
+ idmap_rid_t rid;
+ char *mapdomain;
+
+ if (idmap_id_to_numeric_domain_rid(id, isuser,
+ &mapdomain, &rid) != 0)
+ return (ENOENT);
+ (void) strlcpy(domain, mapdomain, domainlen);
+ *ridp = rid;
+#else
+ return (ENOSYS);
+#endif /* HAVE_IDMAP */
+ } else {
+ *ridp = id;
+ }
+ }
+
+ return (0);
+}
+
+static int
+zfs_prop_get_userquota_common(zfs_handle_t *zhp, const char *propname,
+ uint64_t *propvalue, zfs_userquota_prop_t *typep)
+{
+ int err;
+ zfs_cmd_t zc = {"\0"};
+
+ (void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name));
+
+ err = userquota_propname_decode(propname,
+ zfs_prop_get_int(zhp, ZFS_PROP_ZONED),
+ typep, zc.zc_value, sizeof (zc.zc_value), &zc.zc_guid);
+ zc.zc_objset_type = *typep;
+ if (err)
+ return (err);
+
+ err = zfs_ioctl(zhp->zfs_hdl, ZFS_IOC_USERSPACE_ONE, &zc);
+ if (err)
+ return (err);
+
+ *propvalue = zc.zc_cookie;
+ return (0);
+}
+
+int
+zfs_prop_get_userquota_int(zfs_handle_t *zhp, const char *propname,
+ uint64_t *propvalue)
+{
+ zfs_userquota_prop_t type;
+
+ return (zfs_prop_get_userquota_common(zhp, propname, propvalue,
+ &type));
+}
+
+int
+zfs_prop_get_userquota(zfs_handle_t *zhp, const char *propname,
+ char *propbuf, int proplen, boolean_t literal)
+{
+ int err;
+ uint64_t propvalue;
+ zfs_userquota_prop_t type;
+
+ err = zfs_prop_get_userquota_common(zhp, propname, &propvalue,
+ &type);
+
+ if (err)
+ return (err);
+
+ if (literal) {
+ (void) snprintf(propbuf, proplen, "%llu",
+ (u_longlong_t)propvalue);
+ } else if (propvalue == 0 &&
+ (type == ZFS_PROP_USERQUOTA || type == ZFS_PROP_GROUPQUOTA ||
+ type == ZFS_PROP_USEROBJQUOTA || type == ZFS_PROP_GROUPOBJQUOTA ||
+ type == ZFS_PROP_PROJECTQUOTA ||
+ type == ZFS_PROP_PROJECTOBJQUOTA)) {
+ (void) strlcpy(propbuf, "none", proplen);
+ } else if (type == ZFS_PROP_USERQUOTA || type == ZFS_PROP_GROUPQUOTA ||
+ type == ZFS_PROP_USERUSED || type == ZFS_PROP_GROUPUSED ||
+ type == ZFS_PROP_PROJECTUSED || type == ZFS_PROP_PROJECTQUOTA) {
+ zfs_nicebytes(propvalue, propbuf, proplen);
+ } else {
+ zfs_nicenum(propvalue, propbuf, proplen);
+ }
+ return (0);
+}
+
+/*
+ * propname must start with "written@" or "written#".
+ */
+int
+zfs_prop_get_written_int(zfs_handle_t *zhp, const char *propname,
+ uint64_t *propvalue)
+{
+ int err;
+ zfs_cmd_t zc = {"\0"};
+ const char *snapname;
+
+ (void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name));
+
+ assert(zfs_prop_written(propname));
+ snapname = propname + strlen("written@");
+ if (strchr(snapname, '@') != NULL || strchr(snapname, '#') != NULL) {
+ /* full snapshot or bookmark name specified */
+ (void) strlcpy(zc.zc_value, snapname, sizeof (zc.zc_value));
+ } else {
+ /* snapname is the short name, append it to zhp's fsname */
+ char *cp;
+
+ (void) strlcpy(zc.zc_value, zhp->zfs_name,
+ sizeof (zc.zc_value));
+ cp = strchr(zc.zc_value, '@');
+ if (cp != NULL)
+ *cp = '\0';
+ (void) strlcat(zc.zc_value, snapname - 1, sizeof (zc.zc_value));
+ }
+
+ err = zfs_ioctl(zhp->zfs_hdl, ZFS_IOC_SPACE_WRITTEN, &zc);
+ if (err)
+ return (err);
+
+ *propvalue = zc.zc_cookie;
+ return (0);
+}
+
+int
+zfs_prop_get_written(zfs_handle_t *zhp, const char *propname,
+ char *propbuf, int proplen, boolean_t literal)
+{
+ int err;
+ uint64_t propvalue;
+
+ err = zfs_prop_get_written_int(zhp, propname, &propvalue);
+
+ if (err)
+ return (err);
+
+ if (literal) {
+ (void) snprintf(propbuf, proplen, "%llu",
+ (u_longlong_t)propvalue);
+ } else {
+ zfs_nicebytes(propvalue, propbuf, proplen);
+ }
+
+ return (0);
+}
+
+/*
+ * Returns the name of the given zfs handle.
+ */
+const char *
+zfs_get_name(const zfs_handle_t *zhp)
+{
+ return (zhp->zfs_name);
+}
+
+/*
+ * Returns the name of the parent pool for the given zfs handle.
+ */
+const char *
+zfs_get_pool_name(const zfs_handle_t *zhp)
+{
+ return (zhp->zpool_hdl->zpool_name);
+}
+
+/*
+ * Returns the type of the given zfs handle.
+ */
+zfs_type_t
+zfs_get_type(const zfs_handle_t *zhp)
+{
+ return (zhp->zfs_type);
+}
+
+/*
+ * Is one dataset name a child dataset of another?
+ *
+ * Needs to handle these cases:
+ * Dataset 1 "a/foo" "a/foo" "a/foo" "a/foo"
+ * Dataset 2 "a/fo" "a/foobar" "a/bar/baz" "a/foo/bar"
+ * Descendant? No. No. No. Yes.
+ */
+static boolean_t
+is_descendant(const char *ds1, const char *ds2)
+{
+ size_t d1len = strlen(ds1);
+
+ /* ds2 can't be a descendant if it's smaller */
+ if (strlen(ds2) < d1len)
+ return (B_FALSE);
+
+ /* otherwise, compare strings and verify that there's a '/' char */
+ return (ds2[d1len] == '/' && (strncmp(ds1, ds2, d1len) == 0));
+}
+
+/*
+ * Given a complete name, return just the portion that refers to the parent.
+ * Will return -1 if there is no parent (path is just the name of the
+ * pool).
+ */
+static int
+parent_name(const char *path, char *buf, size_t buflen)
+{
+ char *slashp;
+
+ (void) strlcpy(buf, path, buflen);
+
+ if ((slashp = strrchr(buf, '/')) == NULL)
+ return (-1);
+ *slashp = '\0';
+
+ return (0);
+}
+
+int
+zfs_parent_name(zfs_handle_t *zhp, char *buf, size_t buflen)
+{
+ return (parent_name(zfs_get_name(zhp), buf, buflen));
+}
+
+/*
+ * If accept_ancestor is false, then check to make sure that the given path has
+ * a parent, and that it exists. If accept_ancestor is true, then find the
+ * closest existing ancestor for the given path. In prefixlen return the
+ * length of already existing prefix of the given path. We also fetch the
+ * 'zoned' property, which is used to validate property settings when creating
+ * new datasets.
+ */
+static int
+check_parents(libzfs_handle_t *hdl, const char *path, uint64_t *zoned,
+ boolean_t accept_ancestor, int *prefixlen)
+{
+ zfs_cmd_t zc = {"\0"};
+ char parent[ZFS_MAX_DATASET_NAME_LEN];
+ char *slash;
+ zfs_handle_t *zhp;
+ char errbuf[1024];
+ uint64_t is_zoned;
+
+ (void) snprintf(errbuf, sizeof (errbuf),
+ dgettext(TEXT_DOMAIN, "cannot create '%s'"), path);
+
+ /* get parent, and check to see if this is just a pool */
+ if (parent_name(path, parent, sizeof (parent)) != 0) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "missing dataset name"));
+ return (zfs_error(hdl, EZFS_INVALIDNAME, errbuf));
+ }
+
+ /* check to see if the pool exists */
+ if ((slash = strchr(parent, '/')) == NULL)
+ slash = parent + strlen(parent);
+ (void) strncpy(zc.zc_name, parent, slash - parent);
+ zc.zc_name[slash - parent] = '\0';
+ if (zfs_ioctl(hdl, ZFS_IOC_OBJSET_STATS, &zc) != 0 &&
+ errno == ENOENT) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "no such pool '%s'"), zc.zc_name);
+ return (zfs_error(hdl, EZFS_NOENT, errbuf));
+ }
+
+ /* check to see if the parent dataset exists */
+ while ((zhp = make_dataset_handle(hdl, parent)) == NULL) {
+ if (errno == ENOENT && accept_ancestor) {
+ /*
+ * Go deeper to find an ancestor, give up on top level.
+ */
+ if (parent_name(parent, parent, sizeof (parent)) != 0) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "no such pool '%s'"), zc.zc_name);
+ return (zfs_error(hdl, EZFS_NOENT, errbuf));
+ }
+ } else if (errno == ENOENT) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "parent does not exist"));
+ return (zfs_error(hdl, EZFS_NOENT, errbuf));
+ } else
+ return (zfs_standard_error(hdl, errno, errbuf));
+ }
+
+ is_zoned = zfs_prop_get_int(zhp, ZFS_PROP_ZONED);
+ if (zoned != NULL)
+ *zoned = is_zoned;
+
+ /* we are in a non-global zone, but parent is in the global zone */
+ if (getzoneid() != GLOBAL_ZONEID && !is_zoned) {
+ (void) zfs_standard_error(hdl, EPERM, errbuf);
+ zfs_close(zhp);
+ return (-1);
+ }
+
+ /* make sure parent is a filesystem */
+ if (zfs_get_type(zhp) != ZFS_TYPE_FILESYSTEM) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "parent is not a filesystem"));
+ (void) zfs_error(hdl, EZFS_BADTYPE, errbuf);
+ zfs_close(zhp);
+ return (-1);
+ }
+
+ zfs_close(zhp);
+ if (prefixlen != NULL)
+ *prefixlen = strlen(parent);
+ return (0);
+}
+
+/*
+ * Finds whether the dataset of the given type(s) exists.
+ */
+boolean_t
+zfs_dataset_exists(libzfs_handle_t *hdl, const char *path, zfs_type_t types)
+{
+ zfs_handle_t *zhp;
+
+ if (!zfs_validate_name(hdl, path, types, B_FALSE))
+ return (B_FALSE);
+
+ /*
+ * Try to get stats for the dataset, which will tell us if it exists.
+ */
+ if ((zhp = make_dataset_handle(hdl, path)) != NULL) {
+ int ds_type = zhp->zfs_type;
+
+ zfs_close(zhp);
+ if (types & ds_type)
+ return (B_TRUE);
+ }
+ return (B_FALSE);
+}
+
+/*
+ * Given a path to 'target', create all the ancestors between
+ * the prefixlen portion of the path, and the target itself.
+ * Fail if the initial prefixlen-ancestor does not already exist.
+ */
+int
+create_parents(libzfs_handle_t *hdl, char *target, int prefixlen)
+{
+ zfs_handle_t *h;
+ char *cp;
+ const char *opname;
+
+ /* make sure prefix exists */
+ cp = target + prefixlen;
+ if (*cp != '/') {
+ assert(strchr(cp, '/') == NULL);
+ h = zfs_open(hdl, target, ZFS_TYPE_FILESYSTEM);
+ } else {
+ *cp = '\0';
+ h = zfs_open(hdl, target, ZFS_TYPE_FILESYSTEM);
+ *cp = '/';
+ }
+ if (h == NULL)
+ return (-1);
+ zfs_close(h);
+
+ /*
+ * Attempt to create, mount, and share any ancestor filesystems,
+ * up to the prefixlen-long one.
+ */
+ for (cp = target + prefixlen + 1;
+ (cp = strchr(cp, '/')) != NULL; *cp = '/', cp++) {
+
+ *cp = '\0';
+
+ h = make_dataset_handle(hdl, target);
+ if (h) {
+ /* it already exists, nothing to do here */
+ zfs_close(h);
+ continue;
+ }
+
+ if (zfs_create(hdl, target, ZFS_TYPE_FILESYSTEM,
+ NULL) != 0) {
+ opname = dgettext(TEXT_DOMAIN, "create");
+ goto ancestorerr;
+ }
+
+ h = zfs_open(hdl, target, ZFS_TYPE_FILESYSTEM);
+ if (h == NULL) {
+ opname = dgettext(TEXT_DOMAIN, "open");
+ goto ancestorerr;
+ }
+
+ if (zfs_mount(h, NULL, 0) != 0) {
+ opname = dgettext(TEXT_DOMAIN, "mount");
+ goto ancestorerr;
+ }
+
+ if (zfs_share(h) != 0) {
+ opname = dgettext(TEXT_DOMAIN, "share");
+ goto ancestorerr;
+ }
+
+ zfs_close(h);
+ }
+ zfs_commit_all_shares();
+
+ return (0);
+
+ancestorerr:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "failed to %s ancestor '%s'"), opname, target);
+ return (-1);
+}
+
+/*
+ * Creates non-existing ancestors of the given path.
+ */
+int
+zfs_create_ancestors(libzfs_handle_t *hdl, const char *path)
+{
+ int prefix;
+ char *path_copy;
+ char errbuf[1024];
+ int rc = 0;
+
+ (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
+ "cannot create '%s'"), path);
+
+ /*
+ * Check that we are not passing the nesting limit
+ * before we start creating any ancestors.
+ */
+ if (dataset_nestcheck(path) != 0) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "maximum name nesting depth exceeded"));
+ return (zfs_error(hdl, EZFS_INVALIDNAME, errbuf));
+ }
+
+ if (check_parents(hdl, path, NULL, B_TRUE, &prefix) != 0)
+ return (-1);
+
+ if ((path_copy = strdup(path)) != NULL) {
+ rc = create_parents(hdl, path_copy, prefix);
+ free(path_copy);
+ }
+ if (path_copy == NULL || rc != 0)
+ return (-1);
+
+ return (0);
+}
+
+/*
+ * Create a new filesystem or volume.
+ */
+int
+zfs_create(libzfs_handle_t *hdl, const char *path, zfs_type_t type,
+ nvlist_t *props)
+{
+ int ret;
+ uint64_t size = 0;
+ uint64_t blocksize = zfs_prop_default_numeric(ZFS_PROP_VOLBLOCKSIZE);
+ uint64_t zoned;
+ enum lzc_dataset_type ost;
+ zpool_handle_t *zpool_handle;
+ uint8_t *wkeydata = NULL;
+ uint_t wkeylen = 0;
+ char errbuf[1024];
+ char parent[ZFS_MAX_DATASET_NAME_LEN];
+
+ (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
+ "cannot create '%s'"), path);
+
+ /* validate the path, taking care to note the extended error message */
+ if (!zfs_validate_name(hdl, path, type, B_TRUE))
+ return (zfs_error(hdl, EZFS_INVALIDNAME, errbuf));
+
+ if (dataset_nestcheck(path) != 0) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "maximum name nesting depth exceeded"));
+ return (zfs_error(hdl, EZFS_INVALIDNAME, errbuf));
+ }
+
+ /* validate parents exist */
+ if (check_parents(hdl, path, &zoned, B_FALSE, NULL) != 0)
+ return (-1);
+
+ /*
+ * The failure modes when creating a dataset of a different type over
+ * one that already exists is a little strange. In particular, if you
+ * try to create a dataset on top of an existing dataset, the ioctl()
+ * will return ENOENT, not EEXIST. To prevent this from happening, we
+ * first try to see if the dataset exists.
+ */
+ if (zfs_dataset_exists(hdl, path, ZFS_TYPE_DATASET)) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "dataset already exists"));
+ return (zfs_error(hdl, EZFS_EXISTS, errbuf));
+ }
+
+ if (type == ZFS_TYPE_VOLUME)
+ ost = LZC_DATSET_TYPE_ZVOL;
+ else
+ ost = LZC_DATSET_TYPE_ZFS;
+
+ /* open zpool handle for prop validation */
+ char pool_path[ZFS_MAX_DATASET_NAME_LEN];
+ (void) strlcpy(pool_path, path, sizeof (pool_path));
+
+ /* truncate pool_path at first slash */
+ char *p = strchr(pool_path, '/');
+ if (p != NULL)
+ *p = '\0';
+
+ if ((zpool_handle = zpool_open(hdl, pool_path)) == NULL)
+ return (-1);
+
+ if (props && (props = zfs_valid_proplist(hdl, type, props,
+ zoned, NULL, zpool_handle, B_TRUE, errbuf)) == 0) {
+ zpool_close(zpool_handle);
+ return (-1);
+ }
+ zpool_close(zpool_handle);
+
+ if (type == ZFS_TYPE_VOLUME) {
+ /*
+ * If we are creating a volume, the size and block size must
+ * satisfy a few restraints. First, the blocksize must be a
+ * valid block size between SPA_{MIN,MAX}BLOCKSIZE. Second, the
+ * volsize must be a multiple of the block size, and cannot be
+ * zero.
+ */
+ if (props == NULL || nvlist_lookup_uint64(props,
+ zfs_prop_to_name(ZFS_PROP_VOLSIZE), &size) != 0) {
+ nvlist_free(props);
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "missing volume size"));
+ return (zfs_error(hdl, EZFS_BADPROP, errbuf));
+ }
+
+ if ((ret = nvlist_lookup_uint64(props,
+ zfs_prop_to_name(ZFS_PROP_VOLBLOCKSIZE),
+ &blocksize)) != 0) {
+ if (ret == ENOENT) {
+ blocksize = zfs_prop_default_numeric(
+ ZFS_PROP_VOLBLOCKSIZE);
+ } else {
+ nvlist_free(props);
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "missing volume block size"));
+ return (zfs_error(hdl, EZFS_BADPROP, errbuf));
+ }
+ }
+
+ if (size == 0) {
+ nvlist_free(props);
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "volume size cannot be zero"));
+ return (zfs_error(hdl, EZFS_BADPROP, errbuf));
+ }
+
+ if (size % blocksize != 0) {
+ nvlist_free(props);
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "volume size must be a multiple of volume block "
+ "size"));
+ return (zfs_error(hdl, EZFS_BADPROP, errbuf));
+ }
+ }
+
+ (void) parent_name(path, parent, sizeof (parent));
+ if (zfs_crypto_create(hdl, parent, props, NULL, B_TRUE,
+ &wkeydata, &wkeylen) != 0) {
+ nvlist_free(props);
+ return (zfs_error(hdl, EZFS_CRYPTOFAILED, errbuf));
+ }
+
+ /* create the dataset */
+ ret = lzc_create(path, ost, props, wkeydata, wkeylen);
+ nvlist_free(props);
+ if (wkeydata != NULL)
+ free(wkeydata);
+
+ /* check for failure */
+ if (ret != 0) {
+ switch (errno) {
+ case ENOENT:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "no such parent '%s'"), parent);
+ return (zfs_error(hdl, EZFS_NOENT, errbuf));
+
+ case ENOTSUP:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "pool must be upgraded to set this "
+ "property or value"));
+ return (zfs_error(hdl, EZFS_BADVERSION, errbuf));
+
+ case EACCES:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "encryption root's key is not loaded "
+ "or provided"));
+ return (zfs_error(hdl, EZFS_CRYPTOFAILED, errbuf));
+
+ case ERANGE:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "invalid property value(s) specified"));
+ return (zfs_error(hdl, EZFS_BADPROP, errbuf));
+#ifdef _ILP32
+ case EOVERFLOW:
+ /*
+ * This platform can't address a volume this big.
+ */
+ if (type == ZFS_TYPE_VOLUME)
+ return (zfs_error(hdl, EZFS_VOLTOOBIG,
+ errbuf));
+#endif
+ /* FALLTHROUGH */
+ default:
+ return (zfs_standard_error(hdl, errno, errbuf));
+ }
+ }
+
+ return (0);
+}
+
+/*
+ * Destroys the given dataset. The caller must make sure that the filesystem
+ * isn't mounted, and that there are no active dependents. If the file system
+ * does not exist this function does nothing.
+ */
+int
+zfs_destroy(zfs_handle_t *zhp, boolean_t defer)
+{
+ int error;
+
+ if (zhp->zfs_type != ZFS_TYPE_SNAPSHOT && defer)
+ return (EINVAL);
+
+ if (zhp->zfs_type == ZFS_TYPE_BOOKMARK) {
+ nvlist_t *nv = fnvlist_alloc();
+ fnvlist_add_boolean(nv, zhp->zfs_name);
+ error = lzc_destroy_bookmarks(nv, NULL);
+ fnvlist_free(nv);
+ if (error != 0) {
+ return (zfs_standard_error_fmt(zhp->zfs_hdl, error,
+ dgettext(TEXT_DOMAIN, "cannot destroy '%s'"),
+ zhp->zfs_name));
+ }
+ return (0);
+ }
+
+ if (zhp->zfs_type == ZFS_TYPE_SNAPSHOT) {
+ nvlist_t *nv = fnvlist_alloc();
+ fnvlist_add_boolean(nv, zhp->zfs_name);
+ error = lzc_destroy_snaps(nv, defer, NULL);
+ fnvlist_free(nv);
+ } else {
+ error = lzc_destroy(zhp->zfs_name);
+ }
+
+ if (error != 0 && error != ENOENT) {
+ return (zfs_standard_error_fmt(zhp->zfs_hdl, errno,
+ dgettext(TEXT_DOMAIN, "cannot destroy '%s'"),
+ zhp->zfs_name));
+ }
+
+ remove_mountpoint(zhp);
+
+ return (0);
+}
+
+struct destroydata {
+ nvlist_t *nvl;
+ const char *snapname;
+};
+
+static int
+zfs_check_snap_cb(zfs_handle_t *zhp, void *arg)
+{
+ struct destroydata *dd = arg;
+ char name[ZFS_MAX_DATASET_NAME_LEN];
+ int rv = 0;
+
+ if (snprintf(name, sizeof (name), "%s@%s", zhp->zfs_name,
+ dd->snapname) >= sizeof (name))
+ return (EINVAL);
+
+ if (lzc_exists(name))
+ verify(nvlist_add_boolean(dd->nvl, name) == 0);
+
+ rv = zfs_iter_filesystems(zhp, zfs_check_snap_cb, dd);
+ zfs_close(zhp);
+ return (rv);
+}
+
+/*
+ * Destroys all snapshots with the given name in zhp & descendants.
+ */
+int
+zfs_destroy_snaps(zfs_handle_t *zhp, char *snapname, boolean_t defer)
+{
+ int ret;
+ struct destroydata dd = { 0 };
+
+ dd.snapname = snapname;
+ verify(nvlist_alloc(&dd.nvl, NV_UNIQUE_NAME, 0) == 0);
+ (void) zfs_check_snap_cb(zfs_handle_dup(zhp), &dd);
+
+ if (nvlist_empty(dd.nvl)) {
+ ret = zfs_standard_error_fmt(zhp->zfs_hdl, ENOENT,
+ dgettext(TEXT_DOMAIN, "cannot destroy '%s@%s'"),
+ zhp->zfs_name, snapname);
+ } else {
+ ret = zfs_destroy_snaps_nvl(zhp->zfs_hdl, dd.nvl, defer);
+ }
+ nvlist_free(dd.nvl);
+ return (ret);
+}
+
+/*
+ * Destroys all the snapshots named in the nvlist.
+ */
+int
+zfs_destroy_snaps_nvl(libzfs_handle_t *hdl, nvlist_t *snaps, boolean_t defer)
+{
+ int ret;
+ nvlist_t *errlist = NULL;
+ nvpair_t *pair;
+
+ ret = lzc_destroy_snaps(snaps, defer, &errlist);
+
+ if (ret == 0) {
+ nvlist_free(errlist);
+ return (0);
+ }
+
+ if (nvlist_empty(errlist)) {
+ char errbuf[1024];
+ (void) snprintf(errbuf, sizeof (errbuf),
+ dgettext(TEXT_DOMAIN, "cannot destroy snapshots"));
+
+ ret = zfs_standard_error(hdl, ret, errbuf);
+ }
+ for (pair = nvlist_next_nvpair(errlist, NULL);
+ pair != NULL; pair = nvlist_next_nvpair(errlist, pair)) {
+ char errbuf[1024];
+ (void) snprintf(errbuf, sizeof (errbuf),
+ dgettext(TEXT_DOMAIN, "cannot destroy snapshot %s"),
+ nvpair_name(pair));
+
+ switch (fnvpair_value_int32(pair)) {
+ case EEXIST:
+ zfs_error_aux(hdl,
+ dgettext(TEXT_DOMAIN, "snapshot is cloned"));
+ ret = zfs_error(hdl, EZFS_EXISTS, errbuf);
+ break;
+ default:
+ ret = zfs_standard_error(hdl, errno, errbuf);
+ break;
+ }
+ }
+
+ nvlist_free(errlist);
+ return (ret);
+}
+
+/*
+ * Clones the given dataset. The target must be of the same type as the source.
+ */
+int
+zfs_clone(zfs_handle_t *zhp, const char *target, nvlist_t *props)
+{
+ char parent[ZFS_MAX_DATASET_NAME_LEN];
+ int ret;
+ char errbuf[1024];
+ libzfs_handle_t *hdl = zhp->zfs_hdl;
+ uint64_t zoned;
+
+ assert(zhp->zfs_type == ZFS_TYPE_SNAPSHOT);
+
+ (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
+ "cannot create '%s'"), target);
+
+ /* validate the target/clone name */
+ if (!zfs_validate_name(hdl, target, ZFS_TYPE_FILESYSTEM, B_TRUE))
+ return (zfs_error(hdl, EZFS_INVALIDNAME, errbuf));
+
+ /* validate parents exist */
+ if (check_parents(hdl, target, &zoned, B_FALSE, NULL) != 0)
+ return (-1);
+
+ (void) parent_name(target, parent, sizeof (parent));
+
+ /* do the clone */
+
+ if (props) {
+ zfs_type_t type;
+
+ if (ZFS_IS_VOLUME(zhp)) {
+ type = ZFS_TYPE_VOLUME;
+ } else {
+ type = ZFS_TYPE_FILESYSTEM;
+ }
+ if ((props = zfs_valid_proplist(hdl, type, props, zoned,
+ zhp, zhp->zpool_hdl, B_TRUE, errbuf)) == NULL)
+ return (-1);
+ if (zfs_fix_auto_resv(zhp, props) == -1) {
+ nvlist_free(props);
+ return (-1);
+ }
+ }
+
+ if (zfs_crypto_clone_check(hdl, zhp, parent, props) != 0) {
+ nvlist_free(props);
+ return (zfs_error(hdl, EZFS_CRYPTOFAILED, errbuf));
+ }
+
+ ret = lzc_clone(target, zhp->zfs_name, props);
+ nvlist_free(props);
+
+ if (ret != 0) {
+ switch (errno) {
+
+ case ENOENT:
+ /*
+ * The parent doesn't exist. We should have caught this
+ * above, but there may a race condition that has since
+ * destroyed the parent.
+ *
+ * At this point, we don't know whether it's the source
+ * that doesn't exist anymore, or whether the target
+ * dataset doesn't exist.
+ */
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "no such parent '%s'"), parent);
+ return (zfs_error(zhp->zfs_hdl, EZFS_NOENT, errbuf));
+
+ case EXDEV:
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "source and target pools differ"));
+ return (zfs_error(zhp->zfs_hdl, EZFS_CROSSTARGET,
+ errbuf));
+
+ default:
+ return (zfs_standard_error(zhp->zfs_hdl, errno,
+ errbuf));
+ }
+ }
+
+ return (ret);
+}
+
+/*
+ * Promotes the given clone fs to be the clone parent.
+ */
+int
+zfs_promote(zfs_handle_t *zhp)
+{
+ libzfs_handle_t *hdl = zhp->zfs_hdl;
+ char snapname[ZFS_MAX_DATASET_NAME_LEN];
+ int ret;
+ char errbuf[1024];
+
+ (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
+ "cannot promote '%s'"), zhp->zfs_name);
+
+ if (zhp->zfs_type == ZFS_TYPE_SNAPSHOT) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "snapshots can not be promoted"));
+ return (zfs_error(hdl, EZFS_BADTYPE, errbuf));
+ }
+
+ if (zhp->zfs_dmustats.dds_origin[0] == '\0') {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "not a cloned filesystem"));
+ return (zfs_error(hdl, EZFS_BADTYPE, errbuf));
+ }
+
+ if (!zfs_validate_name(hdl, zhp->zfs_name, zhp->zfs_type, B_TRUE))
+ return (zfs_error(hdl, EZFS_INVALIDNAME, errbuf));
+
+ ret = lzc_promote(zhp->zfs_name, snapname, sizeof (snapname));
+
+ if (ret != 0) {
+ switch (ret) {
+ case EACCES:
+ /*
+ * Promoting encrypted dataset outside its
+ * encryption root.
+ */
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "cannot promote dataset outside its "
+ "encryption root"));
+ return (zfs_error(hdl, EZFS_EXISTS, errbuf));
+
+ case EEXIST:
+ /* There is a conflicting snapshot name. */
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "conflicting snapshot '%s' from parent '%s'"),
+ snapname, zhp->zfs_dmustats.dds_origin);
+ return (zfs_error(hdl, EZFS_EXISTS, errbuf));
+
+ default:
+ return (zfs_standard_error(hdl, ret, errbuf));
+ }
+ }
+ return (ret);
+}
+
+typedef struct snapdata {
+ nvlist_t *sd_nvl;
+ const char *sd_snapname;
+} snapdata_t;
+
+static int
+zfs_snapshot_cb(zfs_handle_t *zhp, void *arg)
+{
+ snapdata_t *sd = arg;
+ char name[ZFS_MAX_DATASET_NAME_LEN];
+ int rv = 0;
+
+ if (zfs_prop_get_int(zhp, ZFS_PROP_INCONSISTENT) == 0) {
+ if (snprintf(name, sizeof (name), "%s@%s", zfs_get_name(zhp),
+ sd->sd_snapname) >= sizeof (name))
+ return (EINVAL);
+
+ fnvlist_add_boolean(sd->sd_nvl, name);
+
+ rv = zfs_iter_filesystems(zhp, zfs_snapshot_cb, sd);
+ }
+ zfs_close(zhp);
+
+ return (rv);
+}
+
+/*
+ * Creates snapshots. The keys in the snaps nvlist are the snapshots to be
+ * created.
+ */
+int
+zfs_snapshot_nvl(libzfs_handle_t *hdl, nvlist_t *snaps, nvlist_t *props)
+{
+ int ret;
+ char errbuf[1024];
+ nvpair_t *elem;
+ nvlist_t *errors;
+ zpool_handle_t *zpool_hdl;
+ char pool[ZFS_MAX_DATASET_NAME_LEN];
+
+ (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
+ "cannot create snapshots "));
+
+ elem = NULL;
+ while ((elem = nvlist_next_nvpair(snaps, elem)) != NULL) {
+ const char *snapname = nvpair_name(elem);
+
+ /* validate the target name */
+ if (!zfs_validate_name(hdl, snapname, ZFS_TYPE_SNAPSHOT,
+ B_TRUE)) {
+ (void) snprintf(errbuf, sizeof (errbuf),
+ dgettext(TEXT_DOMAIN,
+ "cannot create snapshot '%s'"), snapname);
+ return (zfs_error(hdl, EZFS_INVALIDNAME, errbuf));
+ }
+ }
+
+ /*
+ * get pool handle for prop validation. assumes all snaps are in the
+ * same pool, as does lzc_snapshot (below).
+ */
+ elem = nvlist_next_nvpair(snaps, NULL);
+ (void) strlcpy(pool, nvpair_name(elem), sizeof (pool));
+ pool[strcspn(pool, "/@")] = '\0';
+ zpool_hdl = zpool_open(hdl, pool);
+ if (zpool_hdl == NULL)
+ return (-1);
+
+ if (props != NULL &&
+ (props = zfs_valid_proplist(hdl, ZFS_TYPE_SNAPSHOT,
+ props, B_FALSE, NULL, zpool_hdl, B_FALSE, errbuf)) == NULL) {
+ zpool_close(zpool_hdl);
+ return (-1);
+ }
+ zpool_close(zpool_hdl);
+
+ ret = lzc_snapshot(snaps, props, &errors);
+
+ if (ret != 0) {
+ boolean_t printed = B_FALSE;
+ for (elem = nvlist_next_nvpair(errors, NULL);
+ elem != NULL;
+ elem = nvlist_next_nvpair(errors, elem)) {
+ (void) snprintf(errbuf, sizeof (errbuf),
+ dgettext(TEXT_DOMAIN,
+ "cannot create snapshot '%s'"), nvpair_name(elem));
+ (void) zfs_standard_error(hdl,
+ fnvpair_value_int32(elem), errbuf);
+ printed = B_TRUE;
+ }
+ if (!printed) {
+ switch (ret) {
+ case EXDEV:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "multiple snapshots of same "
+ "fs not allowed"));
+ (void) zfs_error(hdl, EZFS_EXISTS, errbuf);
+
+ break;
+ default:
+ (void) zfs_standard_error(hdl, ret, errbuf);
+ }
+ }
+ }
+
+ nvlist_free(props);
+ nvlist_free(errors);
+ return (ret);
+}
+
+int
+zfs_snapshot(libzfs_handle_t *hdl, const char *path, boolean_t recursive,
+ nvlist_t *props)
+{
+ int ret;
+ snapdata_t sd = { 0 };
+ char fsname[ZFS_MAX_DATASET_NAME_LEN];
+ char *cp;
+ zfs_handle_t *zhp;
+ char errbuf[1024];
+
+ (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
+ "cannot snapshot %s"), path);
+
+ if (!zfs_validate_name(hdl, path, ZFS_TYPE_SNAPSHOT, B_TRUE))
+ return (zfs_error(hdl, EZFS_INVALIDNAME, errbuf));
+
+ (void) strlcpy(fsname, path, sizeof (fsname));
+ cp = strchr(fsname, '@');
+ *cp = '\0';
+ sd.sd_snapname = cp + 1;
+
+ if ((zhp = zfs_open(hdl, fsname, ZFS_TYPE_FILESYSTEM |
+ ZFS_TYPE_VOLUME)) == NULL) {
+ return (-1);
+ }
+
+ verify(nvlist_alloc(&sd.sd_nvl, NV_UNIQUE_NAME, 0) == 0);
+ if (recursive) {
+ (void) zfs_snapshot_cb(zfs_handle_dup(zhp), &sd);
+ } else {
+ fnvlist_add_boolean(sd.sd_nvl, path);
+ }
+
+ ret = zfs_snapshot_nvl(hdl, sd.sd_nvl, props);
+ nvlist_free(sd.sd_nvl);
+ zfs_close(zhp);
+ return (ret);
+}
+
+/*
+ * Destroy any more recent snapshots. We invoke this callback on any dependents
+ * of the snapshot first. If the 'cb_dependent' member is non-zero, then this
+ * is a dependent and we should just destroy it without checking the transaction
+ * group.
+ */
+typedef struct rollback_data {
+ const char *cb_target; /* the snapshot */
+ uint64_t cb_create; /* creation time reference */
+ boolean_t cb_error;
+ boolean_t cb_force;
+} rollback_data_t;
+
+static int
+rollback_destroy_dependent(zfs_handle_t *zhp, void *data)
+{
+ rollback_data_t *cbp = data;
+ prop_changelist_t *clp;
+
+ /* We must destroy this clone; first unmount it */
+ clp = changelist_gather(zhp, ZFS_PROP_NAME, 0,
+ cbp->cb_force ? MS_FORCE: 0);
+ if (clp == NULL || changelist_prefix(clp) != 0) {
+ cbp->cb_error = B_TRUE;
+ zfs_close(zhp);
+ return (0);
+ }
+ if (zfs_destroy(zhp, B_FALSE) != 0)
+ cbp->cb_error = B_TRUE;
+ else
+ changelist_remove(clp, zhp->zfs_name);
+ (void) changelist_postfix(clp);
+ changelist_free(clp);
+
+ zfs_close(zhp);
+ return (0);
+}
+
+static int
+rollback_destroy(zfs_handle_t *zhp, void *data)
+{
+ rollback_data_t *cbp = data;
+
+ if (zfs_prop_get_int(zhp, ZFS_PROP_CREATETXG) > cbp->cb_create) {
+ cbp->cb_error |= zfs_iter_dependents(zhp, B_FALSE,
+ rollback_destroy_dependent, cbp);
+
+ cbp->cb_error |= zfs_destroy(zhp, B_FALSE);
+ }
+
+ zfs_close(zhp);
+ return (0);
+}
+
+/*
+ * Given a dataset, rollback to a specific snapshot, discarding any
+ * data changes since then and making it the active dataset.
+ *
+ * Any snapshots and bookmarks more recent than the target are
+ * destroyed, along with their dependents (i.e. clones).
+ */
+int
+zfs_rollback(zfs_handle_t *zhp, zfs_handle_t *snap, boolean_t force)
+{
+ rollback_data_t cb = { 0 };
+ int err;
+ boolean_t restore_resv = 0;
+ uint64_t old_volsize = 0, new_volsize;
+ zfs_prop_t resv_prop = { 0 };
+ uint64_t min_txg = 0;
+
+ assert(zhp->zfs_type == ZFS_TYPE_FILESYSTEM ||
+ zhp->zfs_type == ZFS_TYPE_VOLUME);
+
+ /*
+ * Destroy all recent snapshots and their dependents.
+ */
+ cb.cb_force = force;
+ cb.cb_target = snap->zfs_name;
+ cb.cb_create = zfs_prop_get_int(snap, ZFS_PROP_CREATETXG);
+
+ if (cb.cb_create > 0)
+ min_txg = cb.cb_create;
+
+ (void) zfs_iter_snapshots(zhp, B_FALSE, rollback_destroy, &cb,
+ min_txg, 0);
+
+ (void) zfs_iter_bookmarks(zhp, rollback_destroy, &cb);
+
+ if (cb.cb_error)
+ return (-1);
+
+ /*
+ * Now that we have verified that the snapshot is the latest,
+ * rollback to the given snapshot.
+ */
+
+ if (zhp->zfs_type == ZFS_TYPE_VOLUME) {
+ if (zfs_which_resv_prop(zhp, &resv_prop) < 0)
+ return (-1);
+ old_volsize = zfs_prop_get_int(zhp, ZFS_PROP_VOLSIZE);
+ restore_resv =
+ (old_volsize == zfs_prop_get_int(zhp, resv_prop));
+ }
+
+ /*
+ * Pass both the filesystem and the wanted snapshot names,
+ * we would get an error back if the snapshot is destroyed or
+ * a new snapshot is created before this request is processed.
+ */
+ err = lzc_rollback_to(zhp->zfs_name, snap->zfs_name);
+ if (err != 0) {
+ char errbuf[1024];
+
+ (void) snprintf(errbuf, sizeof (errbuf),
+ dgettext(TEXT_DOMAIN, "cannot rollback '%s'"),
+ zhp->zfs_name);
+ switch (err) {
+ case EEXIST:
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "there is a snapshot or bookmark more recent "
+ "than '%s'"), snap->zfs_name);
+ (void) zfs_error(zhp->zfs_hdl, EZFS_EXISTS, errbuf);
+ break;
+ case ESRCH:
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "'%s' is not found among snapshots of '%s'"),
+ snap->zfs_name, zhp->zfs_name);
+ (void) zfs_error(zhp->zfs_hdl, EZFS_NOENT, errbuf);
+ break;
+ case EINVAL:
+ (void) zfs_error(zhp->zfs_hdl, EZFS_BADTYPE, errbuf);
+ break;
+ default:
+ (void) zfs_standard_error(zhp->zfs_hdl, err, errbuf);
+ }
+ return (err);
+ }
+
+ /*
+ * For volumes, if the pre-rollback volsize matched the pre-
+ * rollback reservation and the volsize has changed then set
+ * the reservation property to the post-rollback volsize.
+ * Make a new handle since the rollback closed the dataset.
+ */
+ if ((zhp->zfs_type == ZFS_TYPE_VOLUME) &&
+ (zhp = make_dataset_handle(zhp->zfs_hdl, zhp->zfs_name))) {
+ if (restore_resv) {
+ new_volsize = zfs_prop_get_int(zhp, ZFS_PROP_VOLSIZE);
+ if (old_volsize != new_volsize)
+ err = zfs_prop_set_int(zhp, resv_prop,
+ new_volsize);
+ }
+ zfs_close(zhp);
+ }
+ return (err);
+}
+
+/*
+ * Renames the given dataset.
+ */
+int
+zfs_rename(zfs_handle_t *zhp, const char *target, boolean_t recursive,
+ boolean_t force_unmount)
+{
+ int ret = 0;
+ zfs_cmd_t zc = {"\0"};
+ char *delim;
+ prop_changelist_t *cl = NULL;
+ char parent[ZFS_MAX_DATASET_NAME_LEN];
+ libzfs_handle_t *hdl = zhp->zfs_hdl;
+ char errbuf[1024];
+
+ /* if we have the same exact name, just return success */
+ if (strcmp(zhp->zfs_name, target) == 0)
+ return (0);
+
+ (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
+ "cannot rename to '%s'"), target);
+
+ /* make sure source name is valid */
+ if (!zfs_validate_name(hdl, zhp->zfs_name, zhp->zfs_type, B_TRUE))
+ return (zfs_error(hdl, EZFS_INVALIDNAME, errbuf));
+
+ /*
+ * Make sure the target name is valid
+ */
+ if (zhp->zfs_type == ZFS_TYPE_SNAPSHOT) {
+ if ((strchr(target, '@') == NULL) ||
+ *target == '@') {
+ /*
+ * Snapshot target name is abbreviated,
+ * reconstruct full dataset name
+ */
+ (void) strlcpy(parent, zhp->zfs_name,
+ sizeof (parent));
+ delim = strchr(parent, '@');
+ if (strchr(target, '@') == NULL)
+ *(++delim) = '\0';
+ else
+ *delim = '\0';
+ (void) strlcat(parent, target, sizeof (parent));
+ target = parent;
+ } else {
+ /*
+ * Make sure we're renaming within the same dataset.
+ */
+ delim = strchr(target, '@');
+ if (strncmp(zhp->zfs_name, target, delim - target)
+ != 0 || zhp->zfs_name[delim - target] != '@') {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "snapshots must be part of same "
+ "dataset"));
+ return (zfs_error(hdl, EZFS_CROSSTARGET,
+ errbuf));
+ }
+ }
+
+ if (!zfs_validate_name(hdl, target, zhp->zfs_type, B_TRUE))
+ return (zfs_error(hdl, EZFS_INVALIDNAME, errbuf));
+ } else {
+ if (recursive) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "recursive rename must be a snapshot"));
+ return (zfs_error(hdl, EZFS_BADTYPE, errbuf));
+ }
+
+ if (!zfs_validate_name(hdl, target, zhp->zfs_type, B_TRUE))
+ return (zfs_error(hdl, EZFS_INVALIDNAME, errbuf));
+
+ /* validate parents */
+ if (check_parents(hdl, target, NULL, B_FALSE, NULL) != 0)
+ return (-1);
+
+ /* make sure we're in the same pool */
+ verify((delim = strchr(target, '/')) != NULL);
+ if (strncmp(zhp->zfs_name, target, delim - target) != 0 ||
+ zhp->zfs_name[delim - target] != '/') {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "datasets must be within same pool"));
+ return (zfs_error(hdl, EZFS_CROSSTARGET, errbuf));
+ }
+
+ /* new name cannot be a child of the current dataset name */
+ if (is_descendant(zhp->zfs_name, target)) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "New dataset name cannot be a descendant of "
+ "current dataset name"));
+ return (zfs_error(hdl, EZFS_INVALIDNAME, errbuf));
+ }
+ }
+
+ (void) snprintf(errbuf, sizeof (errbuf),
+ dgettext(TEXT_DOMAIN, "cannot rename '%s'"), zhp->zfs_name);
+
+ if (getzoneid() == GLOBAL_ZONEID &&
+ zfs_prop_get_int(zhp, ZFS_PROP_ZONED)) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "dataset is used in a non-global zone"));
+ return (zfs_error(hdl, EZFS_ZONED, errbuf));
+ }
+
+ if (recursive) {
+ zfs_handle_t *zhrp;
+ char *parentname = zfs_strdup(zhp->zfs_hdl, zhp->zfs_name);
+ if (parentname == NULL) {
+ ret = -1;
+ goto error;
+ }
+ delim = strchr(parentname, '@');
+ *delim = '\0';
+ zhrp = zfs_open(zhp->zfs_hdl, parentname, ZFS_TYPE_DATASET);
+ free(parentname);
+ if (zhrp == NULL) {
+ ret = -1;
+ goto error;
+ }
+ zfs_close(zhrp);
+ } else if (zhp->zfs_type != ZFS_TYPE_SNAPSHOT) {
+ if ((cl = changelist_gather(zhp, ZFS_PROP_NAME,
+ CL_GATHER_ITER_MOUNTED,
+ force_unmount ? MS_FORCE : 0)) == NULL)
+ return (-1);
+
+ if (changelist_haszonedchild(cl)) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "child dataset with inherited mountpoint is used "
+ "in a non-global zone"));
+ (void) zfs_error(hdl, EZFS_ZONED, errbuf);
+ ret = -1;
+ goto error;
+ }
+
+ if ((ret = changelist_prefix(cl)) != 0)
+ goto error;
+ }
+
+ if (ZFS_IS_VOLUME(zhp))
+ zc.zc_objset_type = DMU_OST_ZVOL;
+ else
+ zc.zc_objset_type = DMU_OST_ZFS;
+
+ (void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name));
+ (void) strlcpy(zc.zc_value, target, sizeof (zc.zc_value));
+
+ zc.zc_cookie = recursive;
+
+ if ((ret = zfs_ioctl(zhp->zfs_hdl, ZFS_IOC_RENAME, &zc)) != 0) {
+ /*
+ * if it was recursive, the one that actually failed will
+ * be in zc.zc_name
+ */
+ (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
+ "cannot rename '%s'"), zc.zc_name);
+
+ if (recursive && errno == EEXIST) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "a child dataset already has a snapshot "
+ "with the new name"));
+ (void) zfs_error(hdl, EZFS_EXISTS, errbuf);
+ } else if (errno == EACCES) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "cannot move encrypted child outside of "
+ "its encryption root"));
+ (void) zfs_error(hdl, EZFS_CRYPTOFAILED, errbuf);
+ } else {
+ (void) zfs_standard_error(zhp->zfs_hdl, errno, errbuf);
+ }
+
+ /*
+ * On failure, we still want to remount any filesystems that
+ * were previously mounted, so we don't alter the system state.
+ */
+ if (cl != NULL)
+ (void) changelist_postfix(cl);
+ } else {
+ if (cl != NULL) {
+ changelist_rename(cl, zfs_get_name(zhp), target);
+ ret = changelist_postfix(cl);
+ }
+ }
+
+error:
+ if (cl != NULL) {
+ changelist_free(cl);
+ }
+ return (ret);
+}
+
+nvlist_t *
+zfs_get_all_props(zfs_handle_t *zhp)
+{
+ return (zhp->zfs_props);
+}
+
+nvlist_t *
+zfs_get_recvd_props(zfs_handle_t *zhp)
+{
+ if (zhp->zfs_recvd_props == NULL)
+ if (get_recvd_props_ioctl(zhp) != 0)
+ return (NULL);
+ return (zhp->zfs_recvd_props);
+}
+
+nvlist_t *
+zfs_get_user_props(zfs_handle_t *zhp)
+{
+ return (zhp->zfs_user_props);
+}
+
+/*
+ * This function is used by 'zfs list' to determine the exact set of columns to
+ * display, and their maximum widths. This does two main things:
+ *
+ * - If this is a list of all properties, then expand the list to include
+ * all native properties, and set a flag so that for each dataset we look
+ * for new unique user properties and add them to the list.
+ *
+ * - For non fixed-width properties, keep track of the maximum width seen
+ * so that we can size the column appropriately. If the user has
+ * requested received property values, we also need to compute the width
+ * of the RECEIVED column.
+ */
+int
+zfs_expand_proplist(zfs_handle_t *zhp, zprop_list_t **plp, boolean_t received,
+ boolean_t literal)
+{
+ libzfs_handle_t *hdl = zhp->zfs_hdl;
+ zprop_list_t *entry;
+ zprop_list_t **last, **start;
+ nvlist_t *userprops, *propval;
+ nvpair_t *elem;
+ char *strval;
+ char buf[ZFS_MAXPROPLEN];
+
+ if (zprop_expand_list(hdl, plp, ZFS_TYPE_DATASET) != 0)
+ return (-1);
+
+ userprops = zfs_get_user_props(zhp);
+
+ entry = *plp;
+ if (entry->pl_all && nvlist_next_nvpair(userprops, NULL) != NULL) {
+ /*
+ * Go through and add any user properties as necessary. We
+ * start by incrementing our list pointer to the first
+ * non-native property.
+ */
+ start = plp;
+ while (*start != NULL) {
+ if ((*start)->pl_prop == ZPROP_INVAL)
+ break;
+ start = &(*start)->pl_next;
+ }
+
+ elem = NULL;
+ while ((elem = nvlist_next_nvpair(userprops, elem)) != NULL) {
+ /*
+ * See if we've already found this property in our list.
+ */
+ for (last = start; *last != NULL;
+ last = &(*last)->pl_next) {
+ if (strcmp((*last)->pl_user_prop,
+ nvpair_name(elem)) == 0)
+ break;
+ }
+
+ if (*last == NULL) {
+ if ((entry = zfs_alloc(hdl,
+ sizeof (zprop_list_t))) == NULL ||
+ ((entry->pl_user_prop = zfs_strdup(hdl,
+ nvpair_name(elem)))) == NULL) {
+ free(entry);
+ return (-1);
+ }
+
+ entry->pl_prop = ZPROP_INVAL;
+ entry->pl_width = strlen(nvpair_name(elem));
+ entry->pl_all = B_TRUE;
+ *last = entry;
+ }
+ }
+ }
+
+ /*
+ * Now go through and check the width of any non-fixed columns
+ */
+ for (entry = *plp; entry != NULL; entry = entry->pl_next) {
+ if (entry->pl_fixed && !literal)
+ continue;
+
+ if (entry->pl_prop != ZPROP_INVAL) {
+ if (zfs_prop_get(zhp, entry->pl_prop,
+ buf, sizeof (buf), NULL, NULL, 0, literal) == 0) {
+ if (strlen(buf) > entry->pl_width)
+ entry->pl_width = strlen(buf);
+ }
+ if (received && zfs_prop_get_recvd(zhp,
+ zfs_prop_to_name(entry->pl_prop),
+ buf, sizeof (buf), literal) == 0)
+ if (strlen(buf) > entry->pl_recvd_width)
+ entry->pl_recvd_width = strlen(buf);
+ } else {
+ if (nvlist_lookup_nvlist(userprops, entry->pl_user_prop,
+ &propval) == 0) {
+ verify(nvlist_lookup_string(propval,
+ ZPROP_VALUE, &strval) == 0);
+ if (strlen(strval) > entry->pl_width)
+ entry->pl_width = strlen(strval);
+ }
+ if (received && zfs_prop_get_recvd(zhp,
+ entry->pl_user_prop,
+ buf, sizeof (buf), literal) == 0)
+ if (strlen(buf) > entry->pl_recvd_width)
+ entry->pl_recvd_width = strlen(buf);
+ }
+ }
+
+ return (0);
+}
+
+void
+zfs_prune_proplist(zfs_handle_t *zhp, uint8_t *props)
+{
+ nvpair_t *curr;
+ nvpair_t *next;
+
+ /*
+ * Keep a reference to the props-table against which we prune the
+ * properties.
+ */
+ zhp->zfs_props_table = props;
+
+ curr = nvlist_next_nvpair(zhp->zfs_props, NULL);
+
+ while (curr) {
+ zfs_prop_t zfs_prop = zfs_name_to_prop(nvpair_name(curr));
+ next = nvlist_next_nvpair(zhp->zfs_props, curr);
+
+ /*
+ * User properties will result in ZPROP_INVAL, and since we
+ * only know how to prune standard ZFS properties, we always
+ * leave these in the list. This can also happen if we
+ * encounter an unknown DSL property (when running older
+ * software, for example).
+ */
+ if (zfs_prop != ZPROP_INVAL && props[zfs_prop] == B_FALSE)
+ (void) nvlist_remove(zhp->zfs_props,
+ nvpair_name(curr), nvpair_type(curr));
+ curr = next;
+ }
+}
+
+static int
+zfs_smb_acl_mgmt(libzfs_handle_t *hdl, char *dataset, char *path,
+ zfs_smb_acl_op_t cmd, char *resource1, char *resource2)
+{
+ zfs_cmd_t zc = {"\0"};
+ nvlist_t *nvlist = NULL;
+ int error;
+
+ (void) strlcpy(zc.zc_name, dataset, sizeof (zc.zc_name));
+ (void) strlcpy(zc.zc_value, path, sizeof (zc.zc_value));
+ zc.zc_cookie = (uint64_t)cmd;
+
+ if (cmd == ZFS_SMB_ACL_RENAME) {
+ if (nvlist_alloc(&nvlist, NV_UNIQUE_NAME, 0) != 0) {
+ (void) no_memory(hdl);
+ return (0);
+ }
+ }
+
+ switch (cmd) {
+ case ZFS_SMB_ACL_ADD:
+ case ZFS_SMB_ACL_REMOVE:
+ (void) strlcpy(zc.zc_string, resource1, sizeof (zc.zc_string));
+ break;
+ case ZFS_SMB_ACL_RENAME:
+ if (nvlist_add_string(nvlist, ZFS_SMB_ACL_SRC,
+ resource1) != 0) {
+ (void) no_memory(hdl);
+ return (-1);
+ }
+ if (nvlist_add_string(nvlist, ZFS_SMB_ACL_TARGET,
+ resource2) != 0) {
+ (void) no_memory(hdl);
+ return (-1);
+ }
+ if (zcmd_write_src_nvlist(hdl, &zc, nvlist) != 0) {
+ nvlist_free(nvlist);
+ return (-1);
+ }
+ break;
+ case ZFS_SMB_ACL_PURGE:
+ break;
+ default:
+ return (-1);
+ }
+ error = ioctl(hdl->libzfs_fd, ZFS_IOC_SMB_ACL, &zc);
+ nvlist_free(nvlist);
+ return (error);
+}
+
+int
+zfs_smb_acl_add(libzfs_handle_t *hdl, char *dataset,
+ char *path, char *resource)
+{
+ return (zfs_smb_acl_mgmt(hdl, dataset, path, ZFS_SMB_ACL_ADD,
+ resource, NULL));
+}
+
+int
+zfs_smb_acl_remove(libzfs_handle_t *hdl, char *dataset,
+ char *path, char *resource)
+{
+ return (zfs_smb_acl_mgmt(hdl, dataset, path, ZFS_SMB_ACL_REMOVE,
+ resource, NULL));
+}
+
+int
+zfs_smb_acl_purge(libzfs_handle_t *hdl, char *dataset, char *path)
+{
+ return (zfs_smb_acl_mgmt(hdl, dataset, path, ZFS_SMB_ACL_PURGE,
+ NULL, NULL));
+}
+
+int
+zfs_smb_acl_rename(libzfs_handle_t *hdl, char *dataset, char *path,
+ char *oldname, char *newname)
+{
+ return (zfs_smb_acl_mgmt(hdl, dataset, path, ZFS_SMB_ACL_RENAME,
+ oldname, newname));
+}
+
+int
+zfs_userspace(zfs_handle_t *zhp, zfs_userquota_prop_t type,
+ zfs_userspace_cb_t func, void *arg)
+{
+ zfs_cmd_t zc = {"\0"};
+ zfs_useracct_t buf[100];
+ libzfs_handle_t *hdl = zhp->zfs_hdl;
+ int ret;
+
+ (void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name));
+
+ zc.zc_objset_type = type;
+ zc.zc_nvlist_dst = (uintptr_t)buf;
+
+ for (;;) {
+ zfs_useracct_t *zua = buf;
+
+ zc.zc_nvlist_dst_size = sizeof (buf);
+ if (zfs_ioctl(hdl, ZFS_IOC_USERSPACE_MANY, &zc) != 0) {
+ char errbuf[1024];
+
+ if ((errno == ENOTSUP &&
+ (type == ZFS_PROP_USEROBJUSED ||
+ type == ZFS_PROP_GROUPOBJUSED ||
+ type == ZFS_PROP_USEROBJQUOTA ||
+ type == ZFS_PROP_GROUPOBJQUOTA ||
+ type == ZFS_PROP_PROJECTOBJUSED ||
+ type == ZFS_PROP_PROJECTOBJQUOTA ||
+ type == ZFS_PROP_PROJECTUSED ||
+ type == ZFS_PROP_PROJECTQUOTA)))
+ break;
+
+ (void) snprintf(errbuf, sizeof (errbuf),
+ dgettext(TEXT_DOMAIN,
+ "cannot get used/quota for %s"), zc.zc_name);
+ return (zfs_standard_error_fmt(hdl, errno, errbuf));
+ }
+ if (zc.zc_nvlist_dst_size == 0)
+ break;
+
+ while (zc.zc_nvlist_dst_size > 0) {
+ if ((ret = func(arg, zua->zu_domain, zua->zu_rid,
+ zua->zu_space)) != 0)
+ return (ret);
+ zua++;
+ zc.zc_nvlist_dst_size -= sizeof (zfs_useracct_t);
+ }
+ }
+
+ return (0);
+}
+
+struct holdarg {
+ nvlist_t *nvl;
+ const char *snapname;
+ const char *tag;
+ boolean_t recursive;
+ int error;
+};
+
+static int
+zfs_hold_one(zfs_handle_t *zhp, void *arg)
+{
+ struct holdarg *ha = arg;
+ char name[ZFS_MAX_DATASET_NAME_LEN];
+ int rv = 0;
+
+ if (snprintf(name, sizeof (name), "%s@%s", zhp->zfs_name,
+ ha->snapname) >= sizeof (name))
+ return (EINVAL);
+
+ if (lzc_exists(name))
+ fnvlist_add_string(ha->nvl, name, ha->tag);
+
+ if (ha->recursive)
+ rv = zfs_iter_filesystems(zhp, zfs_hold_one, ha);
+ zfs_close(zhp);
+ return (rv);
+}
+
+int
+zfs_hold(zfs_handle_t *zhp, const char *snapname, const char *tag,
+ boolean_t recursive, int cleanup_fd)
+{
+ int ret;
+ struct holdarg ha;
+
+ ha.nvl = fnvlist_alloc();
+ ha.snapname = snapname;
+ ha.tag = tag;
+ ha.recursive = recursive;
+ (void) zfs_hold_one(zfs_handle_dup(zhp), &ha);
+
+ if (nvlist_empty(ha.nvl)) {
+ char errbuf[1024];
+
+ fnvlist_free(ha.nvl);
+ ret = ENOENT;
+ (void) snprintf(errbuf, sizeof (errbuf),
+ dgettext(TEXT_DOMAIN,
+ "cannot hold snapshot '%s@%s'"),
+ zhp->zfs_name, snapname);
+ (void) zfs_standard_error(zhp->zfs_hdl, ret, errbuf);
+ return (ret);
+ }
+
+ ret = zfs_hold_nvl(zhp, cleanup_fd, ha.nvl);
+ fnvlist_free(ha.nvl);
+
+ return (ret);
+}
+
+int
+zfs_hold_nvl(zfs_handle_t *zhp, int cleanup_fd, nvlist_t *holds)
+{
+ int ret;
+ nvlist_t *errors;
+ libzfs_handle_t *hdl = zhp->zfs_hdl;
+ char errbuf[1024];
+ nvpair_t *elem;
+
+ errors = NULL;
+ ret = lzc_hold(holds, cleanup_fd, &errors);
+
+ if (ret == 0) {
+ /* There may be errors even in the success case. */
+ fnvlist_free(errors);
+ return (0);
+ }
+
+ if (nvlist_empty(errors)) {
+ /* no hold-specific errors */
+ (void) snprintf(errbuf, sizeof (errbuf),
+ dgettext(TEXT_DOMAIN, "cannot hold"));
+ switch (ret) {
+ case ENOTSUP:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "pool must be upgraded"));
+ (void) zfs_error(hdl, EZFS_BADVERSION, errbuf);
+ break;
+ case EINVAL:
+ (void) zfs_error(hdl, EZFS_BADTYPE, errbuf);
+ break;
+ default:
+ (void) zfs_standard_error(hdl, ret, errbuf);
+ }
+ }
+
+ for (elem = nvlist_next_nvpair(errors, NULL);
+ elem != NULL;
+ elem = nvlist_next_nvpair(errors, elem)) {
+ (void) snprintf(errbuf, sizeof (errbuf),
+ dgettext(TEXT_DOMAIN,
+ "cannot hold snapshot '%s'"), nvpair_name(elem));
+ switch (fnvpair_value_int32(elem)) {
+ case E2BIG:
+ /*
+ * Temporary tags wind up having the ds object id
+ * prepended. So even if we passed the length check
+ * above, it's still possible for the tag to wind
+ * up being slightly too long.
+ */
+ (void) zfs_error(hdl, EZFS_TAGTOOLONG, errbuf);
+ break;
+ case EINVAL:
+ (void) zfs_error(hdl, EZFS_BADTYPE, errbuf);
+ break;
+ case EEXIST:
+ (void) zfs_error(hdl, EZFS_REFTAG_HOLD, errbuf);
+ break;
+ default:
+ (void) zfs_standard_error(hdl,
+ fnvpair_value_int32(elem), errbuf);
+ }
+ }
+
+ fnvlist_free(errors);
+ return (ret);
+}
+
+static int
+zfs_release_one(zfs_handle_t *zhp, void *arg)
+{
+ struct holdarg *ha = arg;
+ char name[ZFS_MAX_DATASET_NAME_LEN];
+ int rv = 0;
+ nvlist_t *existing_holds;
+
+ if (snprintf(name, sizeof (name), "%s@%s", zhp->zfs_name,
+ ha->snapname) >= sizeof (name)) {
+ ha->error = EINVAL;
+ rv = EINVAL;
+ }
+
+ if (lzc_get_holds(name, &existing_holds) != 0) {
+ ha->error = ENOENT;
+ } else if (!nvlist_exists(existing_holds, ha->tag)) {
+ ha->error = ESRCH;
+ } else {
+ nvlist_t *torelease = fnvlist_alloc();
+ fnvlist_add_boolean(torelease, ha->tag);
+ fnvlist_add_nvlist(ha->nvl, name, torelease);
+ fnvlist_free(torelease);
+ }
+
+ if (ha->recursive)
+ rv = zfs_iter_filesystems(zhp, zfs_release_one, ha);
+ zfs_close(zhp);
+ return (rv);
+}
+
+int
+zfs_release(zfs_handle_t *zhp, const char *snapname, const char *tag,
+ boolean_t recursive)
+{
+ int ret;
+ struct holdarg ha;
+ nvlist_t *errors = NULL;
+ nvpair_t *elem;
+ libzfs_handle_t *hdl = zhp->zfs_hdl;
+ char errbuf[1024];
+
+ ha.nvl = fnvlist_alloc();
+ ha.snapname = snapname;
+ ha.tag = tag;
+ ha.recursive = recursive;
+ ha.error = 0;
+ (void) zfs_release_one(zfs_handle_dup(zhp), &ha);
+
+ if (nvlist_empty(ha.nvl)) {
+ fnvlist_free(ha.nvl);
+ ret = ha.error;
+ (void) snprintf(errbuf, sizeof (errbuf),
+ dgettext(TEXT_DOMAIN,
+ "cannot release hold from snapshot '%s@%s'"),
+ zhp->zfs_name, snapname);
+ if (ret == ESRCH) {
+ (void) zfs_error(hdl, EZFS_REFTAG_RELE, errbuf);
+ } else {
+ (void) zfs_standard_error(hdl, ret, errbuf);
+ }
+ return (ret);
+ }
+
+ ret = lzc_release(ha.nvl, &errors);
+ fnvlist_free(ha.nvl);
+
+ if (ret == 0) {
+ /* There may be errors even in the success case. */
+ fnvlist_free(errors);
+ return (0);
+ }
+
+ if (nvlist_empty(errors)) {
+ /* no hold-specific errors */
+ (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
+ "cannot release"));
+ switch (errno) {
+ case ENOTSUP:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "pool must be upgraded"));
+ (void) zfs_error(hdl, EZFS_BADVERSION, errbuf);
+ break;
+ default:
+ (void) zfs_standard_error_fmt(hdl, errno, errbuf);
+ }
+ }
+
+ for (elem = nvlist_next_nvpair(errors, NULL);
+ elem != NULL;
+ elem = nvlist_next_nvpair(errors, elem)) {
+ (void) snprintf(errbuf, sizeof (errbuf),
+ dgettext(TEXT_DOMAIN,
+ "cannot release hold from snapshot '%s'"),
+ nvpair_name(elem));
+ switch (fnvpair_value_int32(elem)) {
+ case ESRCH:
+ (void) zfs_error(hdl, EZFS_REFTAG_RELE, errbuf);
+ break;
+ case EINVAL:
+ (void) zfs_error(hdl, EZFS_BADTYPE, errbuf);
+ break;
+ default:
+ (void) zfs_standard_error_fmt(hdl,
+ fnvpair_value_int32(elem), errbuf);
+ }
+ }
+
+ fnvlist_free(errors);
+ return (ret);
+}
+
+int
+zfs_get_fsacl(zfs_handle_t *zhp, nvlist_t **nvl)
+{
+ zfs_cmd_t zc = {"\0"};
+ libzfs_handle_t *hdl = zhp->zfs_hdl;
+ int nvsz = 2048;
+ void *nvbuf;
+ int err = 0;
+ char errbuf[1024];
+
+ assert(zhp->zfs_type == ZFS_TYPE_VOLUME ||
+ zhp->zfs_type == ZFS_TYPE_FILESYSTEM);
+
+tryagain:
+
+ nvbuf = malloc(nvsz);
+ if (nvbuf == NULL) {
+ err = (zfs_error(hdl, EZFS_NOMEM, strerror(errno)));
+ goto out;
+ }
+
+ zc.zc_nvlist_dst_size = nvsz;
+ zc.zc_nvlist_dst = (uintptr_t)nvbuf;
+
+ (void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name));
+
+ if (zfs_ioctl(hdl, ZFS_IOC_GET_FSACL, &zc) != 0) {
+ (void) snprintf(errbuf, sizeof (errbuf),
+ dgettext(TEXT_DOMAIN, "cannot get permissions on '%s'"),
+ zc.zc_name);
+ switch (errno) {
+ case ENOMEM:
+ free(nvbuf);
+ nvsz = zc.zc_nvlist_dst_size;
+ goto tryagain;
+
+ case ENOTSUP:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "pool must be upgraded"));
+ err = zfs_error(hdl, EZFS_BADVERSION, errbuf);
+ break;
+ case EINVAL:
+ err = zfs_error(hdl, EZFS_BADTYPE, errbuf);
+ break;
+ case ENOENT:
+ err = zfs_error(hdl, EZFS_NOENT, errbuf);
+ break;
+ default:
+ err = zfs_standard_error_fmt(hdl, errno, errbuf);
+ break;
+ }
+ } else {
+ /* success */
+ int rc = nvlist_unpack(nvbuf, zc.zc_nvlist_dst_size, nvl, 0);
+ if (rc) {
+ (void) snprintf(errbuf, sizeof (errbuf), dgettext(
+ TEXT_DOMAIN, "cannot get permissions on '%s'"),
+ zc.zc_name);
+ err = zfs_standard_error_fmt(hdl, rc, errbuf);
+ }
+ }
+
+ free(nvbuf);
+out:
+ return (err);
+}
+
+int
+zfs_set_fsacl(zfs_handle_t *zhp, boolean_t un, nvlist_t *nvl)
+{
+ zfs_cmd_t zc = {"\0"};
+ libzfs_handle_t *hdl = zhp->zfs_hdl;
+ char *nvbuf;
+ char errbuf[1024];
+ size_t nvsz;
+ int err;
+
+ assert(zhp->zfs_type == ZFS_TYPE_VOLUME ||
+ zhp->zfs_type == ZFS_TYPE_FILESYSTEM);
+
+ err = nvlist_size(nvl, &nvsz, NV_ENCODE_NATIVE);
+ assert(err == 0);
+
+ nvbuf = malloc(nvsz);
+
+ err = nvlist_pack(nvl, &nvbuf, &nvsz, NV_ENCODE_NATIVE, 0);
+ assert(err == 0);
+
+ zc.zc_nvlist_src_size = nvsz;
+ zc.zc_nvlist_src = (uintptr_t)nvbuf;
+ zc.zc_perm_action = un;
+
+ (void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name));
+
+ if (zfs_ioctl(hdl, ZFS_IOC_SET_FSACL, &zc) != 0) {
+ (void) snprintf(errbuf, sizeof (errbuf),
+ dgettext(TEXT_DOMAIN, "cannot set permissions on '%s'"),
+ zc.zc_name);
+ switch (errno) {
+ case ENOTSUP:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "pool must be upgraded"));
+ err = zfs_error(hdl, EZFS_BADVERSION, errbuf);
+ break;
+ case EINVAL:
+ err = zfs_error(hdl, EZFS_BADTYPE, errbuf);
+ break;
+ case ENOENT:
+ err = zfs_error(hdl, EZFS_NOENT, errbuf);
+ break;
+ default:
+ err = zfs_standard_error_fmt(hdl, errno, errbuf);
+ break;
+ }
+ }
+
+ free(nvbuf);
+
+ return (err);
+}
+
+int
+zfs_get_holds(zfs_handle_t *zhp, nvlist_t **nvl)
+{
+ int err;
+ char errbuf[1024];
+
+ err = lzc_get_holds(zhp->zfs_name, nvl);
+
+ if (err != 0) {
+ libzfs_handle_t *hdl = zhp->zfs_hdl;
+
+ (void) snprintf(errbuf, sizeof (errbuf),
+ dgettext(TEXT_DOMAIN, "cannot get holds for '%s'"),
+ zhp->zfs_name);
+ switch (err) {
+ case ENOTSUP:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "pool must be upgraded"));
+ err = zfs_error(hdl, EZFS_BADVERSION, errbuf);
+ break;
+ case EINVAL:
+ err = zfs_error(hdl, EZFS_BADTYPE, errbuf);
+ break;
+ case ENOENT:
+ err = zfs_error(hdl, EZFS_NOENT, errbuf);
+ break;
+ default:
+ err = zfs_standard_error_fmt(hdl, errno, errbuf);
+ break;
+ }
+ }
+
+ return (err);
+}
+
+/*
+ * The theory of raidz space accounting
+ *
+ * The "referenced" property of RAIDZ vdevs is scaled such that a 128KB block
+ * will "reference" 128KB, even though it allocates more than that, to store the
+ * parity information (and perhaps skip sectors). This concept of the
+ * "referenced" (and other DMU space accounting) being lower than the allocated
+ * space by a constant factor is called "raidz deflation."
+ *
+ * As mentioned above, the constant factor for raidz deflation assumes a 128KB
+ * block size. However, zvols typically have a much smaller block size (default
+ * 8KB). These smaller blocks may require proportionally much more parity
+ * information (and perhaps skip sectors). In this case, the change to the
+ * "referenced" property may be much more than the logical block size.
+ *
+ * Suppose a raidz vdev has 5 disks with ashift=12. A 128k block may be written
+ * as follows.
+ *
+ * +-------+-------+-------+-------+-------+
+ * | disk1 | disk2 | disk3 | disk4 | disk5 |
+ * +-------+-------+-------+-------+-------+
+ * | P0 | D0 | D8 | D16 | D24 |
+ * | P1 | D1 | D9 | D17 | D25 |
+ * | P2 | D2 | D10 | D18 | D26 |
+ * | P3 | D3 | D11 | D19 | D27 |
+ * | P4 | D4 | D12 | D20 | D28 |
+ * | P5 | D5 | D13 | D21 | D29 |
+ * | P6 | D6 | D14 | D22 | D30 |
+ * | P7 | D7 | D15 | D23 | D31 |
+ * +-------+-------+-------+-------+-------+
+ *
+ * Above, notice that 160k was allocated: 8 x 4k parity sectors + 32 x 4k data
+ * sectors. The dataset's referenced will increase by 128k and the pool's
+ * allocated and free properties will be adjusted by 160k.
+ *
+ * A 4k block written to the same raidz vdev will require two 4k sectors. The
+ * blank cells represent unallocated space.
+ *
+ * +-------+-------+-------+-------+-------+
+ * | disk1 | disk2 | disk3 | disk4 | disk5 |
+ * +-------+-------+-------+-------+-------+
+ * | P0 | D0 | | | |
+ * +-------+-------+-------+-------+-------+
+ *
+ * Above, notice that the 4k block required one sector for parity and another
+ * for data. vdev_raidz_asize() will return 8k and as such the pool's allocated
+ * and free properties will be adjusted by 8k. The dataset will not be charged
+ * 8k. Rather, it will be charged a value that is scaled according to the
+ * overhead of the 128k block on the same vdev. This 8k allocation will be
+ * charged 8k * 128k / 160k. 128k is from SPA_OLD_MAXBLOCKSIZE and 160k is as
+ * calculated in the 128k block example above.
+ *
+ * Every raidz allocation is sized to be a multiple of nparity+1 sectors. That
+ * is, every raidz1 allocation will be a multiple of 2 sectors, raidz2
+ * allocations are a multiple of 3 sectors, and raidz3 allocations are a
+ * multiple of of 4 sectors. When a block does not fill the required number of
+ * sectors, skip blocks (sectors) are used.
+ *
+ * An 8k block being written to a raidz vdev may be written as follows:
+ *
+ * +-------+-------+-------+-------+-------+
+ * | disk1 | disk2 | disk3 | disk4 | disk5 |
+ * +-------+-------+-------+-------+-------+
+ * | P0 | D0 | D1 | S0 | |
+ * +-------+-------+-------+-------+-------+
+ *
+ * In order to maintain the nparity+1 allocation size, a skip block (S0) was
+ * added. For this 8k block, the pool's allocated and free properties are
+ * adjusted by 16k and the dataset's referenced is increased by 16k * 128k /
+ * 160k. Again, 128k is from SPA_OLD_MAXBLOCKSIZE and 160k is as calculated in
+ * the 128k block example above.
+ *
+ * Compression may lead to a variety of block sizes being written for the same
+ * volume or file. There is no clear way to reserve just the amount of space
+ * that will be required, so the worst case (no compression) is assumed.
+ * Note that metadata blocks will typically be compressed, so the reservation
+ * size returned by zvol_volsize_to_reservation() will generally be slightly
+ * larger than the maximum that the volume can reference.
+ */
+
+/*
+ * Derived from function of same name in module/zfs/vdev_raidz.c. Returns the
+ * amount of space (in bytes) that will be allocated for the specified block
+ * size. Note that the "referenced" space accounted will be less than this, but
+ * not necessarily equal to "blksize", due to RAIDZ deflation.
+ */
+static uint64_t
+vdev_raidz_asize(uint64_t ndisks, uint64_t nparity, uint64_t ashift,
+ uint64_t blksize)
+{
+ uint64_t asize, ndata;
+
+ ASSERT3U(ndisks, >, nparity);
+ ndata = ndisks - nparity;
+ asize = ((blksize - 1) >> ashift) + 1;
+ asize += nparity * ((asize + ndata - 1) / ndata);
+ asize = roundup(asize, nparity + 1) << ashift;
+
+ return (asize);
+}
+
+/*
+ * Determine how much space will be allocated if it lands on the most space-
+ * inefficient top-level vdev. Returns the size in bytes required to store one
+ * copy of the volume data. See theory comment above.
+ */
+static uint64_t
+volsize_from_vdevs(zpool_handle_t *zhp, uint64_t nblocks, uint64_t blksize)
+{
+ nvlist_t *config, *tree, **vdevs;
+ uint_t nvdevs, v;
+ uint64_t ret = 0;
+
+ config = zpool_get_config(zhp, NULL);
+ if (nvlist_lookup_nvlist(config, ZPOOL_CONFIG_VDEV_TREE, &tree) != 0 ||
+ nvlist_lookup_nvlist_array(tree, ZPOOL_CONFIG_CHILDREN,
+ &vdevs, &nvdevs) != 0) {
+ return (nblocks * blksize);
+ }
+
+ for (v = 0; v < nvdevs; v++) {
+ char *type;
+ uint64_t nparity, ashift, asize, tsize;
+ nvlist_t **disks;
+ uint_t ndisks;
+ uint64_t volsize;
+
+ if (nvlist_lookup_string(vdevs[v], ZPOOL_CONFIG_TYPE,
+ &type) != 0 || strcmp(type, VDEV_TYPE_RAIDZ) != 0 ||
+ nvlist_lookup_uint64(vdevs[v], ZPOOL_CONFIG_NPARITY,
+ &nparity) != 0 ||
+ nvlist_lookup_uint64(vdevs[v], ZPOOL_CONFIG_ASHIFT,
+ &ashift) != 0 ||
+ nvlist_lookup_nvlist_array(vdevs[v], ZPOOL_CONFIG_CHILDREN,
+ &disks, &ndisks) != 0) {
+ continue;
+ }
+
+ /* allocation size for the "typical" 128k block */
+ tsize = vdev_raidz_asize(ndisks, nparity, ashift,
+ SPA_OLD_MAXBLOCKSIZE);
+ /* allocation size for the blksize block */
+ asize = vdev_raidz_asize(ndisks, nparity, ashift, blksize);
+
+ /*
+ * Scale this size down as a ratio of 128k / tsize. See theory
+ * statement above.
+ */
+ volsize = nblocks * asize * SPA_OLD_MAXBLOCKSIZE / tsize;
+ if (volsize > ret) {
+ ret = volsize;
+ }
+ }
+
+ if (ret == 0) {
+ ret = nblocks * blksize;
+ }
+
+ return (ret);
+}
+
+/*
+ * Convert the zvol's volume size to an appropriate reservation. See theory
+ * comment above.
+ *
+ * Note: If this routine is updated, it is necessary to update the ZFS test
+ * suite's shell version in reservation.shlib.
+ */
+uint64_t
+zvol_volsize_to_reservation(zpool_handle_t *zph, uint64_t volsize,
+ nvlist_t *props)
+{
+ uint64_t numdb;
+ uint64_t nblocks, volblocksize;
+ int ncopies;
+ char *strval;
+
+ if (nvlist_lookup_string(props,
+ zfs_prop_to_name(ZFS_PROP_COPIES), &strval) == 0)
+ ncopies = atoi(strval);
+ else
+ ncopies = 1;
+ if (nvlist_lookup_uint64(props,
+ zfs_prop_to_name(ZFS_PROP_VOLBLOCKSIZE),
+ &volblocksize) != 0)
+ volblocksize = ZVOL_DEFAULT_BLOCKSIZE;
+
+ nblocks = volsize / volblocksize;
+ /*
+ * Metadata defaults to using 128k blocks, not volblocksize blocks. For
+ * this reason, only the data blocks are scaled based on vdev config.
+ */
+ volsize = volsize_from_vdevs(zph, nblocks, volblocksize);
+
+ /* start with metadnode L0-L6 */
+ numdb = 7;
+ /* calculate number of indirects */
+ while (nblocks > 1) {
+ nblocks += DNODES_PER_LEVEL - 1;
+ nblocks /= DNODES_PER_LEVEL;
+ numdb += nblocks;
+ }
+ numdb *= MIN(SPA_DVAS_PER_BP, ncopies + 1);
+ volsize *= ncopies;
+ /*
+ * this is exactly DN_MAX_INDBLKSHIFT when metadata isn't
+ * compressed, but in practice they compress down to about
+ * 1100 bytes
+ */
+ numdb *= 1ULL << DN_MAX_INDBLKSHIFT;
+ volsize += numdb;
+ return (volsize);
+}
+
+/*
+ * Wait for the given activity and return the status of the wait (whether or not
+ * any waiting was done) in the 'waited' parameter. Non-existent fses are
+ * reported via the 'missing' parameter, rather than by printing an error
+ * message. This is convenient when this function is called in a loop over a
+ * long period of time (as it is, for example, by zfs's wait cmd). In that
+ * scenario, a fs being exported or destroyed should be considered a normal
+ * event, so we don't want to print an error when we find that the fs doesn't
+ * exist.
+ */
+int
+zfs_wait_status(zfs_handle_t *zhp, zfs_wait_activity_t activity,
+ boolean_t *missing, boolean_t *waited)
+{
+ int error = lzc_wait_fs(zhp->zfs_name, activity, waited);
+ *missing = (error == ENOENT);
+ if (*missing)
+ return (0);
+
+ if (error != 0) {
+ (void) zfs_standard_error_fmt(zhp->zfs_hdl, error,
+ dgettext(TEXT_DOMAIN, "error waiting in fs '%s'"),
+ zhp->zfs_name);
+ }
+
+ return (error);
+}
diff --git a/lib/libzfs/libzfs_diff.c b/lib/libzfs/libzfs_diff.c
new file mode 100644
index 000000000000..7941a5883074
--- /dev/null
+++ b/lib/libzfs/libzfs_diff.c
@@ -0,0 +1,797 @@
+/*
+ * 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) 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright 2015 Nexenta Systems, Inc. All rights reserved.
+ * Copyright (c) 2015, 2018 by Delphix. All rights reserved.
+ * Copyright 2016 Joyent, Inc.
+ * Copyright 2016 Igor Kozhukhov <ikozhukhov@gmail.com>
+ */
+
+/*
+ * zfs diff support
+ */
+#include <ctype.h>
+#include <errno.h>
+#include <libintl.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stropts.h>
+#include <pthread.h>
+#include <sys/zfs_ioctl.h>
+#include <libzfs.h>
+#include "libzfs_impl.h"
+
+#define ZDIFF_SNAPDIR "/.zfs/snapshot/"
+#define ZDIFF_PREFIX "zfs-diff-%d"
+
+#define ZDIFF_ADDED '+'
+#define ZDIFF_MODIFIED 'M'
+#define ZDIFF_REMOVED '-'
+#define ZDIFF_RENAMED 'R'
+
+
+/*
+ * Given a {dsname, object id}, get the object path
+ */
+static int
+get_stats_for_obj(differ_info_t *di, const char *dsname, uint64_t obj,
+ char *pn, int maxlen, zfs_stat_t *sb)
+{
+ zfs_cmd_t zc = {"\0"};
+ int error;
+
+ (void) strlcpy(zc.zc_name, dsname, sizeof (zc.zc_name));
+ zc.zc_obj = obj;
+
+ errno = 0;
+ error = zfs_ioctl(di->zhp->zfs_hdl, ZFS_IOC_OBJ_TO_STATS, &zc);
+ di->zerr = errno;
+
+ /* we can get stats even if we failed to get a path */
+ (void) memcpy(sb, &zc.zc_stat, sizeof (zfs_stat_t));
+ if (error == 0) {
+ ASSERT(di->zerr == 0);
+ (void) strlcpy(pn, zc.zc_value, maxlen);
+ return (0);
+ }
+
+ if (di->zerr == ESTALE) {
+ (void) snprintf(pn, maxlen, "(on_delete_queue)");
+ return (0);
+ } else if (di->zerr == EPERM) {
+ (void) snprintf(di->errbuf, sizeof (di->errbuf),
+ dgettext(TEXT_DOMAIN,
+ "The sys_config privilege or diff delegated permission "
+ "is needed\nto discover path names"));
+ return (-1);
+ } else if (di->zerr == EACCES) {
+ (void) snprintf(di->errbuf, sizeof (di->errbuf),
+ dgettext(TEXT_DOMAIN,
+ "Key must be loaded to discover path names"));
+ return (-1);
+ } else {
+ (void) snprintf(di->errbuf, sizeof (di->errbuf),
+ dgettext(TEXT_DOMAIN,
+ "Unable to determine path or stats for "
+ "object %lld in %s"), (longlong_t)obj, dsname);
+ return (-1);
+ }
+}
+
+/*
+ * stream_bytes
+ *
+ * Prints a file name out a character at a time. If the character is
+ * not in the range of what we consider "printable" ASCII, display it
+ * as an escaped 4-digit octal value. ASCII values less than a space
+ * are all control characters and we declare the upper end as the
+ * DELete character. This also is the last 7-bit ASCII character.
+ * We choose to treat all 8-bit ASCII as not printable for this
+ * application.
+ */
+static void
+stream_bytes(FILE *fp, const char *string)
+{
+ char c;
+
+ while ((c = *string++) != '\0') {
+ if (c > ' ' && c != '\\' && c < '\177') {
+ (void) fprintf(fp, "%c", c);
+ } else {
+ (void) fprintf(fp, "\\%04o", (uint8_t)c);
+ }
+ }
+}
+
+static void
+print_what(FILE *fp, mode_t what)
+{
+ char symbol;
+
+ switch (what & S_IFMT) {
+ case S_IFBLK:
+ symbol = 'B';
+ break;
+ case S_IFCHR:
+ symbol = 'C';
+ break;
+ case S_IFDIR:
+ symbol = '/';
+ break;
+#ifdef S_IFDOOR
+ case S_IFDOOR:
+ symbol = '>';
+ break;
+#endif
+ case S_IFIFO:
+ symbol = '|';
+ break;
+ case S_IFLNK:
+ symbol = '@';
+ break;
+#ifdef S_IFPORT
+ case S_IFPORT:
+ symbol = 'P';
+ break;
+#endif
+ case S_IFSOCK:
+ symbol = '=';
+ break;
+ case S_IFREG:
+ symbol = 'F';
+ break;
+ default:
+ symbol = '?';
+ break;
+ }
+ (void) fprintf(fp, "%c", symbol);
+}
+
+static void
+print_cmn(FILE *fp, differ_info_t *di, const char *file)
+{
+ stream_bytes(fp, di->dsmnt);
+ stream_bytes(fp, file);
+}
+
+static void
+print_rename(FILE *fp, differ_info_t *di, const char *old, const char *new,
+ zfs_stat_t *isb)
+{
+ if (di->timestamped)
+ (void) fprintf(fp, "%10lld.%09lld\t",
+ (longlong_t)isb->zs_ctime[0],
+ (longlong_t)isb->zs_ctime[1]);
+ (void) fprintf(fp, "%c\t", ZDIFF_RENAMED);
+ if (di->classify) {
+ print_what(fp, isb->zs_mode);
+ (void) fprintf(fp, "\t");
+ }
+ print_cmn(fp, di, old);
+ if (di->scripted)
+ (void) fprintf(fp, "\t");
+ else
+ (void) fprintf(fp, " -> ");
+ print_cmn(fp, di, new);
+ (void) fprintf(fp, "\n");
+}
+
+static void
+print_link_change(FILE *fp, differ_info_t *di, int delta, const char *file,
+ zfs_stat_t *isb)
+{
+ if (di->timestamped)
+ (void) fprintf(fp, "%10lld.%09lld\t",
+ (longlong_t)isb->zs_ctime[0],
+ (longlong_t)isb->zs_ctime[1]);
+ (void) fprintf(fp, "%c\t", ZDIFF_MODIFIED);
+ if (di->classify) {
+ print_what(fp, isb->zs_mode);
+ (void) fprintf(fp, "\t");
+ }
+ print_cmn(fp, di, file);
+ (void) fprintf(fp, "\t(%+d)", delta);
+ (void) fprintf(fp, "\n");
+}
+
+static void
+print_file(FILE *fp, differ_info_t *di, char type, const char *file,
+ zfs_stat_t *isb)
+{
+ if (di->timestamped)
+ (void) fprintf(fp, "%10lld.%09lld\t",
+ (longlong_t)isb->zs_ctime[0],
+ (longlong_t)isb->zs_ctime[1]);
+ (void) fprintf(fp, "%c\t", type);
+ if (di->classify) {
+ print_what(fp, isb->zs_mode);
+ (void) fprintf(fp, "\t");
+ }
+ print_cmn(fp, di, file);
+ (void) fprintf(fp, "\n");
+}
+
+static int
+write_inuse_diffs_one(FILE *fp, differ_info_t *di, uint64_t dobj)
+{
+ struct zfs_stat fsb, tsb;
+ mode_t fmode, tmode;
+ char fobjname[MAXPATHLEN], tobjname[MAXPATHLEN];
+ int fobjerr, tobjerr;
+ int change;
+
+ if (dobj == di->shares)
+ return (0);
+
+ /*
+ * Check the from and to snapshots for info on the object. If
+ * we get ENOENT, then the object just didn't exist in that
+ * snapshot. If we get ENOTSUP, then we tried to get
+ * info on a non-ZPL object, which we don't care about anyway.
+ */
+ fobjerr = get_stats_for_obj(di, di->fromsnap, dobj, fobjname,
+ MAXPATHLEN, &fsb);
+ if (fobjerr && di->zerr != ENOENT && di->zerr != ENOTSUP)
+ return (-1);
+
+ tobjerr = get_stats_for_obj(di, di->tosnap, dobj, tobjname,
+ MAXPATHLEN, &tsb);
+ if (tobjerr && di->zerr != ENOENT && di->zerr != ENOTSUP)
+ return (-1);
+
+ /*
+ * Unallocated object sharing the same meta dnode block
+ */
+ if (fobjerr && tobjerr) {
+ ASSERT(di->zerr == ENOENT || di->zerr == ENOTSUP);
+ di->zerr = 0;
+ return (0);
+ }
+
+ di->zerr = 0; /* negate get_stats_for_obj() from side that failed */
+ fmode = fsb.zs_mode & S_IFMT;
+ tmode = tsb.zs_mode & S_IFMT;
+ if (fmode == S_IFDIR || tmode == S_IFDIR || fsb.zs_links == 0 ||
+ tsb.zs_links == 0)
+ change = 0;
+ else
+ change = tsb.zs_links - fsb.zs_links;
+
+ if (fobjerr) {
+ if (change) {
+ print_link_change(fp, di, change, tobjname, &tsb);
+ return (0);
+ }
+ print_file(fp, di, ZDIFF_ADDED, tobjname, &tsb);
+ return (0);
+ } else if (tobjerr) {
+ if (change) {
+ print_link_change(fp, di, change, fobjname, &fsb);
+ return (0);
+ }
+ print_file(fp, di, ZDIFF_REMOVED, fobjname, &fsb);
+ return (0);
+ }
+
+ if (fmode != tmode && fsb.zs_gen == tsb.zs_gen)
+ tsb.zs_gen++; /* Force a generational difference */
+
+ /* Simple modification or no change */
+ if (fsb.zs_gen == tsb.zs_gen) {
+ /* No apparent changes. Could we assert !this? */
+ if (fsb.zs_ctime[0] == tsb.zs_ctime[0] &&
+ fsb.zs_ctime[1] == tsb.zs_ctime[1])
+ return (0);
+ if (change) {
+ print_link_change(fp, di, change,
+ change > 0 ? fobjname : tobjname, &tsb);
+ } else if (strcmp(fobjname, tobjname) == 0) {
+ print_file(fp, di, ZDIFF_MODIFIED, fobjname, &tsb);
+ } else {
+ print_rename(fp, di, fobjname, tobjname, &tsb);
+ }
+ return (0);
+ } else {
+ /* file re-created or object re-used */
+ print_file(fp, di, ZDIFF_REMOVED, fobjname, &fsb);
+ print_file(fp, di, ZDIFF_ADDED, tobjname, &tsb);
+ return (0);
+ }
+}
+
+static int
+write_inuse_diffs(FILE *fp, differ_info_t *di, dmu_diff_record_t *dr)
+{
+ uint64_t o;
+ int err;
+
+ for (o = dr->ddr_first; o <= dr->ddr_last; o++) {
+ if ((err = write_inuse_diffs_one(fp, di, o)) != 0)
+ return (err);
+ }
+ return (0);
+}
+
+static int
+describe_free(FILE *fp, differ_info_t *di, uint64_t object, char *namebuf,
+ int maxlen)
+{
+ struct zfs_stat sb;
+
+ if (get_stats_for_obj(di, di->fromsnap, object, namebuf,
+ maxlen, &sb) != 0) {
+ return (-1);
+ }
+ /* Don't print if in the delete queue on from side */
+ if (di->zerr == ESTALE) {
+ di->zerr = 0;
+ return (0);
+ }
+
+ print_file(fp, di, ZDIFF_REMOVED, namebuf, &sb);
+ return (0);
+}
+
+static int
+write_free_diffs(FILE *fp, differ_info_t *di, dmu_diff_record_t *dr)
+{
+ zfs_cmd_t zc = {"\0"};
+ libzfs_handle_t *lhdl = di->zhp->zfs_hdl;
+ char fobjname[MAXPATHLEN];
+
+ (void) strlcpy(zc.zc_name, di->fromsnap, sizeof (zc.zc_name));
+ zc.zc_obj = dr->ddr_first - 1;
+
+ ASSERT(di->zerr == 0);
+
+ while (zc.zc_obj < dr->ddr_last) {
+ int err;
+
+ err = zfs_ioctl(lhdl, ZFS_IOC_NEXT_OBJ, &zc);
+ if (err == 0) {
+ if (zc.zc_obj == di->shares) {
+ zc.zc_obj++;
+ continue;
+ }
+ if (zc.zc_obj > dr->ddr_last) {
+ break;
+ }
+ err = describe_free(fp, di, zc.zc_obj, fobjname,
+ MAXPATHLEN);
+ if (err)
+ break;
+ } else if (errno == ESRCH) {
+ break;
+ } else {
+ (void) snprintf(di->errbuf, sizeof (di->errbuf),
+ dgettext(TEXT_DOMAIN,
+ "next allocated object (> %lld) find failure"),
+ (longlong_t)zc.zc_obj);
+ di->zerr = errno;
+ break;
+ }
+ }
+ if (di->zerr)
+ return (-1);
+ return (0);
+}
+
+static void *
+differ(void *arg)
+{
+ differ_info_t *di = arg;
+ dmu_diff_record_t dr;
+ FILE *ofp;
+ int err = 0;
+
+ if ((ofp = fdopen(di->outputfd, "w")) == NULL) {
+ di->zerr = errno;
+ strlcpy(di->errbuf, strerror(errno), sizeof (di->errbuf));
+ (void) close(di->datafd);
+ return ((void *)-1);
+ }
+
+ for (;;) {
+ char *cp = (char *)&dr;
+ int len = sizeof (dr);
+ int rv;
+
+ do {
+ rv = read(di->datafd, cp, len);
+ cp += rv;
+ len -= rv;
+ } while (len > 0 && rv > 0);
+
+ if (rv < 0 || (rv == 0 && len != sizeof (dr))) {
+ di->zerr = EPIPE;
+ break;
+ } else if (rv == 0) {
+ /* end of file at a natural breaking point */
+ break;
+ }
+
+ switch (dr.ddr_type) {
+ case DDR_FREE:
+ err = write_free_diffs(ofp, di, &dr);
+ break;
+ case DDR_INUSE:
+ err = write_inuse_diffs(ofp, di, &dr);
+ break;
+ default:
+ di->zerr = EPIPE;
+ break;
+ }
+
+ if (err || di->zerr)
+ break;
+ }
+
+ (void) fclose(ofp);
+ (void) close(di->datafd);
+ if (err)
+ return ((void *)-1);
+ if (di->zerr) {
+ ASSERT(di->zerr == EPIPE);
+ (void) snprintf(di->errbuf, sizeof (di->errbuf),
+ dgettext(TEXT_DOMAIN,
+ "Internal error: bad data from diff IOCTL"));
+ return ((void *)-1);
+ }
+ return ((void *)0);
+}
+
+static int
+make_temp_snapshot(differ_info_t *di)
+{
+ libzfs_handle_t *hdl = di->zhp->zfs_hdl;
+ zfs_cmd_t zc = {"\0"};
+
+ (void) snprintf(zc.zc_value, sizeof (zc.zc_value),
+ ZDIFF_PREFIX, getpid());
+ (void) strlcpy(zc.zc_name, di->ds, sizeof (zc.zc_name));
+ zc.zc_cleanup_fd = di->cleanupfd;
+
+ if (zfs_ioctl(hdl, ZFS_IOC_TMP_SNAPSHOT, &zc) != 0) {
+ int err = errno;
+ if (err == EPERM) {
+ (void) snprintf(di->errbuf, sizeof (di->errbuf),
+ dgettext(TEXT_DOMAIN, "The diff delegated "
+ "permission is needed in order\nto create a "
+ "just-in-time snapshot for diffing\n"));
+ return (zfs_error(hdl, EZFS_DIFF, di->errbuf));
+ } else {
+ (void) snprintf(di->errbuf, sizeof (di->errbuf),
+ dgettext(TEXT_DOMAIN, "Cannot create just-in-time "
+ "snapshot of '%s'"), zc.zc_name);
+ return (zfs_standard_error(hdl, err, di->errbuf));
+ }
+ }
+
+ di->tmpsnap = zfs_strdup(hdl, zc.zc_value);
+ di->tosnap = zfs_asprintf(hdl, "%s@%s", di->ds, di->tmpsnap);
+ return (0);
+}
+
+static void
+teardown_differ_info(differ_info_t *di)
+{
+ free(di->ds);
+ free(di->dsmnt);
+ free(di->fromsnap);
+ free(di->frommnt);
+ free(di->tosnap);
+ free(di->tmpsnap);
+ free(di->tomnt);
+ (void) close(di->cleanupfd);
+}
+
+static int
+get_snapshot_names(differ_info_t *di, const char *fromsnap,
+ const char *tosnap)
+{
+ libzfs_handle_t *hdl = di->zhp->zfs_hdl;
+ char *atptrf = NULL;
+ char *atptrt = NULL;
+ int fdslen, fsnlen;
+ int tdslen, tsnlen;
+
+ /*
+ * Can accept
+ * fdslen fsnlen tdslen tsnlen
+ * dataset@snap1
+ * 0. dataset@snap1 dataset@snap2 >0 >1 >0 >1
+ * 1. dataset@snap1 @snap2 >0 >1 ==0 >1
+ * 2. dataset@snap1 dataset >0 >1 >0 ==0
+ * 3. @snap1 dataset@snap2 ==0 >1 >0 >1
+ * 4. @snap1 dataset ==0 >1 >0 ==0
+ */
+ if (tosnap == NULL) {
+ /* only a from snapshot given, must be valid */
+ (void) snprintf(di->errbuf, sizeof (di->errbuf),
+ dgettext(TEXT_DOMAIN,
+ "Badly formed snapshot name %s"), fromsnap);
+
+ if (!zfs_validate_name(hdl, fromsnap, ZFS_TYPE_SNAPSHOT,
+ B_FALSE)) {
+ return (zfs_error(hdl, EZFS_INVALIDNAME,
+ di->errbuf));
+ }
+
+ atptrf = strchr(fromsnap, '@');
+ ASSERT(atptrf != NULL);
+ fdslen = atptrf - fromsnap;
+
+ di->fromsnap = zfs_strdup(hdl, fromsnap);
+ di->ds = zfs_strdup(hdl, fromsnap);
+ di->ds[fdslen] = '\0';
+
+ /* the to snap will be a just-in-time snap of the head */
+ return (make_temp_snapshot(di));
+ }
+
+ (void) snprintf(di->errbuf, sizeof (di->errbuf),
+ dgettext(TEXT_DOMAIN,
+ "Unable to determine which snapshots to compare"));
+
+ atptrf = strchr(fromsnap, '@');
+ atptrt = strchr(tosnap, '@');
+ fdslen = atptrf ? atptrf - fromsnap : strlen(fromsnap);
+ tdslen = atptrt ? atptrt - tosnap : strlen(tosnap);
+ fsnlen = strlen(fromsnap) - fdslen; /* includes @ sign */
+ tsnlen = strlen(tosnap) - tdslen; /* includes @ sign */
+
+ if (fsnlen <= 1 || tsnlen == 1 || (fdslen == 0 && tdslen == 0)) {
+ return (zfs_error(hdl, EZFS_INVALIDNAME, di->errbuf));
+ } else if ((fdslen > 0 && tdslen > 0) &&
+ ((tdslen != fdslen || strncmp(fromsnap, tosnap, fdslen) != 0))) {
+ /*
+ * not the same dataset name, might be okay if
+ * tosnap is a clone of a fromsnap descendant.
+ */
+ char origin[ZFS_MAX_DATASET_NAME_LEN];
+ zprop_source_t src;
+ zfs_handle_t *zhp;
+
+ di->ds = zfs_alloc(di->zhp->zfs_hdl, tdslen + 1);
+ (void) strncpy(di->ds, tosnap, tdslen);
+ di->ds[tdslen] = '\0';
+
+ zhp = zfs_open(hdl, di->ds, ZFS_TYPE_FILESYSTEM);
+ while (zhp != NULL) {
+ if (zfs_prop_get(zhp, ZFS_PROP_ORIGIN, origin,
+ sizeof (origin), &src, NULL, 0, B_FALSE) != 0) {
+ (void) zfs_close(zhp);
+ zhp = NULL;
+ break;
+ }
+ if (strncmp(origin, fromsnap, fsnlen) == 0)
+ break;
+
+ (void) zfs_close(zhp);
+ zhp = zfs_open(hdl, origin, ZFS_TYPE_FILESYSTEM);
+ }
+
+ if (zhp == NULL) {
+ (void) snprintf(di->errbuf, sizeof (di->errbuf),
+ dgettext(TEXT_DOMAIN,
+ "Not an earlier snapshot from the same fs"));
+ return (zfs_error(hdl, EZFS_INVALIDNAME, di->errbuf));
+ } else {
+ (void) zfs_close(zhp);
+ }
+
+ di->isclone = B_TRUE;
+ di->fromsnap = zfs_strdup(hdl, fromsnap);
+ if (tsnlen) {
+ di->tosnap = zfs_strdup(hdl, tosnap);
+ } else {
+ return (make_temp_snapshot(di));
+ }
+ } else {
+ int dslen = fdslen ? fdslen : tdslen;
+
+ di->ds = zfs_alloc(hdl, dslen + 1);
+ (void) strncpy(di->ds, fdslen ? fromsnap : tosnap, dslen);
+ di->ds[dslen] = '\0';
+
+ di->fromsnap = zfs_asprintf(hdl, "%s%s", di->ds, atptrf);
+ if (tsnlen) {
+ di->tosnap = zfs_asprintf(hdl, "%s%s", di->ds, atptrt);
+ } else {
+ return (make_temp_snapshot(di));
+ }
+ }
+ return (0);
+}
+
+static int
+get_mountpoint(differ_info_t *di, char *dsnm, char **mntpt)
+{
+ boolean_t mounted;
+
+ mounted = is_mounted(di->zhp->zfs_hdl, dsnm, mntpt);
+ if (mounted == B_FALSE) {
+ (void) snprintf(di->errbuf, sizeof (di->errbuf),
+ dgettext(TEXT_DOMAIN,
+ "Cannot diff an unmounted snapshot"));
+ return (zfs_error(di->zhp->zfs_hdl, EZFS_BADTYPE, di->errbuf));
+ }
+
+ /* Avoid a double slash at the beginning of root-mounted datasets */
+ if (**mntpt == '/' && *(*mntpt + 1) == '\0')
+ **mntpt = '\0';
+ return (0);
+}
+
+static int
+get_mountpoints(differ_info_t *di)
+{
+ char *strptr;
+ char *frommntpt;
+
+ /*
+ * first get the mountpoint for the parent dataset
+ */
+ if (get_mountpoint(di, di->ds, &di->dsmnt) != 0)
+ return (-1);
+
+ strptr = strchr(di->tosnap, '@');
+ ASSERT3P(strptr, !=, NULL);
+ di->tomnt = zfs_asprintf(di->zhp->zfs_hdl, "%s%s%s", di->dsmnt,
+ ZDIFF_SNAPDIR, ++strptr);
+
+ strptr = strchr(di->fromsnap, '@');
+ ASSERT3P(strptr, !=, NULL);
+
+ frommntpt = di->dsmnt;
+ if (di->isclone) {
+ char *mntpt;
+ int err;
+
+ *strptr = '\0';
+ err = get_mountpoint(di, di->fromsnap, &mntpt);
+ *strptr = '@';
+ if (err != 0)
+ return (-1);
+ frommntpt = mntpt;
+ }
+
+ di->frommnt = zfs_asprintf(di->zhp->zfs_hdl, "%s%s%s", frommntpt,
+ ZDIFF_SNAPDIR, ++strptr);
+
+ if (di->isclone)
+ free(frommntpt);
+
+ return (0);
+}
+
+static int
+setup_differ_info(zfs_handle_t *zhp, const char *fromsnap,
+ const char *tosnap, differ_info_t *di)
+{
+ di->zhp = zhp;
+
+ di->cleanupfd = open(ZFS_DEV, O_RDWR);
+ VERIFY(di->cleanupfd >= 0);
+
+ if (get_snapshot_names(di, fromsnap, tosnap) != 0)
+ return (-1);
+
+ if (get_mountpoints(di) != 0)
+ return (-1);
+
+ if (find_shares_object(di) != 0)
+ return (-1);
+
+ return (0);
+}
+
+int
+zfs_show_diffs(zfs_handle_t *zhp, int outfd, const char *fromsnap,
+ const char *tosnap, int flags)
+{
+ zfs_cmd_t zc = {"\0"};
+ char errbuf[1024];
+ differ_info_t di = { 0 };
+ pthread_t tid;
+ int pipefd[2];
+ int iocerr;
+
+ (void) snprintf(errbuf, sizeof (errbuf),
+ dgettext(TEXT_DOMAIN, "zfs diff failed"));
+
+ if (setup_differ_info(zhp, fromsnap, tosnap, &di)) {
+ teardown_differ_info(&di);
+ return (-1);
+ }
+
+ if (pipe(pipefd)) {
+ zfs_error_aux(zhp->zfs_hdl, strerror(errno));
+ teardown_differ_info(&di);
+ return (zfs_error(zhp->zfs_hdl, EZFS_PIPEFAILED, errbuf));
+ }
+
+ di.scripted = (flags & ZFS_DIFF_PARSEABLE);
+ di.classify = (flags & ZFS_DIFF_CLASSIFY);
+ di.timestamped = (flags & ZFS_DIFF_TIMESTAMP);
+
+ di.outputfd = outfd;
+ di.datafd = pipefd[0];
+
+ if (pthread_create(&tid, NULL, differ, &di)) {
+ zfs_error_aux(zhp->zfs_hdl, strerror(errno));
+ (void) close(pipefd[0]);
+ (void) close(pipefd[1]);
+ teardown_differ_info(&di);
+ return (zfs_error(zhp->zfs_hdl,
+ EZFS_THREADCREATEFAILED, errbuf));
+ }
+
+ /* do the ioctl() */
+ (void) strlcpy(zc.zc_value, di.fromsnap, strlen(di.fromsnap) + 1);
+ (void) strlcpy(zc.zc_name, di.tosnap, strlen(di.tosnap) + 1);
+ zc.zc_cookie = pipefd[1];
+
+ iocerr = zfs_ioctl(zhp->zfs_hdl, ZFS_IOC_DIFF, &zc);
+ if (iocerr != 0) {
+ (void) snprintf(errbuf, sizeof (errbuf),
+ dgettext(TEXT_DOMAIN, "Unable to obtain diffs"));
+ if (errno == EPERM) {
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "\n The sys_mount privilege or diff delegated "
+ "permission is needed\n to execute the "
+ "diff ioctl"));
+ } else if (errno == EXDEV) {
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "\n Not an earlier snapshot from the same fs"));
+ } else if (errno != EPIPE || di.zerr == 0) {
+ zfs_error_aux(zhp->zfs_hdl, strerror(errno));
+ }
+ (void) close(pipefd[1]);
+ (void) pthread_cancel(tid);
+ (void) pthread_join(tid, NULL);
+ teardown_differ_info(&di);
+ if (di.zerr != 0 && di.zerr != EPIPE) {
+ zfs_error_aux(zhp->zfs_hdl, strerror(di.zerr));
+ return (zfs_error(zhp->zfs_hdl, EZFS_DIFF, di.errbuf));
+ } else {
+ return (zfs_error(zhp->zfs_hdl, EZFS_DIFFDATA, errbuf));
+ }
+ }
+
+ (void) close(pipefd[1]);
+ (void) pthread_join(tid, NULL);
+
+ if (di.zerr != 0) {
+ zfs_error_aux(zhp->zfs_hdl, strerror(di.zerr));
+ return (zfs_error(zhp->zfs_hdl, EZFS_DIFF, di.errbuf));
+ }
+ teardown_differ_info(&di);
+ return (0);
+}
diff --git a/lib/libzfs/libzfs_import.c b/lib/libzfs/libzfs_import.c
new file mode 100644
index 000000000000..6c5f61836978
--- /dev/null
+++ b/lib/libzfs/libzfs_import.c
@@ -0,0 +1,472 @@
+/*
+ * 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 2015 Nexenta Systems, Inc. All rights reserved.
+ * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2018 by Delphix. All rights reserved.
+ * Copyright 2015 RackTop Systems.
+ * Copyright (c) 2016, Intel Corporation.
+ */
+
+#include <errno.h>
+#include <libintl.h>
+#include <libgen.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <sys/vdev_impl.h>
+#include <libzfs.h>
+#include <libzfs_impl.h>
+#include <libzutil.h>
+#include <sys/arc_impl.h>
+
+/*
+ * Returns true if the named pool matches the given GUID.
+ */
+static int
+pool_active(libzfs_handle_t *hdl, const char *name, uint64_t guid,
+ boolean_t *isactive)
+{
+ zpool_handle_t *zhp;
+ uint64_t theguid;
+
+ if (zpool_open_silent(hdl, name, &zhp) != 0)
+ return (-1);
+
+ if (zhp == NULL) {
+ *isactive = B_FALSE;
+ return (0);
+ }
+
+ verify(nvlist_lookup_uint64(zhp->zpool_config, ZPOOL_CONFIG_POOL_GUID,
+ &theguid) == 0);
+
+ zpool_close(zhp);
+
+ *isactive = (theguid == guid);
+ return (0);
+}
+
+static nvlist_t *
+refresh_config(libzfs_handle_t *hdl, nvlist_t *config)
+{
+ nvlist_t *nvl;
+ zfs_cmd_t zc = {"\0"};
+ int err, dstbuf_size;
+
+ if (zcmd_write_conf_nvlist(hdl, &zc, config) != 0)
+ return (NULL);
+
+ dstbuf_size = MAX(CONFIG_BUF_MINSIZE, zc.zc_nvlist_conf_size * 4);
+
+ if (zcmd_alloc_dst_nvlist(hdl, &zc, dstbuf_size) != 0) {
+ zcmd_free_nvlists(&zc);
+ return (NULL);
+ }
+
+ while ((err = zfs_ioctl(hdl, ZFS_IOC_POOL_TRYIMPORT,
+ &zc)) != 0 && errno == ENOMEM) {
+ if (zcmd_expand_dst_nvlist(hdl, &zc) != 0) {
+ zcmd_free_nvlists(&zc);
+ return (NULL);
+ }
+ }
+
+ if (err) {
+ zcmd_free_nvlists(&zc);
+ return (NULL);
+ }
+
+ if (zcmd_read_dst_nvlist(hdl, &zc, &nvl) != 0) {
+ zcmd_free_nvlists(&zc);
+ return (NULL);
+ }
+
+ zcmd_free_nvlists(&zc);
+ return (nvl);
+}
+
+static nvlist_t *
+refresh_config_libzfs(void *handle, nvlist_t *tryconfig)
+{
+ return (refresh_config((libzfs_handle_t *)handle, tryconfig));
+}
+
+
+static int
+pool_active_libzfs(void *handle, const char *name, uint64_t guid,
+ boolean_t *isactive)
+{
+ return (pool_active((libzfs_handle_t *)handle, name, guid, isactive));
+}
+
+const pool_config_ops_t libzfs_config_ops = {
+ .pco_refresh_config = refresh_config_libzfs,
+ .pco_pool_active = pool_active_libzfs,
+};
+
+/*
+ * Return the offset of the given label.
+ */
+static uint64_t
+label_offset(uint64_t size, int l)
+{
+ ASSERT(P2PHASE_TYPED(size, sizeof (vdev_label_t), uint64_t) == 0);
+ return (l * sizeof (vdev_label_t) + (l < VDEV_LABELS / 2 ?
+ 0 : size - VDEV_LABELS * sizeof (vdev_label_t)));
+}
+
+/*
+ * Given a file descriptor, clear (zero) the label information. This function
+ * is used in the appliance stack as part of the ZFS sysevent module and
+ * to implement the "zpool labelclear" command.
+ */
+int
+zpool_clear_label(int fd)
+{
+ struct stat64 statbuf;
+ int l;
+ vdev_label_t *label;
+ l2arc_dev_hdr_phys_t *l2dhdr;
+ uint64_t size;
+ int labels_cleared = 0, header_cleared = 0;
+ boolean_t clear_l2arc_header = B_FALSE;
+
+ if (fstat64_blk(fd, &statbuf) == -1)
+ return (0);
+
+ size = P2ALIGN_TYPED(statbuf.st_size, sizeof (vdev_label_t), uint64_t);
+
+ if ((label = calloc(1, sizeof (vdev_label_t))) == NULL)
+ return (-1);
+
+ if ((l2dhdr = calloc(1, sizeof (l2arc_dev_hdr_phys_t))) == NULL) {
+ free(label);
+ return (-1);
+ }
+
+ for (l = 0; l < VDEV_LABELS; l++) {
+ uint64_t state, guid, l2cache;
+ nvlist_t *config;
+
+ if (pread64(fd, label, sizeof (vdev_label_t),
+ label_offset(size, l)) != sizeof (vdev_label_t)) {
+ continue;
+ }
+
+ if (nvlist_unpack(label->vl_vdev_phys.vp_nvlist,
+ sizeof (label->vl_vdev_phys.vp_nvlist), &config, 0) != 0) {
+ continue;
+ }
+
+ /* Skip labels which do not have a valid guid. */
+ if (nvlist_lookup_uint64(config, ZPOOL_CONFIG_GUID,
+ &guid) != 0 || guid == 0) {
+ nvlist_free(config);
+ continue;
+ }
+
+ /* Skip labels which are not in a known valid state. */
+ if (nvlist_lookup_uint64(config, ZPOOL_CONFIG_POOL_STATE,
+ &state) != 0 || state > POOL_STATE_L2CACHE) {
+ nvlist_free(config);
+ continue;
+ }
+
+ /* If the device is a cache device clear the header. */
+ if (!clear_l2arc_header) {
+ if (nvlist_lookup_uint64(config,
+ ZPOOL_CONFIG_POOL_STATE, &l2cache) == 0 &&
+ l2cache == POOL_STATE_L2CACHE) {
+ clear_l2arc_header = B_TRUE;
+ }
+ }
+
+ nvlist_free(config);
+
+ /*
+ * A valid label was found, overwrite this label's nvlist
+ * and uberblocks with zeros on disk. This is done to prevent
+ * system utilities, like blkid, from incorrectly detecting a
+ * partial label. The leading pad space is left untouched.
+ */
+ memset(label, 0, sizeof (vdev_label_t));
+ size_t label_size = sizeof (vdev_label_t) - (2 * VDEV_PAD_SIZE);
+
+ if (pwrite64(fd, label, label_size, label_offset(size, l) +
+ (2 * VDEV_PAD_SIZE)) == label_size) {
+ labels_cleared++;
+ }
+ }
+
+ /* Clear the L2ARC header. */
+ if (clear_l2arc_header) {
+ memset(l2dhdr, 0, sizeof (l2arc_dev_hdr_phys_t));
+ if (pwrite64(fd, l2dhdr, sizeof (l2arc_dev_hdr_phys_t),
+ VDEV_LABEL_START_SIZE) == sizeof (l2arc_dev_hdr_phys_t)) {
+ header_cleared++;
+ }
+ }
+
+ free(label);
+ free(l2dhdr);
+
+ if (labels_cleared == 0)
+ return (-1);
+
+ return (0);
+}
+
+static boolean_t
+find_guid(nvlist_t *nv, uint64_t guid)
+{
+ uint64_t tmp;
+ nvlist_t **child;
+ uint_t c, children;
+
+ verify(nvlist_lookup_uint64(nv, ZPOOL_CONFIG_GUID, &tmp) == 0);
+ if (tmp == guid)
+ return (B_TRUE);
+
+ if (nvlist_lookup_nvlist_array(nv, ZPOOL_CONFIG_CHILDREN,
+ &child, &children) == 0) {
+ for (c = 0; c < children; c++)
+ if (find_guid(child[c], guid))
+ return (B_TRUE);
+ }
+
+ return (B_FALSE);
+}
+
+typedef struct aux_cbdata {
+ const char *cb_type;
+ uint64_t cb_guid;
+ zpool_handle_t *cb_zhp;
+} aux_cbdata_t;
+
+static int
+find_aux(zpool_handle_t *zhp, void *data)
+{
+ aux_cbdata_t *cbp = data;
+ nvlist_t **list;
+ uint_t i, count;
+ uint64_t guid;
+ nvlist_t *nvroot;
+
+ verify(nvlist_lookup_nvlist(zhp->zpool_config, ZPOOL_CONFIG_VDEV_TREE,
+ &nvroot) == 0);
+
+ if (nvlist_lookup_nvlist_array(nvroot, cbp->cb_type,
+ &list, &count) == 0) {
+ for (i = 0; i < count; i++) {
+ verify(nvlist_lookup_uint64(list[i],
+ ZPOOL_CONFIG_GUID, &guid) == 0);
+ if (guid == cbp->cb_guid) {
+ cbp->cb_zhp = zhp;
+ return (1);
+ }
+ }
+ }
+
+ zpool_close(zhp);
+ return (0);
+}
+
+/*
+ * Determines if the pool is in use. If so, it returns true and the state of
+ * the pool as well as the name of the pool. Name string is allocated and
+ * must be freed by the caller.
+ */
+int
+zpool_in_use(libzfs_handle_t *hdl, int fd, pool_state_t *state, char **namestr,
+ boolean_t *inuse)
+{
+ nvlist_t *config;
+ char *name;
+ boolean_t ret;
+ uint64_t guid, vdev_guid;
+ zpool_handle_t *zhp;
+ nvlist_t *pool_config;
+ uint64_t stateval, isspare;
+ aux_cbdata_t cb = { 0 };
+ boolean_t isactive;
+
+ *inuse = B_FALSE;
+
+ if (zpool_read_label(fd, &config, NULL) != 0) {
+ (void) no_memory(hdl);
+ return (-1);
+ }
+
+ if (config == NULL)
+ return (0);
+
+ verify(nvlist_lookup_uint64(config, ZPOOL_CONFIG_POOL_STATE,
+ &stateval) == 0);
+ verify(nvlist_lookup_uint64(config, ZPOOL_CONFIG_GUID,
+ &vdev_guid) == 0);
+
+ if (stateval != POOL_STATE_SPARE && stateval != POOL_STATE_L2CACHE) {
+ verify(nvlist_lookup_string(config, ZPOOL_CONFIG_POOL_NAME,
+ &name) == 0);
+ verify(nvlist_lookup_uint64(config, ZPOOL_CONFIG_POOL_GUID,
+ &guid) == 0);
+ }
+
+ switch (stateval) {
+ case POOL_STATE_EXPORTED:
+ /*
+ * A pool with an exported state may in fact be imported
+ * read-only, so check the in-core state to see if it's
+ * active and imported read-only. If it is, set
+ * its state to active.
+ */
+ if (pool_active(hdl, name, guid, &isactive) == 0 && isactive &&
+ (zhp = zpool_open_canfail(hdl, name)) != NULL) {
+ if (zpool_get_prop_int(zhp, ZPOOL_PROP_READONLY, NULL))
+ stateval = POOL_STATE_ACTIVE;
+
+ /*
+ * All we needed the zpool handle for is the
+ * readonly prop check.
+ */
+ zpool_close(zhp);
+ }
+
+ ret = B_TRUE;
+ break;
+
+ case POOL_STATE_ACTIVE:
+ /*
+ * For an active pool, we have to determine if it's really part
+ * of a currently active pool (in which case the pool will exist
+ * and the guid will be the same), or whether it's part of an
+ * active pool that was disconnected without being explicitly
+ * exported.
+ */
+ if (pool_active(hdl, name, guid, &isactive) != 0) {
+ nvlist_free(config);
+ return (-1);
+ }
+
+ if (isactive) {
+ /*
+ * Because the device may have been removed while
+ * offlined, we only report it as active if the vdev is
+ * still present in the config. Otherwise, pretend like
+ * it's not in use.
+ */
+ if ((zhp = zpool_open_canfail(hdl, name)) != NULL &&
+ (pool_config = zpool_get_config(zhp, NULL))
+ != NULL) {
+ nvlist_t *nvroot;
+
+ verify(nvlist_lookup_nvlist(pool_config,
+ ZPOOL_CONFIG_VDEV_TREE, &nvroot) == 0);
+ ret = find_guid(nvroot, vdev_guid);
+ } else {
+ ret = B_FALSE;
+ }
+
+ /*
+ * If this is an active spare within another pool, we
+ * treat it like an unused hot spare. This allows the
+ * user to create a pool with a hot spare that currently
+ * in use within another pool. Since we return B_TRUE,
+ * libdiskmgt will continue to prevent generic consumers
+ * from using the device.
+ */
+ if (ret && nvlist_lookup_uint64(config,
+ ZPOOL_CONFIG_IS_SPARE, &isspare) == 0 && isspare)
+ stateval = POOL_STATE_SPARE;
+
+ if (zhp != NULL)
+ zpool_close(zhp);
+ } else {
+ stateval = POOL_STATE_POTENTIALLY_ACTIVE;
+ ret = B_TRUE;
+ }
+ break;
+
+ case POOL_STATE_SPARE:
+ /*
+ * For a hot spare, it can be either definitively in use, or
+ * potentially active. To determine if it's in use, we iterate
+ * over all pools in the system and search for one with a spare
+ * with a matching guid.
+ *
+ * Due to the shared nature of spares, we don't actually report
+ * the potentially active case as in use. This means the user
+ * can freely create pools on the hot spares of exported pools,
+ * but to do otherwise makes the resulting code complicated, and
+ * we end up having to deal with this case anyway.
+ */
+ cb.cb_zhp = NULL;
+ cb.cb_guid = vdev_guid;
+ cb.cb_type = ZPOOL_CONFIG_SPARES;
+ if (zpool_iter(hdl, find_aux, &cb) == 1) {
+ name = (char *)zpool_get_name(cb.cb_zhp);
+ ret = B_TRUE;
+ } else {
+ ret = B_FALSE;
+ }
+ break;
+
+ case POOL_STATE_L2CACHE:
+
+ /*
+ * Check if any pool is currently using this l2cache device.
+ */
+ cb.cb_zhp = NULL;
+ cb.cb_guid = vdev_guid;
+ cb.cb_type = ZPOOL_CONFIG_L2CACHE;
+ if (zpool_iter(hdl, find_aux, &cb) == 1) {
+ name = (char *)zpool_get_name(cb.cb_zhp);
+ ret = B_TRUE;
+ } else {
+ ret = B_FALSE;
+ }
+ break;
+
+ default:
+ ret = B_FALSE;
+ }
+
+
+ if (ret) {
+ if ((*namestr = zfs_strdup(hdl, name)) == NULL) {
+ if (cb.cb_zhp)
+ zpool_close(cb.cb_zhp);
+ nvlist_free(config);
+ return (-1);
+ }
+ *state = (pool_state_t)stateval;
+ }
+
+ if (cb.cb_zhp)
+ zpool_close(cb.cb_zhp);
+
+ nvlist_free(config);
+ *inuse = ret;
+ return (0);
+}
diff --git a/lib/libzfs/libzfs_iter.c b/lib/libzfs/libzfs_iter.c
new file mode 100644
index 000000000000..7ee326bc6905
--- /dev/null
+++ b/lib/libzfs/libzfs_iter.c
@@ -0,0 +1,600 @@
+/*
+ * 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) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 2019 by Delphix. All rights reserved.
+ * Copyright 2014 Nexenta Systems, Inc. All rights reserved.
+ * Copyright (c) 2019 Datto Inc.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <unistd.h>
+#include <stddef.h>
+#include <libintl.h>
+#include <libzfs.h>
+#include <libzutil.h>
+#include <sys/mntent.h>
+
+#include "libzfs_impl.h"
+
+static int
+zfs_iter_clones(zfs_handle_t *zhp, zfs_iter_f func, void *data)
+{
+ nvlist_t *nvl = zfs_get_clones_nvl(zhp);
+ nvpair_t *pair;
+
+ if (nvl == NULL)
+ return (0);
+
+ for (pair = nvlist_next_nvpair(nvl, NULL); pair != NULL;
+ pair = nvlist_next_nvpair(nvl, pair)) {
+ zfs_handle_t *clone = zfs_open(zhp->zfs_hdl, nvpair_name(pair),
+ ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME);
+ if (clone != NULL) {
+ int err = func(clone, data);
+ if (err != 0)
+ return (err);
+ }
+ }
+ return (0);
+}
+
+static int
+zfs_do_list_ioctl(zfs_handle_t *zhp, int arg, zfs_cmd_t *zc)
+{
+ int rc;
+ uint64_t orig_cookie;
+
+ orig_cookie = zc->zc_cookie;
+top:
+ (void) strlcpy(zc->zc_name, zhp->zfs_name, sizeof (zc->zc_name));
+ rc = zfs_ioctl(zhp->zfs_hdl, arg, zc);
+
+ if (rc == -1) {
+ switch (errno) {
+ case ENOMEM:
+ /* expand nvlist memory and try again */
+ if (zcmd_expand_dst_nvlist(zhp->zfs_hdl, zc) != 0) {
+ zcmd_free_nvlists(zc);
+ return (-1);
+ }
+ zc->zc_cookie = orig_cookie;
+ goto top;
+ /*
+ * An errno value of ESRCH indicates normal completion.
+ * If ENOENT is returned, then the underlying dataset
+ * has been removed since we obtained the handle.
+ */
+ case ESRCH:
+ case ENOENT:
+ rc = 1;
+ break;
+ default:
+ rc = zfs_standard_error(zhp->zfs_hdl, errno,
+ dgettext(TEXT_DOMAIN,
+ "cannot iterate filesystems"));
+ break;
+ }
+ }
+ return (rc);
+}
+
+/*
+ * Iterate over all child filesystems
+ */
+int
+zfs_iter_filesystems(zfs_handle_t *zhp, zfs_iter_f func, void *data)
+{
+ zfs_cmd_t zc = {"\0"};
+ zfs_handle_t *nzhp;
+ int ret;
+
+ if (zhp->zfs_type != ZFS_TYPE_FILESYSTEM)
+ return (0);
+
+ if (zcmd_alloc_dst_nvlist(zhp->zfs_hdl, &zc, 0) != 0)
+ return (-1);
+
+ while ((ret = zfs_do_list_ioctl(zhp, ZFS_IOC_DATASET_LIST_NEXT,
+ &zc)) == 0) {
+ /*
+ * Silently ignore errors, as the only plausible explanation is
+ * that the pool has since been removed.
+ */
+ if ((nzhp = make_dataset_handle_zc(zhp->zfs_hdl,
+ &zc)) == NULL) {
+ continue;
+ }
+
+ if ((ret = func(nzhp, data)) != 0) {
+ zcmd_free_nvlists(&zc);
+ return (ret);
+ }
+ }
+ zcmd_free_nvlists(&zc);
+ return ((ret < 0) ? ret : 0);
+}
+
+/*
+ * Iterate over all snapshots
+ */
+int
+zfs_iter_snapshots(zfs_handle_t *zhp, boolean_t simple, zfs_iter_f func,
+ void *data, uint64_t min_txg, uint64_t max_txg)
+{
+ zfs_cmd_t zc = {"\0"};
+ zfs_handle_t *nzhp;
+ int ret;
+ nvlist_t *range_nvl = NULL;
+
+ if (zhp->zfs_type == ZFS_TYPE_SNAPSHOT ||
+ zhp->zfs_type == ZFS_TYPE_BOOKMARK)
+ return (0);
+
+ zc.zc_simple = simple;
+
+ if (zcmd_alloc_dst_nvlist(zhp->zfs_hdl, &zc, 0) != 0)
+ return (-1);
+
+ if (min_txg != 0) {
+ range_nvl = fnvlist_alloc();
+ fnvlist_add_uint64(range_nvl, SNAP_ITER_MIN_TXG, min_txg);
+ }
+ if (max_txg != 0) {
+ if (range_nvl == NULL)
+ range_nvl = fnvlist_alloc();
+ fnvlist_add_uint64(range_nvl, SNAP_ITER_MAX_TXG, max_txg);
+ }
+
+ if (range_nvl != NULL &&
+ zcmd_write_src_nvlist(zhp->zfs_hdl, &zc, range_nvl) != 0) {
+ zcmd_free_nvlists(&zc);
+ fnvlist_free(range_nvl);
+ return (-1);
+ }
+
+ while ((ret = zfs_do_list_ioctl(zhp, ZFS_IOC_SNAPSHOT_LIST_NEXT,
+ &zc)) == 0) {
+
+ if (simple)
+ nzhp = make_dataset_simple_handle_zc(zhp, &zc);
+ else
+ nzhp = make_dataset_handle_zc(zhp->zfs_hdl, &zc);
+ if (nzhp == NULL)
+ continue;
+
+ if ((ret = func(nzhp, data)) != 0) {
+ zcmd_free_nvlists(&zc);
+ fnvlist_free(range_nvl);
+ return (ret);
+ }
+ }
+ zcmd_free_nvlists(&zc);
+ fnvlist_free(range_nvl);
+ return ((ret < 0) ? ret : 0);
+}
+
+/*
+ * Iterate over all bookmarks
+ */
+int
+zfs_iter_bookmarks(zfs_handle_t *zhp, zfs_iter_f func, void *data)
+{
+ zfs_handle_t *nzhp;
+ nvlist_t *props = NULL;
+ nvlist_t *bmarks = NULL;
+ int err;
+ nvpair_t *pair;
+
+ if ((zfs_get_type(zhp) & (ZFS_TYPE_SNAPSHOT | ZFS_TYPE_BOOKMARK)) != 0)
+ return (0);
+
+ /* Setup the requested properties nvlist. */
+ props = fnvlist_alloc();
+ for (zfs_prop_t p = 0; p < ZFS_NUM_PROPS; p++) {
+ if (zfs_prop_valid_for_type(p, ZFS_TYPE_BOOKMARK, B_FALSE)) {
+ fnvlist_add_boolean(props, zfs_prop_to_name(p));
+ }
+ }
+ fnvlist_add_boolean(props, "redact_complete");
+
+ if ((err = lzc_get_bookmarks(zhp->zfs_name, props, &bmarks)) != 0)
+ goto out;
+
+ for (pair = nvlist_next_nvpair(bmarks, NULL);
+ pair != NULL; pair = nvlist_next_nvpair(bmarks, pair)) {
+ char name[ZFS_MAX_DATASET_NAME_LEN];
+ char *bmark_name;
+ nvlist_t *bmark_props;
+
+ bmark_name = nvpair_name(pair);
+ bmark_props = fnvpair_value_nvlist(pair);
+
+ if (snprintf(name, sizeof (name), "%s#%s", zhp->zfs_name,
+ bmark_name) >= sizeof (name)) {
+ err = EINVAL;
+ goto out;
+ }
+
+ nzhp = make_bookmark_handle(zhp, name, bmark_props);
+ if (nzhp == NULL)
+ continue;
+
+ if ((err = func(nzhp, data)) != 0)
+ goto out;
+ }
+
+out:
+ fnvlist_free(props);
+ fnvlist_free(bmarks);
+
+ return (err);
+}
+
+/*
+ * Routines for dealing with the sorted snapshot functionality
+ */
+typedef struct zfs_node {
+ zfs_handle_t *zn_handle;
+ avl_node_t zn_avlnode;
+} zfs_node_t;
+
+static int
+zfs_sort_snaps(zfs_handle_t *zhp, void *data)
+{
+ avl_tree_t *avl = data;
+ zfs_node_t *node;
+ zfs_node_t search;
+
+ search.zn_handle = zhp;
+ node = avl_find(avl, &search, NULL);
+ if (node) {
+ /*
+ * If this snapshot was renamed while we were creating the
+ * AVL tree, it's possible that we already inserted it under
+ * its old name. Remove the old handle before adding the new
+ * one.
+ */
+ zfs_close(node->zn_handle);
+ avl_remove(avl, node);
+ free(node);
+ }
+
+ node = zfs_alloc(zhp->zfs_hdl, sizeof (zfs_node_t));
+ node->zn_handle = zhp;
+ avl_add(avl, node);
+
+ return (0);
+}
+
+static int
+zfs_snapshot_compare(const void *larg, const void *rarg)
+{
+ zfs_handle_t *l = ((zfs_node_t *)larg)->zn_handle;
+ zfs_handle_t *r = ((zfs_node_t *)rarg)->zn_handle;
+ uint64_t lcreate, rcreate;
+
+ /*
+ * Sort them according to creation time. We use the hidden
+ * CREATETXG property to get an absolute ordering of snapshots.
+ */
+ lcreate = zfs_prop_get_int(l, ZFS_PROP_CREATETXG);
+ rcreate = zfs_prop_get_int(r, ZFS_PROP_CREATETXG);
+
+ return (TREE_CMP(lcreate, rcreate));
+}
+
+int
+zfs_iter_snapshots_sorted(zfs_handle_t *zhp, zfs_iter_f callback, void *data,
+ uint64_t min_txg, uint64_t max_txg)
+{
+ int ret = 0;
+ zfs_node_t *node;
+ avl_tree_t avl;
+ void *cookie = NULL;
+
+ avl_create(&avl, zfs_snapshot_compare,
+ sizeof (zfs_node_t), offsetof(zfs_node_t, zn_avlnode));
+
+ ret = zfs_iter_snapshots(zhp, B_FALSE, zfs_sort_snaps, &avl, min_txg,
+ max_txg);
+
+ for (node = avl_first(&avl); node != NULL; node = AVL_NEXT(&avl, node))
+ ret |= callback(node->zn_handle, data);
+
+ while ((node = avl_destroy_nodes(&avl, &cookie)) != NULL)
+ free(node);
+
+ avl_destroy(&avl);
+
+ return (ret);
+}
+
+typedef struct {
+ char *ssa_first;
+ char *ssa_last;
+ boolean_t ssa_seenfirst;
+ boolean_t ssa_seenlast;
+ zfs_iter_f ssa_func;
+ void *ssa_arg;
+} snapspec_arg_t;
+
+static int
+snapspec_cb(zfs_handle_t *zhp, void *arg)
+{
+ snapspec_arg_t *ssa = arg;
+ const char *shortsnapname;
+ int err = 0;
+
+ if (ssa->ssa_seenlast)
+ return (0);
+
+ shortsnapname = strchr(zfs_get_name(zhp), '@') + 1;
+ if (!ssa->ssa_seenfirst && strcmp(shortsnapname, ssa->ssa_first) == 0)
+ ssa->ssa_seenfirst = B_TRUE;
+ if (strcmp(shortsnapname, ssa->ssa_last) == 0)
+ ssa->ssa_seenlast = B_TRUE;
+
+ if (ssa->ssa_seenfirst) {
+ err = ssa->ssa_func(zhp, ssa->ssa_arg);
+ } else {
+ zfs_close(zhp);
+ }
+
+ return (err);
+}
+
+/*
+ * spec is a string like "A,B%C,D"
+ *
+ * <snaps>, where <snaps> can be:
+ * <snap> (single snapshot)
+ * <snap>%<snap> (range of snapshots, inclusive)
+ * %<snap> (range of snapshots, starting with earliest)
+ * <snap>% (range of snapshots, ending with last)
+ * % (all snapshots)
+ * <snaps>[,...] (comma separated list of the above)
+ *
+ * If a snapshot can not be opened, continue trying to open the others, but
+ * return ENOENT at the end.
+ */
+int
+zfs_iter_snapspec(zfs_handle_t *fs_zhp, const char *spec_orig,
+ zfs_iter_f func, void *arg)
+{
+ char *buf, *comma_separated, *cp;
+ int err = 0;
+ int ret = 0;
+
+ buf = zfs_strdup(fs_zhp->zfs_hdl, spec_orig);
+ cp = buf;
+
+ while ((comma_separated = strsep(&cp, ",")) != NULL) {
+ char *pct = strchr(comma_separated, '%');
+ if (pct != NULL) {
+ snapspec_arg_t ssa = { 0 };
+ ssa.ssa_func = func;
+ ssa.ssa_arg = arg;
+
+ if (pct == comma_separated)
+ ssa.ssa_seenfirst = B_TRUE;
+ else
+ ssa.ssa_first = comma_separated;
+ *pct = '\0';
+ ssa.ssa_last = pct + 1;
+
+ /*
+ * If there is a lastname specified, make sure it
+ * exists.
+ */
+ if (ssa.ssa_last[0] != '\0') {
+ char snapname[ZFS_MAX_DATASET_NAME_LEN];
+ (void) snprintf(snapname, sizeof (snapname),
+ "%s@%s", zfs_get_name(fs_zhp),
+ ssa.ssa_last);
+ if (!zfs_dataset_exists(fs_zhp->zfs_hdl,
+ snapname, ZFS_TYPE_SNAPSHOT)) {
+ ret = ENOENT;
+ continue;
+ }
+ }
+
+ err = zfs_iter_snapshots_sorted(fs_zhp,
+ snapspec_cb, &ssa, 0, 0);
+ if (ret == 0)
+ ret = err;
+ if (ret == 0 && (!ssa.ssa_seenfirst ||
+ (ssa.ssa_last[0] != '\0' && !ssa.ssa_seenlast))) {
+ ret = ENOENT;
+ }
+ } else {
+ char snapname[ZFS_MAX_DATASET_NAME_LEN];
+ zfs_handle_t *snap_zhp;
+ (void) snprintf(snapname, sizeof (snapname), "%s@%s",
+ zfs_get_name(fs_zhp), comma_separated);
+ snap_zhp = make_dataset_handle(fs_zhp->zfs_hdl,
+ snapname);
+ if (snap_zhp == NULL) {
+ ret = ENOENT;
+ continue;
+ }
+ err = func(snap_zhp, arg);
+ if (ret == 0)
+ ret = err;
+ }
+ }
+
+ free(buf);
+ return (ret);
+}
+
+/*
+ * Iterate over all children, snapshots and filesystems
+ * Process snapshots before filesystems because they are nearer the input
+ * handle: this is extremely important when used with zfs_iter_f functions
+ * looking for data, following the logic that we would like to find it as soon
+ * and as close as possible.
+ */
+int
+zfs_iter_children(zfs_handle_t *zhp, zfs_iter_f func, void *data)
+{
+ int ret;
+
+ if ((ret = zfs_iter_snapshots(zhp, B_FALSE, func, data, 0, 0)) != 0)
+ return (ret);
+
+ return (zfs_iter_filesystems(zhp, func, data));
+}
+
+
+typedef struct iter_stack_frame {
+ struct iter_stack_frame *next;
+ zfs_handle_t *zhp;
+} iter_stack_frame_t;
+
+typedef struct iter_dependents_arg {
+ boolean_t first;
+ boolean_t allowrecursion;
+ iter_stack_frame_t *stack;
+ zfs_iter_f func;
+ void *data;
+} iter_dependents_arg_t;
+
+static int
+iter_dependents_cb(zfs_handle_t *zhp, void *arg)
+{
+ iter_dependents_arg_t *ida = arg;
+ int err = 0;
+ boolean_t first = ida->first;
+ ida->first = B_FALSE;
+
+ if (zhp->zfs_type == ZFS_TYPE_SNAPSHOT) {
+ err = zfs_iter_clones(zhp, iter_dependents_cb, ida);
+ } else if (zhp->zfs_type != ZFS_TYPE_BOOKMARK) {
+ iter_stack_frame_t isf;
+ iter_stack_frame_t *f;
+
+ /*
+ * check if there is a cycle by seeing if this fs is already
+ * on the stack.
+ */
+ for (f = ida->stack; f != NULL; f = f->next) {
+ if (f->zhp->zfs_dmustats.dds_guid ==
+ zhp->zfs_dmustats.dds_guid) {
+ if (ida->allowrecursion) {
+ zfs_close(zhp);
+ return (0);
+ } else {
+ zfs_error_aux(zhp->zfs_hdl,
+ dgettext(TEXT_DOMAIN,
+ "recursive dependency at '%s'"),
+ zfs_get_name(zhp));
+ err = zfs_error(zhp->zfs_hdl,
+ EZFS_RECURSIVE,
+ dgettext(TEXT_DOMAIN,
+ "cannot determine dependent "
+ "datasets"));
+ zfs_close(zhp);
+ return (err);
+ }
+ }
+ }
+
+ isf.zhp = zhp;
+ isf.next = ida->stack;
+ ida->stack = &isf;
+ err = zfs_iter_filesystems(zhp, iter_dependents_cb, ida);
+ if (err == 0)
+ err = zfs_iter_snapshots(zhp, B_FALSE,
+ iter_dependents_cb, ida, 0, 0);
+ ida->stack = isf.next;
+ }
+
+ if (!first && err == 0)
+ err = ida->func(zhp, ida->data);
+ else
+ zfs_close(zhp);
+
+ return (err);
+}
+
+int
+zfs_iter_dependents(zfs_handle_t *zhp, boolean_t allowrecursion,
+ zfs_iter_f func, void *data)
+{
+ iter_dependents_arg_t ida;
+ ida.allowrecursion = allowrecursion;
+ ida.stack = NULL;
+ ida.func = func;
+ ida.data = data;
+ ida.first = B_TRUE;
+ return (iter_dependents_cb(zfs_handle_dup(zhp), &ida));
+}
+
+/*
+ * Iterate over mounted children of the specified dataset
+ */
+int
+zfs_iter_mounted(zfs_handle_t *zhp, zfs_iter_f func, void *data)
+{
+ char mnt_prop[ZFS_MAXPROPLEN];
+ struct mnttab entry;
+ zfs_handle_t *mtab_zhp;
+ size_t namelen = strlen(zhp->zfs_name);
+ FILE *mnttab;
+ int err = 0;
+
+ if ((mnttab = fopen(MNTTAB, "r")) == NULL)
+ return (ENOENT);
+
+ while (err == 0 && getmntent(mnttab, &entry) == 0) {
+ /* Ignore non-ZFS entries */
+ if (strcmp(entry.mnt_fstype, MNTTYPE_ZFS) != 0)
+ continue;
+
+ /* Ignore datasets not within the provided dataset */
+ if (strncmp(entry.mnt_special, zhp->zfs_name, namelen) != 0 ||
+ (entry.mnt_special[namelen] != '/' &&
+ entry.mnt_special[namelen] != '@'))
+ continue;
+
+ if ((mtab_zhp = zfs_open(zhp->zfs_hdl, entry.mnt_special,
+ ZFS_TYPE_FILESYSTEM)) == NULL)
+ continue;
+
+ /* Ignore legacy mounts as they are user managed */
+ verify(zfs_prop_get(mtab_zhp, ZFS_PROP_MOUNTPOINT, mnt_prop,
+ sizeof (mnt_prop), NULL, NULL, 0, B_FALSE) == 0);
+ if (strcmp(mnt_prop, "legacy") == 0) {
+ zfs_close(mtab_zhp);
+ continue;
+ }
+
+ err = func(mtab_zhp, data);
+ }
+
+ fclose(mnttab);
+
+ return (err);
+}
diff --git a/lib/libzfs/libzfs_mount.c b/lib/libzfs/libzfs_mount.c
new file mode 100644
index 000000000000..6d11016bca4e
--- /dev/null
+++ b/lib/libzfs/libzfs_mount.c
@@ -0,0 +1,1614 @@
+/*
+ * 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 2015 Nexenta Systems, Inc. All rights reserved.
+ * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2014, 2020 by Delphix. All rights reserved.
+ * Copyright 2016 Igor Kozhukhov <ikozhukhov@gmail.com>
+ * Copyright 2017 RackTop Systems.
+ * Copyright (c) 2018 Datto Inc.
+ * Copyright 2018 OmniOS Community Edition (OmniOSce) Association.
+ */
+
+/*
+ * Routines to manage ZFS mounts. We separate all the nasty routines that have
+ * to deal with the OS. The following functions are the main entry points --
+ * they are used by mount and unmount and when changing a filesystem's
+ * mountpoint.
+ *
+ * zfs_is_mounted()
+ * zfs_mount()
+ * zfs_mount_at()
+ * zfs_unmount()
+ * zfs_unmountall()
+ *
+ * This file also contains the functions used to manage sharing filesystems via
+ * NFS and iSCSI:
+ *
+ * zfs_is_shared()
+ * zfs_share()
+ * zfs_unshare()
+ *
+ * zfs_is_shared_nfs()
+ * zfs_is_shared_smb()
+ * zfs_share_proto()
+ * zfs_shareall();
+ * zfs_unshare_nfs()
+ * zfs_unshare_smb()
+ * zfs_unshareall_nfs()
+ * zfs_unshareall_smb()
+ * zfs_unshareall()
+ * zfs_unshareall_bypath()
+ *
+ * The following functions are available for pool consumers, and will
+ * mount/unmount and share/unshare all datasets within pool:
+ *
+ * zpool_enable_datasets()
+ * zpool_disable_datasets()
+ */
+
+#include <dirent.h>
+#include <dlfcn.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <libintl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <unistd.h>
+#include <zone.h>
+#include <sys/mntent.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/vfs.h>
+#include <sys/dsl_crypt.h>
+
+#include <libzfs.h>
+
+#include "libzfs_impl.h"
+#include <thread_pool.h>
+
+#include <libshare.h>
+#include <sys/systeminfo.h>
+#define MAXISALEN 257 /* based on sysinfo(2) man page */
+
+static int mount_tp_nthr = 512; /* tpool threads for multi-threaded mounting */
+
+static void zfs_mount_task(void *);
+zfs_share_type_t zfs_is_shared_proto(zfs_handle_t *, char **,
+ zfs_share_proto_t);
+
+/*
+ * The share protocols table must be in the same order as the zfs_share_proto_t
+ * enum in libzfs_impl.h
+ */
+proto_table_t proto_table[PROTO_END] = {
+ {ZFS_PROP_SHARENFS, "nfs", EZFS_SHARENFSFAILED, EZFS_UNSHARENFSFAILED},
+ {ZFS_PROP_SHARESMB, "smb", EZFS_SHARESMBFAILED, EZFS_UNSHARESMBFAILED},
+};
+
+zfs_share_proto_t nfs_only[] = {
+ PROTO_NFS,
+ PROTO_END
+};
+
+zfs_share_proto_t smb_only[] = {
+ PROTO_SMB,
+ PROTO_END
+};
+zfs_share_proto_t share_all_proto[] = {
+ PROTO_NFS,
+ PROTO_SMB,
+ PROTO_END
+};
+
+
+
+static boolean_t
+dir_is_empty_stat(const char *dirname)
+{
+ struct stat st;
+
+ /*
+ * We only want to return false if the given path is a non empty
+ * directory, all other errors are handled elsewhere.
+ */
+ if (stat(dirname, &st) < 0 || !S_ISDIR(st.st_mode)) {
+ return (B_TRUE);
+ }
+
+ /*
+ * An empty directory will still have two entries in it, one
+ * entry for each of "." and "..".
+ */
+ if (st.st_size > 2) {
+ return (B_FALSE);
+ }
+
+ return (B_TRUE);
+}
+
+static boolean_t
+dir_is_empty_readdir(const char *dirname)
+{
+ DIR *dirp;
+ struct dirent64 *dp;
+ int dirfd;
+
+ if ((dirfd = openat(AT_FDCWD, dirname,
+ O_RDONLY | O_NDELAY | O_LARGEFILE | O_CLOEXEC, 0)) < 0) {
+ return (B_TRUE);
+ }
+
+ if ((dirp = fdopendir(dirfd)) == NULL) {
+ (void) close(dirfd);
+ return (B_TRUE);
+ }
+
+ while ((dp = readdir64(dirp)) != NULL) {
+
+ if (strcmp(dp->d_name, ".") == 0 ||
+ strcmp(dp->d_name, "..") == 0)
+ continue;
+
+ (void) closedir(dirp);
+ return (B_FALSE);
+ }
+
+ (void) closedir(dirp);
+ return (B_TRUE);
+}
+
+/*
+ * Returns true if the specified directory is empty. If we can't open the
+ * directory at all, return true so that the mount can fail with a more
+ * informative error message.
+ */
+static boolean_t
+dir_is_empty(const char *dirname)
+{
+ struct statfs64 st;
+
+ /*
+ * If the statvfs call fails or the filesystem is not a ZFS
+ * filesystem, fall back to the slow path which uses readdir.
+ */
+ if ((statfs64(dirname, &st) != 0) ||
+ (st.f_type != ZFS_SUPER_MAGIC)) {
+ return (dir_is_empty_readdir(dirname));
+ }
+
+ /*
+ * At this point, we know the provided path is on a ZFS
+ * filesystem, so we can use stat instead of readdir to
+ * determine if the directory is empty or not. We try to avoid
+ * using readdir because that requires opening "dirname"; this
+ * open file descriptor can potentially end up in a child
+ * process if there's a concurrent fork, thus preventing the
+ * zfs_mount() from otherwise succeeding (the open file
+ * descriptor inherited by the child process will cause the
+ * parent's mount to fail with EBUSY). The performance
+ * implications of replacing the open, read, and close with a
+ * single stat is nice; but is not the main motivation for the
+ * added complexity.
+ */
+ return (dir_is_empty_stat(dirname));
+}
+
+/*
+ * Checks to see if the mount is active. If the filesystem is mounted, we fill
+ * in 'where' with the current mountpoint, and return 1. Otherwise, we return
+ * 0.
+ */
+boolean_t
+is_mounted(libzfs_handle_t *zfs_hdl, const char *special, char **where)
+{
+ struct mnttab entry;
+
+ if (libzfs_mnttab_find(zfs_hdl, special, &entry) != 0)
+ return (B_FALSE);
+
+ if (where != NULL)
+ *where = zfs_strdup(zfs_hdl, entry.mnt_mountp);
+
+ return (B_TRUE);
+}
+
+boolean_t
+zfs_is_mounted(zfs_handle_t *zhp, char **where)
+{
+ return (is_mounted(zhp->zfs_hdl, zfs_get_name(zhp), where));
+}
+
+/*
+ * Checks any higher order concerns about whether the given dataset is
+ * mountable, false otherwise. zfs_is_mountable_internal specifically assumes
+ * that the caller has verified the sanity of mounting the dataset at
+ * mountpoint to the extent the caller wants.
+ */
+static boolean_t
+zfs_is_mountable_internal(zfs_handle_t *zhp, const char *mountpoint)
+{
+
+ if (zfs_prop_get_int(zhp, ZFS_PROP_ZONED) &&
+ getzoneid() == GLOBAL_ZONEID)
+ return (B_FALSE);
+
+ return (B_TRUE);
+}
+
+/*
+ * Returns true if the given dataset is mountable, false otherwise. Returns the
+ * mountpoint in 'buf'.
+ */
+boolean_t
+zfs_is_mountable(zfs_handle_t *zhp, char *buf, size_t buflen,
+ zprop_source_t *source, int flags)
+{
+ char sourceloc[MAXNAMELEN];
+ zprop_source_t sourcetype;
+
+ if (!zfs_prop_valid_for_type(ZFS_PROP_MOUNTPOINT, zhp->zfs_type,
+ B_FALSE))
+ return (B_FALSE);
+
+ verify(zfs_prop_get(zhp, ZFS_PROP_MOUNTPOINT, buf, buflen,
+ &sourcetype, sourceloc, sizeof (sourceloc), B_FALSE) == 0);
+
+ if (strcmp(buf, ZFS_MOUNTPOINT_NONE) == 0 ||
+ strcmp(buf, ZFS_MOUNTPOINT_LEGACY) == 0)
+ return (B_FALSE);
+
+ if (zfs_prop_get_int(zhp, ZFS_PROP_CANMOUNT) == ZFS_CANMOUNT_OFF)
+ return (B_FALSE);
+
+ if (!zfs_is_mountable_internal(zhp, buf))
+ return (B_FALSE);
+
+ if (zfs_prop_get_int(zhp, ZFS_PROP_REDACTED) && !(flags & MS_FORCE))
+ return (B_FALSE);
+
+ if (source)
+ *source = sourcetype;
+
+ return (B_TRUE);
+}
+
+/*
+ * The filesystem is mounted by invoking the system mount utility rather
+ * than by the system call mount(2). This ensures that the /etc/mtab
+ * file is correctly locked for the update. Performing our own locking
+ * and /etc/mtab update requires making an unsafe assumption about how
+ * the mount utility performs its locking. Unfortunately, this also means
+ * in the case of a mount failure we do not have the exact errno. We must
+ * make due with return value from the mount process.
+ *
+ * In the long term a shared library called libmount is under development
+ * which provides a common API to address the locking and errno issues.
+ * Once the standard mount utility has been updated to use this library
+ * we can add an autoconf check to conditionally use it.
+ *
+ * http://www.kernel.org/pub/linux/utils/util-linux/libmount-docs/index.html
+ */
+
+static int
+zfs_add_option(zfs_handle_t *zhp, char *options, int len,
+ zfs_prop_t prop, char *on, char *off)
+{
+ char *source;
+ uint64_t value;
+
+ /* Skip adding duplicate default options */
+ if ((strstr(options, on) != NULL) || (strstr(options, off) != NULL))
+ return (0);
+
+ /*
+ * zfs_prop_get_int() is not used to ensure our mount options
+ * are not influenced by the current /proc/self/mounts contents.
+ */
+ value = getprop_uint64(zhp, prop, &source);
+
+ (void) strlcat(options, ",", len);
+ (void) strlcat(options, value ? on : off, len);
+
+ return (0);
+}
+
+static int
+zfs_add_options(zfs_handle_t *zhp, char *options, int len)
+{
+ int error = 0;
+
+ error = zfs_add_option(zhp, options, len,
+ ZFS_PROP_ATIME, MNTOPT_ATIME, MNTOPT_NOATIME);
+ /*
+ * don't add relatime/strictatime when atime=off, otherwise strictatime
+ * will force atime=on
+ */
+ if (strstr(options, MNTOPT_NOATIME) == NULL) {
+ error = zfs_add_option(zhp, options, len,
+ ZFS_PROP_RELATIME, MNTOPT_RELATIME, MNTOPT_STRICTATIME);
+ }
+ error = error ? error : zfs_add_option(zhp, options, len,
+ ZFS_PROP_DEVICES, MNTOPT_DEVICES, MNTOPT_NODEVICES);
+ error = error ? error : zfs_add_option(zhp, options, len,
+ ZFS_PROP_EXEC, MNTOPT_EXEC, MNTOPT_NOEXEC);
+ error = error ? error : zfs_add_option(zhp, options, len,
+ ZFS_PROP_READONLY, MNTOPT_RO, MNTOPT_RW);
+ error = error ? error : zfs_add_option(zhp, options, len,
+ ZFS_PROP_SETUID, MNTOPT_SETUID, MNTOPT_NOSETUID);
+ error = error ? error : zfs_add_option(zhp, options, len,
+ ZFS_PROP_NBMAND, MNTOPT_NBMAND, MNTOPT_NONBMAND);
+
+ return (error);
+}
+
+int
+zfs_mount(zfs_handle_t *zhp, const char *options, int flags)
+{
+ char mountpoint[ZFS_MAXPROPLEN];
+
+ if (!zfs_is_mountable(zhp, mountpoint, sizeof (mountpoint), NULL,
+ flags))
+ return (0);
+
+ return (zfs_mount_at(zhp, options, flags, mountpoint));
+}
+
+/*
+ * Mount the given filesystem.
+ */
+int
+zfs_mount_at(zfs_handle_t *zhp, const char *options, int flags,
+ const char *mountpoint)
+{
+ struct stat buf;
+ char mntopts[MNT_LINE_MAX];
+ char overlay[ZFS_MAXPROPLEN];
+ libzfs_handle_t *hdl = zhp->zfs_hdl;
+ uint64_t keystatus;
+ int remount = 0, rc;
+
+ if (options == NULL) {
+ (void) strlcpy(mntopts, MNTOPT_DEFAULTS, sizeof (mntopts));
+ } else {
+ (void) strlcpy(mntopts, options, sizeof (mntopts));
+ }
+
+ if (strstr(mntopts, MNTOPT_REMOUNT) != NULL)
+ remount = 1;
+
+ /* Potentially duplicates some checks if invoked by zfs_mount(). */
+ if (!zfs_is_mountable_internal(zhp, mountpoint))
+ return (0);
+
+ /*
+ * If the pool is imported read-only then all mounts must be read-only
+ */
+ if (zpool_get_prop_int(zhp->zpool_hdl, ZPOOL_PROP_READONLY, NULL))
+ (void) strlcat(mntopts, "," MNTOPT_RO, sizeof (mntopts));
+
+ /*
+ * Append default mount options which apply to the mount point.
+ * This is done because under Linux (unlike Solaris) multiple mount
+ * points may reference a single super block. This means that just
+ * given a super block there is no back reference to update the per
+ * mount point options.
+ */
+ rc = zfs_add_options(zhp, mntopts, sizeof (mntopts));
+ if (rc) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "default options unavailable"));
+ return (zfs_error_fmt(hdl, EZFS_MOUNTFAILED,
+ dgettext(TEXT_DOMAIN, "cannot mount '%s'"),
+ mountpoint));
+ }
+
+ /*
+ * If the filesystem is encrypted the key must be loaded in order to
+ * mount. If the key isn't loaded, the MS_CRYPT flag decides whether
+ * or not we attempt to load the keys. Note: we must call
+ * zfs_refresh_properties() here since some callers of this function
+ * (most notably zpool_enable_datasets()) may implicitly load our key
+ * by loading the parent's key first.
+ */
+ if (zfs_prop_get_int(zhp, ZFS_PROP_ENCRYPTION) != ZIO_CRYPT_OFF) {
+ zfs_refresh_properties(zhp);
+ keystatus = zfs_prop_get_int(zhp, ZFS_PROP_KEYSTATUS);
+
+ /*
+ * If the key is unavailable and MS_CRYPT is set give the
+ * user a chance to enter the key. Otherwise just fail
+ * immediately.
+ */
+ if (keystatus == ZFS_KEYSTATUS_UNAVAILABLE) {
+ if (flags & MS_CRYPT) {
+ rc = zfs_crypto_load_key(zhp, B_FALSE, NULL);
+ if (rc)
+ return (rc);
+ } else {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "encryption key not loaded"));
+ return (zfs_error_fmt(hdl, EZFS_MOUNTFAILED,
+ dgettext(TEXT_DOMAIN, "cannot mount '%s'"),
+ mountpoint));
+ }
+ }
+
+ }
+
+ /*
+ * Append zfsutil option so the mount helper allow the mount
+ */
+ strlcat(mntopts, "," MNTOPT_ZFSUTIL, sizeof (mntopts));
+
+ /* Create the directory if it doesn't already exist */
+ if (lstat(mountpoint, &buf) != 0) {
+ if (mkdirp(mountpoint, 0755) != 0) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "failed to create mountpoint: %s"),
+ strerror(errno));
+ return (zfs_error_fmt(hdl, EZFS_MOUNTFAILED,
+ dgettext(TEXT_DOMAIN, "cannot mount '%s'"),
+ mountpoint));
+ }
+ }
+
+ /*
+ * Overlay mounts are enabled by default but may be disabled
+ * via the 'overlay' property. The -O flag remains for compatibility.
+ */
+ if (!(flags & MS_OVERLAY)) {
+ if (zfs_prop_get(zhp, ZFS_PROP_OVERLAY, overlay,
+ sizeof (overlay), NULL, NULL, 0, B_FALSE) == 0) {
+ if (strcmp(overlay, "on") == 0) {
+ flags |= MS_OVERLAY;
+ }
+ }
+ }
+
+ /*
+ * Determine if the mountpoint is empty. If so, refuse to perform the
+ * mount. We don't perform this check if 'remount' is
+ * specified or if overlay option (-O) is given
+ */
+ if ((flags & MS_OVERLAY) == 0 && !remount &&
+ !dir_is_empty(mountpoint)) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "directory is not empty"));
+ return (zfs_error_fmt(hdl, EZFS_MOUNTFAILED,
+ dgettext(TEXT_DOMAIN, "cannot mount '%s'"), mountpoint));
+ }
+
+ /* perform the mount */
+ rc = do_mount(zhp, mountpoint, mntopts, flags);
+ if (rc) {
+ /*
+ * Generic errors are nasty, but there are just way too many
+ * from mount(), and they're well-understood. We pick a few
+ * common ones to improve upon.
+ */
+ if (rc == EBUSY) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "mountpoint or dataset is busy"));
+ } else if (rc == EPERM) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Insufficient privileges"));
+ } else if (rc == ENOTSUP) {
+ char buf[256];
+ int spa_version;
+
+ VERIFY(zfs_spa_version(zhp, &spa_version) == 0);
+ (void) snprintf(buf, sizeof (buf),
+ dgettext(TEXT_DOMAIN, "Can't mount a version %lld "
+ "file system on a version %d pool. Pool must be"
+ " upgraded to mount this file system."),
+ (u_longlong_t)zfs_prop_get_int(zhp,
+ ZFS_PROP_VERSION), spa_version);
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, buf));
+ } else {
+ zfs_error_aux(hdl, strerror(rc));
+ }
+ return (zfs_error_fmt(hdl, EZFS_MOUNTFAILED,
+ dgettext(TEXT_DOMAIN, "cannot mount '%s'"),
+ zhp->zfs_name));
+ }
+
+ /* remove the mounted entry before re-adding on remount */
+ if (remount)
+ libzfs_mnttab_remove(hdl, zhp->zfs_name);
+
+ /* add the mounted entry into our cache */
+ libzfs_mnttab_add(hdl, zfs_get_name(zhp), mountpoint, mntopts);
+ return (0);
+}
+
+/*
+ * Unmount a single filesystem.
+ */
+static int
+unmount_one(libzfs_handle_t *hdl, const char *mountpoint, int flags)
+{
+ int error;
+
+ error = do_unmount(mountpoint, flags);
+ if (error != 0) {
+ return (zfs_error_fmt(hdl, EZFS_UMOUNTFAILED,
+ dgettext(TEXT_DOMAIN, "cannot unmount '%s'"),
+ mountpoint));
+ }
+
+ return (0);
+}
+
+/*
+ * Unmount the given filesystem.
+ */
+int
+zfs_unmount(zfs_handle_t *zhp, const char *mountpoint, int flags)
+{
+ libzfs_handle_t *hdl = zhp->zfs_hdl;
+ struct mnttab entry;
+ char *mntpt = NULL;
+ boolean_t encroot, unmounted = B_FALSE;
+
+ /* check to see if we need to unmount the filesystem */
+ if (mountpoint != NULL || ((zfs_get_type(zhp) == ZFS_TYPE_FILESYSTEM) &&
+ libzfs_mnttab_find(hdl, zhp->zfs_name, &entry) == 0)) {
+ /*
+ * mountpoint may have come from a call to
+ * getmnt/getmntany if it isn't NULL. If it is NULL,
+ * we know it comes from libzfs_mnttab_find which can
+ * then get freed later. We strdup it to play it safe.
+ */
+ if (mountpoint == NULL)
+ mntpt = zfs_strdup(hdl, entry.mnt_mountp);
+ else
+ mntpt = zfs_strdup(hdl, mountpoint);
+
+ /*
+ * Unshare and unmount the filesystem
+ */
+ if (zfs_unshare_proto(zhp, mntpt, share_all_proto) != 0) {
+ free(mntpt);
+ return (-1);
+ }
+ zfs_commit_all_shares();
+
+ if (unmount_one(hdl, mntpt, flags) != 0) {
+ free(mntpt);
+ (void) zfs_shareall(zhp);
+ zfs_commit_all_shares();
+ return (-1);
+ }
+
+ libzfs_mnttab_remove(hdl, zhp->zfs_name);
+ free(mntpt);
+ unmounted = B_TRUE;
+ }
+
+ /*
+ * If the MS_CRYPT flag is provided we must ensure we attempt to
+ * unload the dataset's key regardless of whether we did any work
+ * to unmount it. We only do this for encryption roots.
+ */
+ if ((flags & MS_CRYPT) != 0 &&
+ zfs_prop_get_int(zhp, ZFS_PROP_ENCRYPTION) != ZIO_CRYPT_OFF) {
+ zfs_refresh_properties(zhp);
+
+ if (zfs_crypto_get_encryption_root(zhp, &encroot, NULL) != 0 &&
+ unmounted) {
+ (void) zfs_mount(zhp, NULL, 0);
+ return (-1);
+ }
+
+ if (encroot && zfs_prop_get_int(zhp, ZFS_PROP_KEYSTATUS) ==
+ ZFS_KEYSTATUS_AVAILABLE &&
+ zfs_crypto_unload_key(zhp) != 0) {
+ (void) zfs_mount(zhp, NULL, 0);
+ return (-1);
+ }
+ }
+
+ return (0);
+}
+
+/*
+ * Unmount this filesystem and any children inheriting the mountpoint property.
+ * To do this, just act like we're changing the mountpoint property, but don't
+ * remount the filesystems afterwards.
+ */
+int
+zfs_unmountall(zfs_handle_t *zhp, int flags)
+{
+ prop_changelist_t *clp;
+ int ret;
+
+ clp = changelist_gather(zhp, ZFS_PROP_MOUNTPOINT,
+ CL_GATHER_ITER_MOUNTED, flags);
+ if (clp == NULL)
+ return (-1);
+
+ ret = changelist_prefix(clp);
+ changelist_free(clp);
+
+ return (ret);
+}
+
+boolean_t
+zfs_is_shared(zfs_handle_t *zhp)
+{
+ zfs_share_type_t rc = 0;
+ zfs_share_proto_t *curr_proto;
+
+ if (ZFS_IS_VOLUME(zhp))
+ return (B_FALSE);
+
+ for (curr_proto = share_all_proto; *curr_proto != PROTO_END;
+ curr_proto++)
+ rc |= zfs_is_shared_proto(zhp, NULL, *curr_proto);
+
+ return (rc ? B_TRUE : B_FALSE);
+}
+
+/*
+ * Unshare a filesystem by mountpoint.
+ */
+int
+unshare_one(libzfs_handle_t *hdl, const char *name, const char *mountpoint,
+ zfs_share_proto_t proto)
+{
+ int err;
+
+ err = sa_disable_share(mountpoint, proto_table[proto].p_name);
+ if (err != SA_OK) {
+ return (zfs_error_fmt(hdl, proto_table[proto].p_unshare_err,
+ dgettext(TEXT_DOMAIN, "cannot unshare '%s': %s"),
+ name, sa_errorstr(err)));
+ }
+ return (0);
+}
+
+/*
+ * Query libshare for the given mountpoint and protocol, returning
+ * a zfs_share_type_t value.
+ */
+zfs_share_type_t
+is_shared(const char *mountpoint, zfs_share_proto_t proto)
+{
+ if (sa_is_shared(mountpoint, proto_table[proto].p_name)) {
+ switch (proto) {
+ case PROTO_NFS:
+ return (SHARED_NFS);
+ case PROTO_SMB:
+ return (SHARED_SMB);
+ default:
+ return (SHARED_NOT_SHARED);
+ }
+ }
+ return (SHARED_NOT_SHARED);
+}
+
+/*
+ * Share the given filesystem according to the options in the specified
+ * protocol specific properties (sharenfs, sharesmb). We rely
+ * on "libshare" to do the dirty work for us.
+ */
+int
+zfs_share_proto(zfs_handle_t *zhp, zfs_share_proto_t *proto)
+{
+ char mountpoint[ZFS_MAXPROPLEN];
+ char shareopts[ZFS_MAXPROPLEN];
+ char sourcestr[ZFS_MAXPROPLEN];
+ zfs_share_proto_t *curr_proto;
+ zprop_source_t sourcetype;
+ int err = 0;
+
+ if (!zfs_is_mountable(zhp, mountpoint, sizeof (mountpoint), NULL, 0))
+ return (0);
+
+ for (curr_proto = proto; *curr_proto != PROTO_END; curr_proto++) {
+ /*
+ * Return success if there are no share options.
+ */
+ if (zfs_prop_get(zhp, proto_table[*curr_proto].p_prop,
+ shareopts, sizeof (shareopts), &sourcetype, sourcestr,
+ ZFS_MAXPROPLEN, B_FALSE) != 0 ||
+ strcmp(shareopts, "off") == 0)
+ continue;
+
+ /*
+ * If the 'zoned' property is set, then zfs_is_mountable()
+ * will have already bailed out if we are in the global zone.
+ * But local zones cannot be NFS servers, so we ignore it for
+ * local zones as well.
+ */
+ if (zfs_prop_get_int(zhp, ZFS_PROP_ZONED))
+ continue;
+
+ err = sa_enable_share(zfs_get_name(zhp), mountpoint, shareopts,
+ proto_table[*curr_proto].p_name);
+ if (err != SA_OK) {
+ return (zfs_error_fmt(zhp->zfs_hdl,
+ proto_table[*curr_proto].p_share_err,
+ dgettext(TEXT_DOMAIN, "cannot share '%s: %s'"),
+ zfs_get_name(zhp), sa_errorstr(err)));
+ }
+
+ }
+ return (0);
+}
+
+int
+zfs_share(zfs_handle_t *zhp)
+{
+ assert(!ZFS_IS_VOLUME(zhp));
+ return (zfs_share_proto(zhp, share_all_proto));
+}
+
+int
+zfs_unshare(zfs_handle_t *zhp)
+{
+ assert(!ZFS_IS_VOLUME(zhp));
+ return (zfs_unshareall(zhp));
+}
+
+/*
+ * Check to see if the filesystem is currently shared.
+ */
+zfs_share_type_t
+zfs_is_shared_proto(zfs_handle_t *zhp, char **where, zfs_share_proto_t proto)
+{
+ char *mountpoint;
+ zfs_share_type_t rc;
+
+ if (!zfs_is_mounted(zhp, &mountpoint))
+ return (SHARED_NOT_SHARED);
+
+ if ((rc = is_shared(mountpoint, proto))
+ != SHARED_NOT_SHARED) {
+ if (where != NULL)
+ *where = mountpoint;
+ else
+ free(mountpoint);
+ return (rc);
+ } else {
+ free(mountpoint);
+ return (SHARED_NOT_SHARED);
+ }
+}
+
+boolean_t
+zfs_is_shared_nfs(zfs_handle_t *zhp, char **where)
+{
+ return (zfs_is_shared_proto(zhp, where,
+ PROTO_NFS) != SHARED_NOT_SHARED);
+}
+
+boolean_t
+zfs_is_shared_smb(zfs_handle_t *zhp, char **where)
+{
+ return (zfs_is_shared_proto(zhp, where,
+ PROTO_SMB) != SHARED_NOT_SHARED);
+}
+
+/*
+ * zfs_parse_options(options, proto)
+ *
+ * Call the legacy parse interface to get the protocol specific
+ * options using the NULL arg to indicate that this is a "parse" only.
+ */
+int
+zfs_parse_options(char *options, zfs_share_proto_t proto)
+{
+ return (sa_validate_shareopts(options, proto_table[proto].p_name));
+}
+
+void
+zfs_commit_proto(zfs_share_proto_t *proto)
+{
+ zfs_share_proto_t *curr_proto;
+ for (curr_proto = proto; *curr_proto != PROTO_END; curr_proto++) {
+ sa_commit_shares(proto_table[*curr_proto].p_name);
+ }
+}
+
+void
+zfs_commit_nfs_shares(void)
+{
+ zfs_commit_proto(nfs_only);
+}
+
+void
+zfs_commit_smb_shares(void)
+{
+ zfs_commit_proto(smb_only);
+}
+
+void
+zfs_commit_all_shares(void)
+{
+ zfs_commit_proto(share_all_proto);
+}
+
+void
+zfs_commit_shares(const char *proto)
+{
+ if (proto == NULL)
+ zfs_commit_proto(share_all_proto);
+ else if (strcmp(proto, "nfs") == 0)
+ zfs_commit_proto(nfs_only);
+ else if (strcmp(proto, "smb") == 0)
+ zfs_commit_proto(smb_only);
+}
+
+int
+zfs_share_nfs(zfs_handle_t *zhp)
+{
+ return (zfs_share_proto(zhp, nfs_only));
+}
+
+int
+zfs_share_smb(zfs_handle_t *zhp)
+{
+ return (zfs_share_proto(zhp, smb_only));
+}
+
+int
+zfs_shareall(zfs_handle_t *zhp)
+{
+ return (zfs_share_proto(zhp, share_all_proto));
+}
+
+/*
+ * Unshare the given filesystem.
+ */
+int
+zfs_unshare_proto(zfs_handle_t *zhp, const char *mountpoint,
+ zfs_share_proto_t *proto)
+{
+ libzfs_handle_t *hdl = zhp->zfs_hdl;
+ struct mnttab entry;
+ char *mntpt = NULL;
+
+ /* check to see if need to unmount the filesystem */
+ if (mountpoint != NULL)
+ mntpt = zfs_strdup(hdl, mountpoint);
+
+ if (mountpoint != NULL || ((zfs_get_type(zhp) == ZFS_TYPE_FILESYSTEM) &&
+ libzfs_mnttab_find(hdl, zfs_get_name(zhp), &entry) == 0)) {
+ zfs_share_proto_t *curr_proto;
+
+ if (mountpoint == NULL)
+ mntpt = zfs_strdup(zhp->zfs_hdl, entry.mnt_mountp);
+
+ for (curr_proto = proto; *curr_proto != PROTO_END;
+ curr_proto++) {
+
+ if (is_shared(mntpt, *curr_proto)) {
+ if (unshare_one(hdl, zhp->zfs_name,
+ mntpt, *curr_proto) != 0) {
+ if (mntpt != NULL)
+ free(mntpt);
+ return (-1);
+ }
+ }
+ }
+ }
+ if (mntpt != NULL)
+ free(mntpt);
+
+ return (0);
+}
+
+int
+zfs_unshare_nfs(zfs_handle_t *zhp, const char *mountpoint)
+{
+ return (zfs_unshare_proto(zhp, mountpoint, nfs_only));
+}
+
+int
+zfs_unshare_smb(zfs_handle_t *zhp, const char *mountpoint)
+{
+ return (zfs_unshare_proto(zhp, mountpoint, smb_only));
+}
+
+/*
+ * Same as zfs_unmountall(), but for NFS and SMB unshares.
+ */
+static int
+zfs_unshareall_proto(zfs_handle_t *zhp, zfs_share_proto_t *proto)
+{
+ prop_changelist_t *clp;
+ int ret;
+
+ clp = changelist_gather(zhp, ZFS_PROP_SHARENFS, 0, 0);
+ if (clp == NULL)
+ return (-1);
+
+ ret = changelist_unshare(clp, proto);
+ changelist_free(clp);
+
+ return (ret);
+}
+
+int
+zfs_unshareall_nfs(zfs_handle_t *zhp)
+{
+ return (zfs_unshareall_proto(zhp, nfs_only));
+}
+
+int
+zfs_unshareall_smb(zfs_handle_t *zhp)
+{
+ return (zfs_unshareall_proto(zhp, smb_only));
+}
+
+int
+zfs_unshareall(zfs_handle_t *zhp)
+{
+ return (zfs_unshareall_proto(zhp, share_all_proto));
+}
+
+int
+zfs_unshareall_bypath(zfs_handle_t *zhp, const char *mountpoint)
+{
+ return (zfs_unshare_proto(zhp, mountpoint, share_all_proto));
+}
+
+int
+zfs_unshareall_bytype(zfs_handle_t *zhp, const char *mountpoint,
+ const char *proto)
+{
+ if (proto == NULL)
+ return (zfs_unshare_proto(zhp, mountpoint, share_all_proto));
+ if (strcmp(proto, "nfs") == 0)
+ return (zfs_unshare_proto(zhp, mountpoint, nfs_only));
+ else if (strcmp(proto, "smb") == 0)
+ return (zfs_unshare_proto(zhp, mountpoint, smb_only));
+ else
+ return (1);
+}
+
+/*
+ * Remove the mountpoint associated with the current dataset, if necessary.
+ * We only remove the underlying directory if:
+ *
+ * - The mountpoint is not 'none' or 'legacy'
+ * - The mountpoint is non-empty
+ * - The mountpoint is the default or inherited
+ * - The 'zoned' property is set, or we're in a local zone
+ *
+ * Any other directories we leave alone.
+ */
+void
+remove_mountpoint(zfs_handle_t *zhp)
+{
+ char mountpoint[ZFS_MAXPROPLEN];
+ zprop_source_t source;
+
+ if (!zfs_is_mountable(zhp, mountpoint, sizeof (mountpoint),
+ &source, 0))
+ return;
+
+ if (source == ZPROP_SRC_DEFAULT ||
+ source == ZPROP_SRC_INHERITED) {
+ /*
+ * Try to remove the directory, silently ignoring any errors.
+ * The filesystem may have since been removed or moved around,
+ * and this error isn't really useful to the administrator in
+ * any way.
+ */
+ (void) rmdir(mountpoint);
+ }
+}
+
+/*
+ * Add the given zfs handle to the cb_handles array, dynamically reallocating
+ * the array if it is out of space.
+ */
+void
+libzfs_add_handle(get_all_cb_t *cbp, zfs_handle_t *zhp)
+{
+ if (cbp->cb_alloc == cbp->cb_used) {
+ size_t newsz;
+ zfs_handle_t **newhandles;
+
+ newsz = cbp->cb_alloc != 0 ? cbp->cb_alloc * 2 : 64;
+ newhandles = zfs_realloc(zhp->zfs_hdl,
+ cbp->cb_handles, cbp->cb_alloc * sizeof (zfs_handle_t *),
+ newsz * sizeof (zfs_handle_t *));
+ cbp->cb_handles = newhandles;
+ cbp->cb_alloc = newsz;
+ }
+ cbp->cb_handles[cbp->cb_used++] = zhp;
+}
+
+/*
+ * Recursive helper function used during file system enumeration
+ */
+static int
+zfs_iter_cb(zfs_handle_t *zhp, void *data)
+{
+ get_all_cb_t *cbp = data;
+
+ if (!(zfs_get_type(zhp) & ZFS_TYPE_FILESYSTEM)) {
+ zfs_close(zhp);
+ return (0);
+ }
+
+ if (zfs_prop_get_int(zhp, ZFS_PROP_CANMOUNT) == ZFS_CANMOUNT_NOAUTO) {
+ zfs_close(zhp);
+ return (0);
+ }
+
+ if (zfs_prop_get_int(zhp, ZFS_PROP_KEYSTATUS) ==
+ ZFS_KEYSTATUS_UNAVAILABLE) {
+ zfs_close(zhp);
+ return (0);
+ }
+
+ /*
+ * If this filesystem is inconsistent and has a receive resume
+ * token, we can not mount it.
+ */
+ if (zfs_prop_get_int(zhp, ZFS_PROP_INCONSISTENT) &&
+ zfs_prop_get(zhp, ZFS_PROP_RECEIVE_RESUME_TOKEN,
+ NULL, 0, NULL, NULL, 0, B_TRUE) == 0) {
+ zfs_close(zhp);
+ return (0);
+ }
+
+ libzfs_add_handle(cbp, zhp);
+ if (zfs_iter_filesystems(zhp, zfs_iter_cb, cbp) != 0) {
+ zfs_close(zhp);
+ return (-1);
+ }
+ return (0);
+}
+
+/*
+ * Sort comparator that compares two mountpoint paths. We sort these paths so
+ * that subdirectories immediately follow their parents. This means that we
+ * effectively treat the '/' character as the lowest value non-nul char.
+ * Since filesystems from non-global zones can have the same mountpoint
+ * as other filesystems, the comparator sorts global zone filesystems to
+ * the top of the list. This means that the global zone will traverse the
+ * filesystem list in the correct order and can stop when it sees the
+ * first zoned filesystem. In a non-global zone, only the delegated
+ * filesystems are seen.
+ *
+ * An example sorted list using this comparator would look like:
+ *
+ * /foo
+ * /foo/bar
+ * /foo/bar/baz
+ * /foo/baz
+ * /foo.bar
+ * /foo (NGZ1)
+ * /foo (NGZ2)
+ *
+ * The mounting code depends on this ordering to deterministically iterate
+ * over filesystems in order to spawn parallel mount tasks.
+ */
+static int
+mountpoint_cmp(const void *arga, const void *argb)
+{
+ zfs_handle_t *const *zap = arga;
+ zfs_handle_t *za = *zap;
+ zfs_handle_t *const *zbp = argb;
+ zfs_handle_t *zb = *zbp;
+ char mounta[MAXPATHLEN];
+ char mountb[MAXPATHLEN];
+ const char *a = mounta;
+ const char *b = mountb;
+ boolean_t gota, gotb;
+ uint64_t zoneda, zonedb;
+
+ zoneda = zfs_prop_get_int(za, ZFS_PROP_ZONED);
+ zonedb = zfs_prop_get_int(zb, ZFS_PROP_ZONED);
+ if (zoneda && !zonedb)
+ return (1);
+ if (!zoneda && zonedb)
+ return (-1);
+
+ gota = (zfs_get_type(za) == ZFS_TYPE_FILESYSTEM);
+ if (gota) {
+ verify(zfs_prop_get(za, ZFS_PROP_MOUNTPOINT, mounta,
+ sizeof (mounta), NULL, NULL, 0, B_FALSE) == 0);
+ }
+ gotb = (zfs_get_type(zb) == ZFS_TYPE_FILESYSTEM);
+ if (gotb) {
+ verify(zfs_prop_get(zb, ZFS_PROP_MOUNTPOINT, mountb,
+ sizeof (mountb), NULL, NULL, 0, B_FALSE) == 0);
+ }
+
+ if (gota && gotb) {
+ while (*a != '\0' && (*a == *b)) {
+ a++;
+ b++;
+ }
+ if (*a == *b)
+ return (0);
+ if (*a == '\0')
+ return (-1);
+ if (*b == '\0')
+ return (1);
+ if (*a == '/')
+ return (-1);
+ if (*b == '/')
+ return (1);
+ return (*a < *b ? -1 : *a > *b);
+ }
+
+ if (gota)
+ return (-1);
+ if (gotb)
+ return (1);
+
+ /*
+ * If neither filesystem has a mountpoint, revert to sorting by
+ * dataset name.
+ */
+ return (strcmp(zfs_get_name(za), zfs_get_name(zb)));
+}
+
+/*
+ * Return true if path2 is a child of path1 or path2 equals path1 or
+ * path1 is "/" (path2 is always a child of "/").
+ */
+static boolean_t
+libzfs_path_contains(const char *path1, const char *path2)
+{
+ return (strcmp(path1, path2) == 0 || strcmp(path1, "/") == 0 ||
+ (strstr(path2, path1) == path2 && path2[strlen(path1)] == '/'));
+}
+
+/*
+ * Given a mountpoint specified by idx in the handles array, find the first
+ * non-descendent of that mountpoint and return its index. Descendant paths
+ * start with the parent's path. This function relies on the ordering
+ * enforced by mountpoint_cmp().
+ */
+static int
+non_descendant_idx(zfs_handle_t **handles, size_t num_handles, int idx)
+{
+ char parent[ZFS_MAXPROPLEN];
+ char child[ZFS_MAXPROPLEN];
+ int i;
+
+ verify(zfs_prop_get(handles[idx], ZFS_PROP_MOUNTPOINT, parent,
+ sizeof (parent), NULL, NULL, 0, B_FALSE) == 0);
+
+ for (i = idx + 1; i < num_handles; i++) {
+ verify(zfs_prop_get(handles[i], ZFS_PROP_MOUNTPOINT, child,
+ sizeof (child), NULL, NULL, 0, B_FALSE) == 0);
+ if (!libzfs_path_contains(parent, child))
+ break;
+ }
+ return (i);
+}
+
+typedef struct mnt_param {
+ libzfs_handle_t *mnt_hdl;
+ tpool_t *mnt_tp;
+ zfs_handle_t **mnt_zhps; /* filesystems to mount */
+ size_t mnt_num_handles;
+ int mnt_idx; /* Index of selected entry to mount */
+ zfs_iter_f mnt_func;
+ void *mnt_data;
+} mnt_param_t;
+
+/*
+ * Allocate and populate the parameter struct for mount function, and
+ * schedule mounting of the entry selected by idx.
+ */
+static void
+zfs_dispatch_mount(libzfs_handle_t *hdl, zfs_handle_t **handles,
+ size_t num_handles, int idx, zfs_iter_f func, void *data, tpool_t *tp)
+{
+ mnt_param_t *mnt_param = zfs_alloc(hdl, sizeof (mnt_param_t));
+
+ mnt_param->mnt_hdl = hdl;
+ mnt_param->mnt_tp = tp;
+ mnt_param->mnt_zhps = handles;
+ mnt_param->mnt_num_handles = num_handles;
+ mnt_param->mnt_idx = idx;
+ mnt_param->mnt_func = func;
+ mnt_param->mnt_data = data;
+
+ (void) tpool_dispatch(tp, zfs_mount_task, (void*)mnt_param);
+}
+
+/*
+ * This is the structure used to keep state of mounting or sharing operations
+ * during a call to zpool_enable_datasets().
+ */
+typedef struct mount_state {
+ /*
+ * ms_mntstatus is set to -1 if any mount fails. While multiple threads
+ * could update this variable concurrently, no synchronization is
+ * needed as it's only ever set to -1.
+ */
+ int ms_mntstatus;
+ int ms_mntflags;
+ const char *ms_mntopts;
+} mount_state_t;
+
+static int
+zfs_mount_one(zfs_handle_t *zhp, void *arg)
+{
+ mount_state_t *ms = arg;
+ int ret = 0;
+
+ /*
+ * don't attempt to mount encrypted datasets with
+ * unloaded keys
+ */
+ if (zfs_prop_get_int(zhp, ZFS_PROP_KEYSTATUS) ==
+ ZFS_KEYSTATUS_UNAVAILABLE)
+ return (0);
+
+ if (zfs_mount(zhp, ms->ms_mntopts, ms->ms_mntflags) != 0)
+ ret = ms->ms_mntstatus = -1;
+ return (ret);
+}
+
+static int
+zfs_share_one(zfs_handle_t *zhp, void *arg)
+{
+ mount_state_t *ms = arg;
+ int ret = 0;
+
+ if (zfs_share(zhp) != 0)
+ ret = ms->ms_mntstatus = -1;
+ return (ret);
+}
+
+/*
+ * Thread pool function to mount one file system. On completion, it finds and
+ * schedules its children to be mounted. This depends on the sorting done in
+ * zfs_foreach_mountpoint(). Note that the degenerate case (chain of entries
+ * each descending from the previous) will have no parallelism since we always
+ * have to wait for the parent to finish mounting before we can schedule
+ * its children.
+ */
+static void
+zfs_mount_task(void *arg)
+{
+ mnt_param_t *mp = arg;
+ int idx = mp->mnt_idx;
+ zfs_handle_t **handles = mp->mnt_zhps;
+ size_t num_handles = mp->mnt_num_handles;
+ char mountpoint[ZFS_MAXPROPLEN];
+
+ verify(zfs_prop_get(handles[idx], ZFS_PROP_MOUNTPOINT, mountpoint,
+ sizeof (mountpoint), NULL, NULL, 0, B_FALSE) == 0);
+
+ if (mp->mnt_func(handles[idx], mp->mnt_data) != 0)
+ return;
+
+ /*
+ * We dispatch tasks to mount filesystems with mountpoints underneath
+ * this one. We do this by dispatching the next filesystem with a
+ * descendant mountpoint of the one we just mounted, then skip all of
+ * its descendants, dispatch the next descendant mountpoint, and so on.
+ * The non_descendant_idx() function skips over filesystems that are
+ * descendants of the filesystem we just dispatched.
+ */
+ for (int i = idx + 1; i < num_handles;
+ i = non_descendant_idx(handles, num_handles, i)) {
+ char child[ZFS_MAXPROPLEN];
+ verify(zfs_prop_get(handles[i], ZFS_PROP_MOUNTPOINT,
+ child, sizeof (child), NULL, NULL, 0, B_FALSE) == 0);
+
+ if (!libzfs_path_contains(mountpoint, child))
+ break; /* not a descendant, return */
+ zfs_dispatch_mount(mp->mnt_hdl, handles, num_handles, i,
+ mp->mnt_func, mp->mnt_data, mp->mnt_tp);
+ }
+ free(mp);
+}
+
+/*
+ * Issue the func callback for each ZFS handle contained in the handles
+ * array. This function is used to mount all datasets, and so this function
+ * guarantees that filesystems for parent mountpoints are called before their
+ * children. As such, before issuing any callbacks, we first sort the array
+ * of handles by mountpoint.
+ *
+ * Callbacks are issued in one of two ways:
+ *
+ * 1. Sequentially: If the parallel argument is B_FALSE or the ZFS_SERIAL_MOUNT
+ * environment variable is set, then we issue callbacks sequentially.
+ *
+ * 2. In parallel: If the parallel argument is B_TRUE and the ZFS_SERIAL_MOUNT
+ * environment variable is not set, then we use a tpool to dispatch threads
+ * to mount filesystems in parallel. This function dispatches tasks to mount
+ * the filesystems at the top-level mountpoints, and these tasks in turn
+ * are responsible for recursively mounting filesystems in their children
+ * mountpoints.
+ */
+void
+zfs_foreach_mountpoint(libzfs_handle_t *hdl, zfs_handle_t **handles,
+ size_t num_handles, zfs_iter_f func, void *data, boolean_t parallel)
+{
+ zoneid_t zoneid = getzoneid();
+
+ /*
+ * The ZFS_SERIAL_MOUNT environment variable is an undocumented
+ * variable that can be used as a convenience to do a/b comparison
+ * of serial vs. parallel mounting.
+ */
+ boolean_t serial_mount = !parallel ||
+ (getenv("ZFS_SERIAL_MOUNT") != NULL);
+
+ /*
+ * Sort the datasets by mountpoint. See mountpoint_cmp for details
+ * of how these are sorted.
+ */
+ qsort(handles, num_handles, sizeof (zfs_handle_t *), mountpoint_cmp);
+
+ if (serial_mount) {
+ for (int i = 0; i < num_handles; i++) {
+ func(handles[i], data);
+ }
+ return;
+ }
+
+ /*
+ * Issue the callback function for each dataset using a parallel
+ * algorithm that uses a thread pool to manage threads.
+ */
+ tpool_t *tp = tpool_create(1, mount_tp_nthr, 0, NULL);
+
+ /*
+ * There may be multiple "top level" mountpoints outside of the pool's
+ * root mountpoint, e.g.: /foo /bar. Dispatch a mount task for each of
+ * these.
+ */
+ for (int i = 0; i < num_handles;
+ i = non_descendant_idx(handles, num_handles, i)) {
+ /*
+ * Since the mountpoints have been sorted so that the zoned
+ * filesystems are at the end, a zoned filesystem seen from
+ * the global zone means that we're done.
+ */
+ if (zoneid == GLOBAL_ZONEID &&
+ zfs_prop_get_int(handles[i], ZFS_PROP_ZONED))
+ break;
+ zfs_dispatch_mount(hdl, handles, num_handles, i, func, data,
+ tp);
+ }
+
+ tpool_wait(tp); /* wait for all scheduled mounts to complete */
+ tpool_destroy(tp);
+}
+
+/*
+ * Mount and share all datasets within the given pool. This assumes that no
+ * datasets within the pool are currently mounted.
+ */
+#pragma weak zpool_mount_datasets = zpool_enable_datasets
+int
+zpool_enable_datasets(zpool_handle_t *zhp, const char *mntopts, int flags)
+{
+ get_all_cb_t cb = { 0 };
+ mount_state_t ms = { 0 };
+ zfs_handle_t *zfsp;
+ int ret = 0;
+
+ if ((zfsp = zfs_open(zhp->zpool_hdl, zhp->zpool_name,
+ ZFS_TYPE_DATASET)) == NULL)
+ goto out;
+
+ /*
+ * Gather all non-snapshot datasets within the pool. Start by adding
+ * the root filesystem for this pool to the list, and then iterate
+ * over all child filesystems.
+ */
+ libzfs_add_handle(&cb, zfsp);
+ if (zfs_iter_filesystems(zfsp, zfs_iter_cb, &cb) != 0)
+ goto out;
+
+ /*
+ * Mount all filesystems
+ */
+ ms.ms_mntopts = mntopts;
+ ms.ms_mntflags = flags;
+ zfs_foreach_mountpoint(zhp->zpool_hdl, cb.cb_handles, cb.cb_used,
+ zfs_mount_one, &ms, B_TRUE);
+ if (ms.ms_mntstatus != 0)
+ ret = ms.ms_mntstatus;
+
+ /*
+ * Share all filesystems that need to be shared. This needs to be
+ * a separate pass because libshare is not mt-safe, and so we need
+ * to share serially.
+ */
+ ms.ms_mntstatus = 0;
+ zfs_foreach_mountpoint(zhp->zpool_hdl, cb.cb_handles, cb.cb_used,
+ zfs_share_one, &ms, B_FALSE);
+ if (ms.ms_mntstatus != 0)
+ ret = ms.ms_mntstatus;
+ else
+ zfs_commit_all_shares();
+
+out:
+ for (int i = 0; i < cb.cb_used; i++)
+ zfs_close(cb.cb_handles[i]);
+ free(cb.cb_handles);
+
+ return (ret);
+}
+
+static int
+mountpoint_compare(const void *a, const void *b)
+{
+ const char *mounta = *((char **)a);
+ const char *mountb = *((char **)b);
+
+ return (strcmp(mountb, mounta));
+}
+
+/* alias for 2002/240 */
+#pragma weak zpool_unmount_datasets = zpool_disable_datasets
+/*
+ * Unshare and unmount all datasets within the given pool. We don't want to
+ * rely on traversing the DSL to discover the filesystems within the pool,
+ * because this may be expensive (if not all of them are mounted), and can fail
+ * arbitrarily (on I/O error, for example). Instead, we walk /proc/self/mounts
+ * and gather all the filesystems that are currently mounted.
+ */
+int
+zpool_disable_datasets(zpool_handle_t *zhp, boolean_t force)
+{
+ int used, alloc;
+ struct mnttab entry;
+ size_t namelen;
+ char **mountpoints = NULL;
+ zfs_handle_t **datasets = NULL;
+ libzfs_handle_t *hdl = zhp->zpool_hdl;
+ int i;
+ int ret = -1;
+ int flags = (force ? MS_FORCE : 0);
+
+ namelen = strlen(zhp->zpool_name);
+
+ /* Reopen MNTTAB to prevent reading stale data from open file */
+ if (freopen(MNTTAB, "r", hdl->libzfs_mnttab) == NULL)
+ return (ENOENT);
+
+ used = alloc = 0;
+ while (getmntent(hdl->libzfs_mnttab, &entry) == 0) {
+ /*
+ * Ignore non-ZFS entries.
+ */
+ if (entry.mnt_fstype == NULL ||
+ strcmp(entry.mnt_fstype, MNTTYPE_ZFS) != 0)
+ continue;
+
+ /*
+ * Ignore filesystems not within this pool.
+ */
+ if (entry.mnt_mountp == NULL ||
+ strncmp(entry.mnt_special, zhp->zpool_name, namelen) != 0 ||
+ (entry.mnt_special[namelen] != '/' &&
+ entry.mnt_special[namelen] != '\0'))
+ continue;
+
+ /*
+ * At this point we've found a filesystem within our pool. Add
+ * it to our growing list.
+ */
+ if (used == alloc) {
+ if (alloc == 0) {
+ if ((mountpoints = zfs_alloc(hdl,
+ 8 * sizeof (void *))) == NULL)
+ goto out;
+
+ if ((datasets = zfs_alloc(hdl,
+ 8 * sizeof (void *))) == NULL)
+ goto out;
+
+ alloc = 8;
+ } else {
+ void *ptr;
+
+ if ((ptr = zfs_realloc(hdl, mountpoints,
+ alloc * sizeof (void *),
+ alloc * 2 * sizeof (void *))) == NULL)
+ goto out;
+ mountpoints = ptr;
+
+ if ((ptr = zfs_realloc(hdl, datasets,
+ alloc * sizeof (void *),
+ alloc * 2 * sizeof (void *))) == NULL)
+ goto out;
+ datasets = ptr;
+
+ alloc *= 2;
+ }
+ }
+
+ if ((mountpoints[used] = zfs_strdup(hdl,
+ entry.mnt_mountp)) == NULL)
+ goto out;
+
+ /*
+ * This is allowed to fail, in case there is some I/O error. It
+ * is only used to determine if we need to remove the underlying
+ * mountpoint, so failure is not fatal.
+ */
+ datasets[used] = make_dataset_handle(hdl, entry.mnt_special);
+
+ used++;
+ }
+
+ /*
+ * At this point, we have the entire list of filesystems, so sort it by
+ * mountpoint.
+ */
+ qsort(mountpoints, used, sizeof (char *), mountpoint_compare);
+
+ /*
+ * Walk through and first unshare everything.
+ */
+ for (i = 0; i < used; i++) {
+ zfs_share_proto_t *curr_proto;
+ for (curr_proto = share_all_proto; *curr_proto != PROTO_END;
+ curr_proto++) {
+ if (is_shared(mountpoints[i], *curr_proto) &&
+ unshare_one(hdl, mountpoints[i],
+ mountpoints[i], *curr_proto) != 0)
+ goto out;
+ }
+ }
+ zfs_commit_all_shares();
+
+ /*
+ * Now unmount everything, removing the underlying directories as
+ * appropriate.
+ */
+ for (i = 0; i < used; i++) {
+ if (unmount_one(hdl, mountpoints[i], flags) != 0)
+ goto out;
+ }
+
+ for (i = 0; i < used; i++) {
+ if (datasets[i])
+ remove_mountpoint(datasets[i]);
+ }
+
+ ret = 0;
+out:
+ for (i = 0; i < used; i++) {
+ if (datasets[i])
+ zfs_close(datasets[i]);
+ free(mountpoints[i]);
+ }
+ free(datasets);
+ free(mountpoints);
+
+ return (ret);
+}
diff --git a/lib/libzfs/libzfs_pool.c b/lib/libzfs/libzfs_pool.c
new file mode 100644
index 000000000000..2501965e42ad
--- /dev/null
+++ b/lib/libzfs/libzfs_pool.c
@@ -0,0 +1,4531 @@
+/*
+ * 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 2015 Nexenta Systems, Inc. All rights reserved.
+ * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 2020 by Delphix. All rights reserved.
+ * Copyright 2016 Igor Kozhukhov <ikozhukhov@gmail.com>
+ * Copyright (c) 2018 Datto Inc.
+ * Copyright (c) 2017 Open-E, Inc. All Rights Reserved.
+ * Copyright (c) 2017, Intel Corporation.
+ * Copyright (c) 2018, loli10K <ezomori.nozomu@gmail.com>
+ */
+
+#include <errno.h>
+#include <libintl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <unistd.h>
+#include <libgen.h>
+#include <zone.h>
+#include <sys/stat.h>
+#include <sys/efi_partition.h>
+#include <sys/systeminfo.h>
+#include <sys/zfs_ioctl.h>
+#include <sys/vdev_disk.h>
+#include <dlfcn.h>
+#include <libzutil.h>
+
+#include "zfs_namecheck.h"
+#include "zfs_prop.h"
+#include "libzfs_impl.h"
+#include "zfs_comutil.h"
+#include "zfeature_common.h"
+
+static boolean_t zpool_vdev_is_interior(const char *name);
+
+typedef struct prop_flags {
+ int create:1; /* Validate property on creation */
+ int import:1; /* Validate property on import */
+} prop_flags_t;
+
+/*
+ * ====================================================================
+ * zpool property functions
+ * ====================================================================
+ */
+
+static int
+zpool_get_all_props(zpool_handle_t *zhp)
+{
+ zfs_cmd_t zc = {"\0"};
+ libzfs_handle_t *hdl = zhp->zpool_hdl;
+
+ (void) strlcpy(zc.zc_name, zhp->zpool_name, sizeof (zc.zc_name));
+
+ if (zcmd_alloc_dst_nvlist(hdl, &zc, 0) != 0)
+ return (-1);
+
+ while (zfs_ioctl(hdl, ZFS_IOC_POOL_GET_PROPS, &zc) != 0) {
+ if (errno == ENOMEM) {
+ if (zcmd_expand_dst_nvlist(hdl, &zc) != 0) {
+ zcmd_free_nvlists(&zc);
+ return (-1);
+ }
+ } else {
+ zcmd_free_nvlists(&zc);
+ return (-1);
+ }
+ }
+
+ if (zcmd_read_dst_nvlist(hdl, &zc, &zhp->zpool_props) != 0) {
+ zcmd_free_nvlists(&zc);
+ return (-1);
+ }
+
+ zcmd_free_nvlists(&zc);
+
+ return (0);
+}
+
+int
+zpool_props_refresh(zpool_handle_t *zhp)
+{
+ nvlist_t *old_props;
+
+ old_props = zhp->zpool_props;
+
+ if (zpool_get_all_props(zhp) != 0)
+ return (-1);
+
+ nvlist_free(old_props);
+ return (0);
+}
+
+static const char *
+zpool_get_prop_string(zpool_handle_t *zhp, zpool_prop_t prop,
+ zprop_source_t *src)
+{
+ nvlist_t *nv, *nvl;
+ uint64_t ival;
+ char *value;
+ zprop_source_t source;
+
+ nvl = zhp->zpool_props;
+ if (nvlist_lookup_nvlist(nvl, zpool_prop_to_name(prop), &nv) == 0) {
+ verify(nvlist_lookup_uint64(nv, ZPROP_SOURCE, &ival) == 0);
+ source = ival;
+ verify(nvlist_lookup_string(nv, ZPROP_VALUE, &value) == 0);
+ } else {
+ source = ZPROP_SRC_DEFAULT;
+ if ((value = (char *)zpool_prop_default_string(prop)) == NULL)
+ value = "-";
+ }
+
+ if (src)
+ *src = source;
+
+ return (value);
+}
+
+uint64_t
+zpool_get_prop_int(zpool_handle_t *zhp, zpool_prop_t prop, zprop_source_t *src)
+{
+ nvlist_t *nv, *nvl;
+ uint64_t value;
+ zprop_source_t source;
+
+ if (zhp->zpool_props == NULL && zpool_get_all_props(zhp)) {
+ /*
+ * zpool_get_all_props() has most likely failed because
+ * the pool is faulted, but if all we need is the top level
+ * vdev's guid then get it from the zhp config nvlist.
+ */
+ if ((prop == ZPOOL_PROP_GUID) &&
+ (nvlist_lookup_nvlist(zhp->zpool_config,
+ ZPOOL_CONFIG_VDEV_TREE, &nv) == 0) &&
+ (nvlist_lookup_uint64(nv, ZPOOL_CONFIG_GUID, &value)
+ == 0)) {
+ return (value);
+ }
+ return (zpool_prop_default_numeric(prop));
+ }
+
+ nvl = zhp->zpool_props;
+ if (nvlist_lookup_nvlist(nvl, zpool_prop_to_name(prop), &nv) == 0) {
+ verify(nvlist_lookup_uint64(nv, ZPROP_SOURCE, &value) == 0);
+ source = value;
+ verify(nvlist_lookup_uint64(nv, ZPROP_VALUE, &value) == 0);
+ } else {
+ source = ZPROP_SRC_DEFAULT;
+ value = zpool_prop_default_numeric(prop);
+ }
+
+ if (src)
+ *src = source;
+
+ return (value);
+}
+
+/*
+ * Map VDEV STATE to printed strings.
+ */
+const char *
+zpool_state_to_name(vdev_state_t state, vdev_aux_t aux)
+{
+ switch (state) {
+ case VDEV_STATE_CLOSED:
+ case VDEV_STATE_OFFLINE:
+ return (gettext("OFFLINE"));
+ case VDEV_STATE_REMOVED:
+ return (gettext("REMOVED"));
+ case VDEV_STATE_CANT_OPEN:
+ if (aux == VDEV_AUX_CORRUPT_DATA || aux == VDEV_AUX_BAD_LOG)
+ return (gettext("FAULTED"));
+ else if (aux == VDEV_AUX_SPLIT_POOL)
+ return (gettext("SPLIT"));
+ else
+ return (gettext("UNAVAIL"));
+ case VDEV_STATE_FAULTED:
+ return (gettext("FAULTED"));
+ case VDEV_STATE_DEGRADED:
+ return (gettext("DEGRADED"));
+ case VDEV_STATE_HEALTHY:
+ return (gettext("ONLINE"));
+
+ default:
+ break;
+ }
+
+ return (gettext("UNKNOWN"));
+}
+
+/*
+ * Map POOL STATE to printed strings.
+ */
+const char *
+zpool_pool_state_to_name(pool_state_t state)
+{
+ switch (state) {
+ default:
+ break;
+ case POOL_STATE_ACTIVE:
+ return (gettext("ACTIVE"));
+ case POOL_STATE_EXPORTED:
+ return (gettext("EXPORTED"));
+ case POOL_STATE_DESTROYED:
+ return (gettext("DESTROYED"));
+ case POOL_STATE_SPARE:
+ return (gettext("SPARE"));
+ case POOL_STATE_L2CACHE:
+ return (gettext("L2CACHE"));
+ case POOL_STATE_UNINITIALIZED:
+ return (gettext("UNINITIALIZED"));
+ case POOL_STATE_UNAVAIL:
+ return (gettext("UNAVAIL"));
+ case POOL_STATE_POTENTIALLY_ACTIVE:
+ return (gettext("POTENTIALLY_ACTIVE"));
+ }
+
+ return (gettext("UNKNOWN"));
+}
+
+/*
+ * Given a pool handle, return the pool health string ("ONLINE", "DEGRADED",
+ * "SUSPENDED", etc).
+ */
+const char *
+zpool_get_state_str(zpool_handle_t *zhp)
+{
+ zpool_errata_t errata;
+ zpool_status_t status;
+ nvlist_t *nvroot;
+ vdev_stat_t *vs;
+ uint_t vsc;
+ const char *str;
+
+ status = zpool_get_status(zhp, NULL, &errata);
+
+ if (zpool_get_state(zhp) == POOL_STATE_UNAVAIL) {
+ str = gettext("FAULTED");
+ } else if (status == ZPOOL_STATUS_IO_FAILURE_WAIT ||
+ status == ZPOOL_STATUS_IO_FAILURE_MMP) {
+ str = gettext("SUSPENDED");
+ } else {
+ verify(nvlist_lookup_nvlist(zpool_get_config(zhp, NULL),
+ ZPOOL_CONFIG_VDEV_TREE, &nvroot) == 0);
+ verify(nvlist_lookup_uint64_array(nvroot,
+ ZPOOL_CONFIG_VDEV_STATS, (uint64_t **)&vs, &vsc)
+ == 0);
+ str = zpool_state_to_name(vs->vs_state, vs->vs_aux);
+ }
+ return (str);
+}
+
+/*
+ * Get a zpool property value for 'prop' and return the value in
+ * a pre-allocated buffer.
+ */
+int
+zpool_get_prop(zpool_handle_t *zhp, zpool_prop_t prop, char *buf,
+ size_t len, zprop_source_t *srctype, boolean_t literal)
+{
+ uint64_t intval;
+ const char *strval;
+ zprop_source_t src = ZPROP_SRC_NONE;
+
+ if (zpool_get_state(zhp) == POOL_STATE_UNAVAIL) {
+ switch (prop) {
+ case ZPOOL_PROP_NAME:
+ (void) strlcpy(buf, zpool_get_name(zhp), len);
+ break;
+
+ case ZPOOL_PROP_HEALTH:
+ (void) strlcpy(buf, zpool_get_state_str(zhp), len);
+ break;
+
+ case ZPOOL_PROP_GUID:
+ intval = zpool_get_prop_int(zhp, prop, &src);
+ (void) snprintf(buf, len, "%llu", (u_longlong_t)intval);
+ break;
+
+ case ZPOOL_PROP_ALTROOT:
+ case ZPOOL_PROP_CACHEFILE:
+ case ZPOOL_PROP_COMMENT:
+ if (zhp->zpool_props != NULL ||
+ zpool_get_all_props(zhp) == 0) {
+ (void) strlcpy(buf,
+ zpool_get_prop_string(zhp, prop, &src),
+ len);
+ break;
+ }
+ /* FALLTHROUGH */
+ default:
+ (void) strlcpy(buf, "-", len);
+ break;
+ }
+
+ if (srctype != NULL)
+ *srctype = src;
+ return (0);
+ }
+
+ if (zhp->zpool_props == NULL && zpool_get_all_props(zhp) &&
+ prop != ZPOOL_PROP_NAME)
+ return (-1);
+
+ switch (zpool_prop_get_type(prop)) {
+ case PROP_TYPE_STRING:
+ (void) strlcpy(buf, zpool_get_prop_string(zhp, prop, &src),
+ len);
+ break;
+
+ case PROP_TYPE_NUMBER:
+ intval = zpool_get_prop_int(zhp, prop, &src);
+
+ switch (prop) {
+ case ZPOOL_PROP_SIZE:
+ case ZPOOL_PROP_ALLOCATED:
+ case ZPOOL_PROP_FREE:
+ case ZPOOL_PROP_FREEING:
+ case ZPOOL_PROP_LEAKED:
+ case ZPOOL_PROP_ASHIFT:
+ if (literal)
+ (void) snprintf(buf, len, "%llu",
+ (u_longlong_t)intval);
+ else
+ (void) zfs_nicenum(intval, buf, len);
+ break;
+
+ case ZPOOL_PROP_EXPANDSZ:
+ case ZPOOL_PROP_CHECKPOINT:
+ if (intval == 0) {
+ (void) strlcpy(buf, "-", len);
+ } else if (literal) {
+ (void) snprintf(buf, len, "%llu",
+ (u_longlong_t)intval);
+ } else {
+ (void) zfs_nicebytes(intval, buf, len);
+ }
+ break;
+
+ case ZPOOL_PROP_CAPACITY:
+ if (literal) {
+ (void) snprintf(buf, len, "%llu",
+ (u_longlong_t)intval);
+ } else {
+ (void) snprintf(buf, len, "%llu%%",
+ (u_longlong_t)intval);
+ }
+ break;
+
+ case ZPOOL_PROP_FRAGMENTATION:
+ if (intval == UINT64_MAX) {
+ (void) strlcpy(buf, "-", len);
+ } else if (literal) {
+ (void) snprintf(buf, len, "%llu",
+ (u_longlong_t)intval);
+ } else {
+ (void) snprintf(buf, len, "%llu%%",
+ (u_longlong_t)intval);
+ }
+ break;
+
+ case ZPOOL_PROP_DEDUPRATIO:
+ if (literal)
+ (void) snprintf(buf, len, "%llu.%02llu",
+ (u_longlong_t)(intval / 100),
+ (u_longlong_t)(intval % 100));
+ else
+ (void) snprintf(buf, len, "%llu.%02llux",
+ (u_longlong_t)(intval / 100),
+ (u_longlong_t)(intval % 100));
+ break;
+
+ case ZPOOL_PROP_HEALTH:
+ (void) strlcpy(buf, zpool_get_state_str(zhp), len);
+ break;
+ case ZPOOL_PROP_VERSION:
+ if (intval >= SPA_VERSION_FEATURES) {
+ (void) snprintf(buf, len, "-");
+ break;
+ }
+ /* FALLTHROUGH */
+ default:
+ (void) snprintf(buf, len, "%llu", (u_longlong_t)intval);
+ }
+ break;
+
+ case PROP_TYPE_INDEX:
+ intval = zpool_get_prop_int(zhp, prop, &src);
+ if (zpool_prop_index_to_string(prop, intval, &strval)
+ != 0)
+ return (-1);
+ (void) strlcpy(buf, strval, len);
+ break;
+
+ default:
+ abort();
+ }
+
+ if (srctype)
+ *srctype = src;
+
+ return (0);
+}
+
+/*
+ * Check if the bootfs name has the same pool name as it is set to.
+ * Assuming bootfs is a valid dataset name.
+ */
+static boolean_t
+bootfs_name_valid(const char *pool, const char *bootfs)
+{
+ int len = strlen(pool);
+ if (bootfs[0] == '\0')
+ return (B_TRUE);
+
+ if (!zfs_name_valid(bootfs, ZFS_TYPE_FILESYSTEM|ZFS_TYPE_SNAPSHOT))
+ return (B_FALSE);
+
+ if (strncmp(pool, bootfs, len) == 0 &&
+ (bootfs[len] == '/' || bootfs[len] == '\0'))
+ return (B_TRUE);
+
+ return (B_FALSE);
+}
+
+/*
+ * Given an nvlist of zpool properties to be set, validate that they are
+ * correct, and parse any numeric properties (index, boolean, etc) if they are
+ * specified as strings.
+ */
+static nvlist_t *
+zpool_valid_proplist(libzfs_handle_t *hdl, const char *poolname,
+ nvlist_t *props, uint64_t version, prop_flags_t flags, char *errbuf)
+{
+ nvpair_t *elem;
+ nvlist_t *retprops;
+ zpool_prop_t prop;
+ char *strval;
+ uint64_t intval;
+ char *slash, *check;
+ struct stat64 statbuf;
+ zpool_handle_t *zhp;
+
+ if (nvlist_alloc(&retprops, NV_UNIQUE_NAME, 0) != 0) {
+ (void) no_memory(hdl);
+ return (NULL);
+ }
+
+ elem = NULL;
+ while ((elem = nvlist_next_nvpair(props, elem)) != NULL) {
+ const char *propname = nvpair_name(elem);
+
+ prop = zpool_name_to_prop(propname);
+ if (prop == ZPOOL_PROP_INVAL && zpool_prop_feature(propname)) {
+ int err;
+ char *fname = strchr(propname, '@') + 1;
+
+ err = zfeature_lookup_name(fname, NULL);
+ if (err != 0) {
+ ASSERT3U(err, ==, ENOENT);
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "invalid feature '%s'"), fname);
+ (void) zfs_error(hdl, EZFS_BADPROP, errbuf);
+ goto error;
+ }
+
+ if (nvpair_type(elem) != DATA_TYPE_STRING) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "'%s' must be a string"), propname);
+ (void) zfs_error(hdl, EZFS_BADPROP, errbuf);
+ goto error;
+ }
+
+ (void) nvpair_value_string(elem, &strval);
+ if (strcmp(strval, ZFS_FEATURE_ENABLED) != 0 &&
+ strcmp(strval, ZFS_FEATURE_DISABLED) != 0) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "property '%s' can only be set to "
+ "'enabled' or 'disabled'"), propname);
+ (void) zfs_error(hdl, EZFS_BADPROP, errbuf);
+ goto error;
+ }
+
+ if (!flags.create &&
+ strcmp(strval, ZFS_FEATURE_DISABLED) == 0) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "property '%s' can only be set to "
+ "'disabled' at creation time"), propname);
+ (void) zfs_error(hdl, EZFS_BADPROP, errbuf);
+ goto error;
+ }
+
+ if (nvlist_add_uint64(retprops, propname, 0) != 0) {
+ (void) no_memory(hdl);
+ goto error;
+ }
+ continue;
+ }
+
+ /*
+ * Make sure this property is valid and applies to this type.
+ */
+ if (prop == ZPOOL_PROP_INVAL) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "invalid property '%s'"), propname);
+ (void) zfs_error(hdl, EZFS_BADPROP, errbuf);
+ goto error;
+ }
+
+ if (zpool_prop_readonly(prop)) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "'%s' "
+ "is readonly"), propname);
+ (void) zfs_error(hdl, EZFS_PROPREADONLY, errbuf);
+ goto error;
+ }
+
+ if (!flags.create && zpool_prop_setonce(prop)) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "property '%s' can only be set at "
+ "creation time"), propname);
+ (void) zfs_error(hdl, EZFS_BADPROP, errbuf);
+ goto error;
+ }
+
+ if (zprop_parse_value(hdl, elem, prop, ZFS_TYPE_POOL, retprops,
+ &strval, &intval, errbuf) != 0)
+ goto error;
+
+ /*
+ * Perform additional checking for specific properties.
+ */
+ switch (prop) {
+ case ZPOOL_PROP_VERSION:
+ if (intval < version ||
+ !SPA_VERSION_IS_SUPPORTED(intval)) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "property '%s' number %d is invalid."),
+ propname, intval);
+ (void) zfs_error(hdl, EZFS_BADVERSION, errbuf);
+ goto error;
+ }
+ break;
+
+ case ZPOOL_PROP_ASHIFT:
+ if (intval != 0 &&
+ (intval < ASHIFT_MIN || intval > ASHIFT_MAX)) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "property '%s' number %d is invalid, only "
+ "values between %" PRId32 " and "
+ "%" PRId32 " are allowed."),
+ propname, intval, ASHIFT_MIN, ASHIFT_MAX);
+ (void) zfs_error(hdl, EZFS_BADPROP, errbuf);
+ goto error;
+ }
+ break;
+
+ case ZPOOL_PROP_BOOTFS:
+ if (flags.create || flags.import) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "property '%s' cannot be set at creation "
+ "or import time"), propname);
+ (void) zfs_error(hdl, EZFS_BADPROP, errbuf);
+ goto error;
+ }
+
+ if (version < SPA_VERSION_BOOTFS) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "pool must be upgraded to support "
+ "'%s' property"), propname);
+ (void) zfs_error(hdl, EZFS_BADVERSION, errbuf);
+ goto error;
+ }
+
+ /*
+ * bootfs property value has to be a dataset name and
+ * the dataset has to be in the same pool as it sets to.
+ */
+ if (!bootfs_name_valid(poolname, strval)) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "'%s' "
+ "is an invalid name"), strval);
+ (void) zfs_error(hdl, EZFS_INVALIDNAME, errbuf);
+ goto error;
+ }
+
+ if ((zhp = zpool_open_canfail(hdl, poolname)) == NULL) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "could not open pool '%s'"), poolname);
+ (void) zfs_error(hdl, EZFS_OPENFAILED, errbuf);
+ goto error;
+ }
+ zpool_close(zhp);
+ break;
+
+ case ZPOOL_PROP_ALTROOT:
+ if (!flags.create && !flags.import) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "property '%s' can only be set during pool "
+ "creation or import"), propname);
+ (void) zfs_error(hdl, EZFS_BADPROP, errbuf);
+ goto error;
+ }
+
+ if (strval[0] != '/') {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "bad alternate root '%s'"), strval);
+ (void) zfs_error(hdl, EZFS_BADPATH, errbuf);
+ goto error;
+ }
+ break;
+
+ case ZPOOL_PROP_CACHEFILE:
+ if (strval[0] == '\0')
+ break;
+
+ if (strcmp(strval, "none") == 0)
+ break;
+
+ if (strval[0] != '/') {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "property '%s' must be empty, an "
+ "absolute path, or 'none'"), propname);
+ (void) zfs_error(hdl, EZFS_BADPATH, errbuf);
+ goto error;
+ }
+
+ slash = strrchr(strval, '/');
+
+ if (slash[1] == '\0' || strcmp(slash, "/.") == 0 ||
+ strcmp(slash, "/..") == 0) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "'%s' is not a valid file"), strval);
+ (void) zfs_error(hdl, EZFS_BADPATH, errbuf);
+ goto error;
+ }
+
+ *slash = '\0';
+
+ if (strval[0] != '\0' &&
+ (stat64(strval, &statbuf) != 0 ||
+ !S_ISDIR(statbuf.st_mode))) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "'%s' is not a valid directory"),
+ strval);
+ (void) zfs_error(hdl, EZFS_BADPATH, errbuf);
+ goto error;
+ }
+
+ *slash = '/';
+ break;
+
+ case ZPOOL_PROP_COMMENT:
+ for (check = strval; *check != '\0'; check++) {
+ if (!isprint(*check)) {
+ zfs_error_aux(hdl,
+ dgettext(TEXT_DOMAIN,
+ "comment may only have printable "
+ "characters"));
+ (void) zfs_error(hdl, EZFS_BADPROP,
+ errbuf);
+ goto error;
+ }
+ }
+ if (strlen(strval) > ZPROP_MAX_COMMENT) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "comment must not exceed %d characters"),
+ ZPROP_MAX_COMMENT);
+ (void) zfs_error(hdl, EZFS_BADPROP, errbuf);
+ goto error;
+ }
+ break;
+ case ZPOOL_PROP_READONLY:
+ if (!flags.import) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "property '%s' can only be set at "
+ "import time"), propname);
+ (void) zfs_error(hdl, EZFS_BADPROP, errbuf);
+ goto error;
+ }
+ break;
+ case ZPOOL_PROP_MULTIHOST:
+ if (get_system_hostid() == 0) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "requires a non-zero system hostid"));
+ (void) zfs_error(hdl, EZFS_BADPROP, errbuf);
+ goto error;
+ }
+ break;
+ case ZPOOL_PROP_DEDUPDITTO:
+ printf("Note: property '%s' no longer has "
+ "any effect\n", propname);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ return (retprops);
+error:
+ nvlist_free(retprops);
+ return (NULL);
+}
+
+/*
+ * Set zpool property : propname=propval.
+ */
+int
+zpool_set_prop(zpool_handle_t *zhp, const char *propname, const char *propval)
+{
+ zfs_cmd_t zc = {"\0"};
+ int ret = -1;
+ char errbuf[1024];
+ nvlist_t *nvl = NULL;
+ nvlist_t *realprops;
+ uint64_t version;
+ prop_flags_t flags = { 0 };
+
+ (void) snprintf(errbuf, sizeof (errbuf),
+ dgettext(TEXT_DOMAIN, "cannot set property for '%s'"),
+ zhp->zpool_name);
+
+ if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0) != 0)
+ return (no_memory(zhp->zpool_hdl));
+
+ if (nvlist_add_string(nvl, propname, propval) != 0) {
+ nvlist_free(nvl);
+ return (no_memory(zhp->zpool_hdl));
+ }
+
+ version = zpool_get_prop_int(zhp, ZPOOL_PROP_VERSION, NULL);
+ if ((realprops = zpool_valid_proplist(zhp->zpool_hdl,
+ zhp->zpool_name, nvl, version, flags, errbuf)) == NULL) {
+ nvlist_free(nvl);
+ return (-1);
+ }
+
+ nvlist_free(nvl);
+ nvl = realprops;
+
+ /*
+ * Execute the corresponding ioctl() to set this property.
+ */
+ (void) strlcpy(zc.zc_name, zhp->zpool_name, sizeof (zc.zc_name));
+
+ if (zcmd_write_src_nvlist(zhp->zpool_hdl, &zc, nvl) != 0) {
+ nvlist_free(nvl);
+ return (-1);
+ }
+
+ ret = zfs_ioctl(zhp->zpool_hdl, ZFS_IOC_POOL_SET_PROPS, &zc);
+
+ zcmd_free_nvlists(&zc);
+ nvlist_free(nvl);
+
+ if (ret)
+ (void) zpool_standard_error(zhp->zpool_hdl, errno, errbuf);
+ else
+ (void) zpool_props_refresh(zhp);
+
+ return (ret);
+}
+
+int
+zpool_expand_proplist(zpool_handle_t *zhp, zprop_list_t **plp)
+{
+ libzfs_handle_t *hdl = zhp->zpool_hdl;
+ zprop_list_t *entry;
+ char buf[ZFS_MAXPROPLEN];
+ nvlist_t *features = NULL;
+ nvpair_t *nvp;
+ zprop_list_t **last;
+ boolean_t firstexpand = (NULL == *plp);
+ int i;
+
+ if (zprop_expand_list(hdl, plp, ZFS_TYPE_POOL) != 0)
+ return (-1);
+
+ last = plp;
+ while (*last != NULL)
+ last = &(*last)->pl_next;
+
+ if ((*plp)->pl_all)
+ features = zpool_get_features(zhp);
+
+ if ((*plp)->pl_all && firstexpand) {
+ for (i = 0; i < SPA_FEATURES; i++) {
+ zprop_list_t *entry = zfs_alloc(hdl,
+ sizeof (zprop_list_t));
+ entry->pl_prop = ZPROP_INVAL;
+ entry->pl_user_prop = zfs_asprintf(hdl, "feature@%s",
+ spa_feature_table[i].fi_uname);
+ entry->pl_width = strlen(entry->pl_user_prop);
+ entry->pl_all = B_TRUE;
+
+ *last = entry;
+ last = &entry->pl_next;
+ }
+ }
+
+ /* add any unsupported features */
+ for (nvp = nvlist_next_nvpair(features, NULL);
+ nvp != NULL; nvp = nvlist_next_nvpair(features, nvp)) {
+ char *propname;
+ boolean_t found;
+ zprop_list_t *entry;
+
+ if (zfeature_is_supported(nvpair_name(nvp)))
+ continue;
+
+ propname = zfs_asprintf(hdl, "unsupported@%s",
+ nvpair_name(nvp));
+
+ /*
+ * Before adding the property to the list make sure that no
+ * other pool already added the same property.
+ */
+ found = B_FALSE;
+ entry = *plp;
+ while (entry != NULL) {
+ if (entry->pl_user_prop != NULL &&
+ strcmp(propname, entry->pl_user_prop) == 0) {
+ found = B_TRUE;
+ break;
+ }
+ entry = entry->pl_next;
+ }
+ if (found) {
+ free(propname);
+ continue;
+ }
+
+ entry = zfs_alloc(hdl, sizeof (zprop_list_t));
+ entry->pl_prop = ZPROP_INVAL;
+ entry->pl_user_prop = propname;
+ entry->pl_width = strlen(entry->pl_user_prop);
+ entry->pl_all = B_TRUE;
+
+ *last = entry;
+ last = &entry->pl_next;
+ }
+
+ for (entry = *plp; entry != NULL; entry = entry->pl_next) {
+
+ if (entry->pl_fixed)
+ continue;
+
+ if (entry->pl_prop != ZPROP_INVAL &&
+ zpool_get_prop(zhp, entry->pl_prop, buf, sizeof (buf),
+ NULL, B_FALSE) == 0) {
+ if (strlen(buf) > entry->pl_width)
+ entry->pl_width = strlen(buf);
+ }
+ }
+
+ return (0);
+}
+
+/*
+ * Get the state for the given feature on the given ZFS pool.
+ */
+int
+zpool_prop_get_feature(zpool_handle_t *zhp, const char *propname, char *buf,
+ size_t len)
+{
+ uint64_t refcount;
+ boolean_t found = B_FALSE;
+ nvlist_t *features = zpool_get_features(zhp);
+ boolean_t supported;
+ const char *feature = strchr(propname, '@') + 1;
+
+ supported = zpool_prop_feature(propname);
+ ASSERT(supported || zpool_prop_unsupported(propname));
+
+ /*
+ * Convert from feature name to feature guid. This conversion is
+ * unnecessary for unsupported@... properties because they already
+ * use guids.
+ */
+ if (supported) {
+ int ret;
+ spa_feature_t fid;
+
+ ret = zfeature_lookup_name(feature, &fid);
+ if (ret != 0) {
+ (void) strlcpy(buf, "-", len);
+ return (ENOTSUP);
+ }
+ feature = spa_feature_table[fid].fi_guid;
+ }
+
+ if (nvlist_lookup_uint64(features, feature, &refcount) == 0)
+ found = B_TRUE;
+
+ if (supported) {
+ if (!found) {
+ (void) strlcpy(buf, ZFS_FEATURE_DISABLED, len);
+ } else {
+ if (refcount == 0)
+ (void) strlcpy(buf, ZFS_FEATURE_ENABLED, len);
+ else
+ (void) strlcpy(buf, ZFS_FEATURE_ACTIVE, len);
+ }
+ } else {
+ if (found) {
+ if (refcount == 0) {
+ (void) strcpy(buf, ZFS_UNSUPPORTED_INACTIVE);
+ } else {
+ (void) strcpy(buf, ZFS_UNSUPPORTED_READONLY);
+ }
+ } else {
+ (void) strlcpy(buf, "-", len);
+ return (ENOTSUP);
+ }
+ }
+
+ return (0);
+}
+
+/*
+ * Validate the given pool name, optionally putting an extended error message in
+ * 'buf'.
+ */
+boolean_t
+zpool_name_valid(libzfs_handle_t *hdl, boolean_t isopen, const char *pool)
+{
+ namecheck_err_t why;
+ char what;
+ int ret;
+
+ ret = pool_namecheck(pool, &why, &what);
+
+ /*
+ * The rules for reserved pool names were extended at a later point.
+ * But we need to support users with existing pools that may now be
+ * invalid. So we only check for this expanded set of names during a
+ * create (or import), and only in userland.
+ */
+ if (ret == 0 && !isopen &&
+ (strncmp(pool, "mirror", 6) == 0 ||
+ strncmp(pool, "raidz", 5) == 0 ||
+ strncmp(pool, "spare", 5) == 0 ||
+ strcmp(pool, "log") == 0)) {
+ if (hdl != NULL)
+ zfs_error_aux(hdl,
+ dgettext(TEXT_DOMAIN, "name is reserved"));
+ return (B_FALSE);
+ }
+
+
+ if (ret != 0) {
+ if (hdl != NULL) {
+ switch (why) {
+ case NAME_ERR_TOOLONG:
+ zfs_error_aux(hdl,
+ dgettext(TEXT_DOMAIN, "name is too long"));
+ break;
+
+ case NAME_ERR_INVALCHAR:
+ zfs_error_aux(hdl,
+ dgettext(TEXT_DOMAIN, "invalid character "
+ "'%c' in pool name"), what);
+ break;
+
+ case NAME_ERR_NOLETTER:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "name must begin with a letter"));
+ break;
+
+ case NAME_ERR_RESERVED:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "name is reserved"));
+ break;
+
+ case NAME_ERR_DISKLIKE:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "pool name is reserved"));
+ break;
+
+ case NAME_ERR_LEADING_SLASH:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "leading slash in name"));
+ break;
+
+ case NAME_ERR_EMPTY_COMPONENT:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "empty component in name"));
+ break;
+
+ case NAME_ERR_TRAILING_SLASH:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "trailing slash in name"));
+ break;
+
+ case NAME_ERR_MULTIPLE_DELIMITERS:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "multiple '@' and/or '#' delimiters in "
+ "name"));
+ break;
+
+ case NAME_ERR_NO_AT:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "permission set is missing '@'"));
+ break;
+
+ default:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "(%d) not defined"), why);
+ break;
+ }
+ }
+ return (B_FALSE);
+ }
+
+ return (B_TRUE);
+}
+
+/*
+ * Open a handle to the given pool, even if the pool is currently in the FAULTED
+ * state.
+ */
+zpool_handle_t *
+zpool_open_canfail(libzfs_handle_t *hdl, const char *pool)
+{
+ zpool_handle_t *zhp;
+ boolean_t missing;
+
+ /*
+ * Make sure the pool name is valid.
+ */
+ if (!zpool_name_valid(hdl, B_TRUE, pool)) {
+ (void) zfs_error_fmt(hdl, EZFS_INVALIDNAME,
+ dgettext(TEXT_DOMAIN, "cannot open '%s'"),
+ pool);
+ return (NULL);
+ }
+
+ if ((zhp = zfs_alloc(hdl, sizeof (zpool_handle_t))) == NULL)
+ return (NULL);
+
+ zhp->zpool_hdl = hdl;
+ (void) strlcpy(zhp->zpool_name, pool, sizeof (zhp->zpool_name));
+
+ if (zpool_refresh_stats(zhp, &missing) != 0) {
+ zpool_close(zhp);
+ return (NULL);
+ }
+
+ if (missing) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "no such pool"));
+ (void) zfs_error_fmt(hdl, EZFS_NOENT,
+ dgettext(TEXT_DOMAIN, "cannot open '%s'"), pool);
+ zpool_close(zhp);
+ return (NULL);
+ }
+
+ return (zhp);
+}
+
+/*
+ * Like the above, but silent on error. Used when iterating over pools (because
+ * the configuration cache may be out of date).
+ */
+int
+zpool_open_silent(libzfs_handle_t *hdl, const char *pool, zpool_handle_t **ret)
+{
+ zpool_handle_t *zhp;
+ boolean_t missing;
+
+ if ((zhp = zfs_alloc(hdl, sizeof (zpool_handle_t))) == NULL)
+ return (-1);
+
+ zhp->zpool_hdl = hdl;
+ (void) strlcpy(zhp->zpool_name, pool, sizeof (zhp->zpool_name));
+
+ if (zpool_refresh_stats(zhp, &missing) != 0) {
+ zpool_close(zhp);
+ return (-1);
+ }
+
+ if (missing) {
+ zpool_close(zhp);
+ *ret = NULL;
+ return (0);
+ }
+
+ *ret = zhp;
+ return (0);
+}
+
+/*
+ * Similar to zpool_open_canfail(), but refuses to open pools in the faulted
+ * state.
+ */
+zpool_handle_t *
+zpool_open(libzfs_handle_t *hdl, const char *pool)
+{
+ zpool_handle_t *zhp;
+
+ if ((zhp = zpool_open_canfail(hdl, pool)) == NULL)
+ return (NULL);
+
+ if (zhp->zpool_state == POOL_STATE_UNAVAIL) {
+ (void) zfs_error_fmt(hdl, EZFS_POOLUNAVAIL,
+ dgettext(TEXT_DOMAIN, "cannot open '%s'"), zhp->zpool_name);
+ zpool_close(zhp);
+ return (NULL);
+ }
+
+ return (zhp);
+}
+
+/*
+ * Close the handle. Simply frees the memory associated with the handle.
+ */
+void
+zpool_close(zpool_handle_t *zhp)
+{
+ nvlist_free(zhp->zpool_config);
+ nvlist_free(zhp->zpool_old_config);
+ nvlist_free(zhp->zpool_props);
+ free(zhp);
+}
+
+/*
+ * Return the name of the pool.
+ */
+const char *
+zpool_get_name(zpool_handle_t *zhp)
+{
+ return (zhp->zpool_name);
+}
+
+
+/*
+ * Return the state of the pool (ACTIVE or UNAVAILABLE)
+ */
+int
+zpool_get_state(zpool_handle_t *zhp)
+{
+ return (zhp->zpool_state);
+}
+
+/*
+ * Check if vdev list contains a special vdev
+ */
+static boolean_t
+zpool_has_special_vdev(nvlist_t *nvroot)
+{
+ nvlist_t **child;
+ uint_t children;
+
+ if (nvlist_lookup_nvlist_array(nvroot, ZPOOL_CONFIG_CHILDREN, &child,
+ &children) == 0) {
+ for (uint_t c = 0; c < children; c++) {
+ char *bias;
+
+ if (nvlist_lookup_string(child[c],
+ ZPOOL_CONFIG_ALLOCATION_BIAS, &bias) == 0 &&
+ strcmp(bias, VDEV_ALLOC_BIAS_SPECIAL) == 0) {
+ return (B_TRUE);
+ }
+ }
+ }
+ return (B_FALSE);
+}
+
+/*
+ * Create the named pool, using the provided vdev list. It is assumed
+ * that the consumer has already validated the contents of the nvlist, so we
+ * don't have to worry about error semantics.
+ */
+int
+zpool_create(libzfs_handle_t *hdl, const char *pool, nvlist_t *nvroot,
+ nvlist_t *props, nvlist_t *fsprops)
+{
+ zfs_cmd_t zc = {"\0"};
+ nvlist_t *zc_fsprops = NULL;
+ nvlist_t *zc_props = NULL;
+ nvlist_t *hidden_args = NULL;
+ uint8_t *wkeydata = NULL;
+ uint_t wkeylen = 0;
+ char msg[1024];
+ int ret = -1;
+
+ (void) snprintf(msg, sizeof (msg), dgettext(TEXT_DOMAIN,
+ "cannot create '%s'"), pool);
+
+ if (!zpool_name_valid(hdl, B_FALSE, pool))
+ return (zfs_error(hdl, EZFS_INVALIDNAME, msg));
+
+ if (zcmd_write_conf_nvlist(hdl, &zc, nvroot) != 0)
+ return (-1);
+
+ if (props) {
+ prop_flags_t flags = { .create = B_TRUE, .import = B_FALSE };
+
+ if ((zc_props = zpool_valid_proplist(hdl, pool, props,
+ SPA_VERSION_1, flags, msg)) == NULL) {
+ goto create_failed;
+ }
+ }
+
+ if (fsprops) {
+ uint64_t zoned;
+ char *zonestr;
+
+ zoned = ((nvlist_lookup_string(fsprops,
+ zfs_prop_to_name(ZFS_PROP_ZONED), &zonestr) == 0) &&
+ strcmp(zonestr, "on") == 0);
+
+ if ((zc_fsprops = zfs_valid_proplist(hdl, ZFS_TYPE_FILESYSTEM,
+ fsprops, zoned, NULL, NULL, B_TRUE, msg)) == NULL) {
+ goto create_failed;
+ }
+
+ if (nvlist_exists(zc_fsprops,
+ zfs_prop_to_name(ZFS_PROP_SPECIAL_SMALL_BLOCKS)) &&
+ !zpool_has_special_vdev(nvroot)) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "%s property requires a special vdev"),
+ zfs_prop_to_name(ZFS_PROP_SPECIAL_SMALL_BLOCKS));
+ (void) zfs_error(hdl, EZFS_BADPROP, msg);
+ goto create_failed;
+ }
+
+ if (!zc_props &&
+ (nvlist_alloc(&zc_props, NV_UNIQUE_NAME, 0) != 0)) {
+ goto create_failed;
+ }
+ if (zfs_crypto_create(hdl, NULL, zc_fsprops, props, B_TRUE,
+ &wkeydata, &wkeylen) != 0) {
+ zfs_error(hdl, EZFS_CRYPTOFAILED, msg);
+ goto create_failed;
+ }
+ if (nvlist_add_nvlist(zc_props,
+ ZPOOL_ROOTFS_PROPS, zc_fsprops) != 0) {
+ goto create_failed;
+ }
+ if (wkeydata != NULL) {
+ if (nvlist_alloc(&hidden_args, NV_UNIQUE_NAME, 0) != 0)
+ goto create_failed;
+
+ if (nvlist_add_uint8_array(hidden_args, "wkeydata",
+ wkeydata, wkeylen) != 0)
+ goto create_failed;
+
+ if (nvlist_add_nvlist(zc_props, ZPOOL_HIDDEN_ARGS,
+ hidden_args) != 0)
+ goto create_failed;
+ }
+ }
+
+ if (zc_props && zcmd_write_src_nvlist(hdl, &zc, zc_props) != 0)
+ goto create_failed;
+
+ (void) strlcpy(zc.zc_name, pool, sizeof (zc.zc_name));
+
+ if ((ret = zfs_ioctl(hdl, ZFS_IOC_POOL_CREATE, &zc)) != 0) {
+
+ zcmd_free_nvlists(&zc);
+ nvlist_free(zc_props);
+ nvlist_free(zc_fsprops);
+ nvlist_free(hidden_args);
+ if (wkeydata != NULL)
+ free(wkeydata);
+
+ switch (errno) {
+ case EBUSY:
+ /*
+ * This can happen if the user has specified the same
+ * device multiple times. We can't reliably detect this
+ * until we try to add it and see we already have a
+ * label. This can also happen under if the device is
+ * part of an active md or lvm device.
+ */
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "one or more vdevs refer to the same device, or "
+ "one of\nthe devices is part of an active md or "
+ "lvm device"));
+ return (zfs_error(hdl, EZFS_BADDEV, msg));
+
+ case ERANGE:
+ /*
+ * This happens if the record size is smaller or larger
+ * than the allowed size range, or not a power of 2.
+ *
+ * NOTE: although zfs_valid_proplist is called earlier,
+ * this case may have slipped through since the
+ * pool does not exist yet and it is therefore
+ * impossible to read properties e.g. max blocksize
+ * from the pool.
+ */
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "record size invalid"));
+ return (zfs_error(hdl, EZFS_BADPROP, msg));
+
+ case EOVERFLOW:
+ /*
+ * This occurs when one of the devices is below
+ * SPA_MINDEVSIZE. Unfortunately, we can't detect which
+ * device was the problem device since there's no
+ * reliable way to determine device size from userland.
+ */
+ {
+ char buf[64];
+
+ zfs_nicebytes(SPA_MINDEVSIZE, buf,
+ sizeof (buf));
+
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "one or more devices is less than the "
+ "minimum size (%s)"), buf);
+ }
+ return (zfs_error(hdl, EZFS_BADDEV, msg));
+
+ case ENOSPC:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "one or more devices is out of space"));
+ return (zfs_error(hdl, EZFS_BADDEV, msg));
+
+ default:
+ return (zpool_standard_error(hdl, errno, msg));
+ }
+ }
+
+create_failed:
+ zcmd_free_nvlists(&zc);
+ nvlist_free(zc_props);
+ nvlist_free(zc_fsprops);
+ nvlist_free(hidden_args);
+ if (wkeydata != NULL)
+ free(wkeydata);
+ return (ret);
+}
+
+/*
+ * Destroy the given pool. It is up to the caller to ensure that there are no
+ * datasets left in the pool.
+ */
+int
+zpool_destroy(zpool_handle_t *zhp, const char *log_str)
+{
+ zfs_cmd_t zc = {"\0"};
+ zfs_handle_t *zfp = NULL;
+ libzfs_handle_t *hdl = zhp->zpool_hdl;
+ char msg[1024];
+
+ if (zhp->zpool_state == POOL_STATE_ACTIVE &&
+ (zfp = zfs_open(hdl, zhp->zpool_name, ZFS_TYPE_FILESYSTEM)) == NULL)
+ return (-1);
+
+ (void) strlcpy(zc.zc_name, zhp->zpool_name, sizeof (zc.zc_name));
+ zc.zc_history = (uint64_t)(uintptr_t)log_str;
+
+ if (zfs_ioctl(hdl, ZFS_IOC_POOL_DESTROY, &zc) != 0) {
+ (void) snprintf(msg, sizeof (msg), dgettext(TEXT_DOMAIN,
+ "cannot destroy '%s'"), zhp->zpool_name);
+
+ if (errno == EROFS) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "one or more devices is read only"));
+ (void) zfs_error(hdl, EZFS_BADDEV, msg);
+ } else {
+ (void) zpool_standard_error(hdl, errno, msg);
+ }
+
+ if (zfp)
+ zfs_close(zfp);
+ return (-1);
+ }
+
+ if (zfp) {
+ remove_mountpoint(zfp);
+ zfs_close(zfp);
+ }
+
+ return (0);
+}
+
+/*
+ * Create a checkpoint in the given pool.
+ */
+int
+zpool_checkpoint(zpool_handle_t *zhp)
+{
+ libzfs_handle_t *hdl = zhp->zpool_hdl;
+ char msg[1024];
+ int error;
+
+ error = lzc_pool_checkpoint(zhp->zpool_name);
+ if (error != 0) {
+ (void) snprintf(msg, sizeof (msg), dgettext(TEXT_DOMAIN,
+ "cannot checkpoint '%s'"), zhp->zpool_name);
+ (void) zpool_standard_error(hdl, error, msg);
+ return (-1);
+ }
+
+ return (0);
+}
+
+/*
+ * Discard the checkpoint from the given pool.
+ */
+int
+zpool_discard_checkpoint(zpool_handle_t *zhp)
+{
+ libzfs_handle_t *hdl = zhp->zpool_hdl;
+ char msg[1024];
+ int error;
+
+ error = lzc_pool_checkpoint_discard(zhp->zpool_name);
+ if (error != 0) {
+ (void) snprintf(msg, sizeof (msg), dgettext(TEXT_DOMAIN,
+ "cannot discard checkpoint in '%s'"), zhp->zpool_name);
+ (void) zpool_standard_error(hdl, error, msg);
+ return (-1);
+ }
+
+ return (0);
+}
+
+/*
+ * Add the given vdevs to the pool. The caller must have already performed the
+ * necessary verification to ensure that the vdev specification is well-formed.
+ */
+int
+zpool_add(zpool_handle_t *zhp, nvlist_t *nvroot)
+{
+ zfs_cmd_t zc = {"\0"};
+ int ret;
+ libzfs_handle_t *hdl = zhp->zpool_hdl;
+ char msg[1024];
+ nvlist_t **spares, **l2cache;
+ uint_t nspares, nl2cache;
+
+ (void) snprintf(msg, sizeof (msg), dgettext(TEXT_DOMAIN,
+ "cannot add to '%s'"), zhp->zpool_name);
+
+ if (zpool_get_prop_int(zhp, ZPOOL_PROP_VERSION, NULL) <
+ SPA_VERSION_SPARES &&
+ nvlist_lookup_nvlist_array(nvroot, ZPOOL_CONFIG_SPARES,
+ &spares, &nspares) == 0) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "pool must be "
+ "upgraded to add hot spares"));
+ return (zfs_error(hdl, EZFS_BADVERSION, msg));
+ }
+
+ if (zpool_get_prop_int(zhp, ZPOOL_PROP_VERSION, NULL) <
+ SPA_VERSION_L2CACHE &&
+ nvlist_lookup_nvlist_array(nvroot, ZPOOL_CONFIG_L2CACHE,
+ &l2cache, &nl2cache) == 0) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "pool must be "
+ "upgraded to add cache devices"));
+ return (zfs_error(hdl, EZFS_BADVERSION, msg));
+ }
+
+ if (zcmd_write_conf_nvlist(hdl, &zc, nvroot) != 0)
+ return (-1);
+ (void) strlcpy(zc.zc_name, zhp->zpool_name, sizeof (zc.zc_name));
+
+ if (zfs_ioctl(hdl, ZFS_IOC_VDEV_ADD, &zc) != 0) {
+ switch (errno) {
+ case EBUSY:
+ /*
+ * This can happen if the user has specified the same
+ * device multiple times. We can't reliably detect this
+ * until we try to add it and see we already have a
+ * label.
+ */
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "one or more vdevs refer to the same device"));
+ (void) zfs_error(hdl, EZFS_BADDEV, msg);
+ break;
+
+ case EINVAL:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "invalid config; a pool with removing/removed "
+ "vdevs does not support adding raidz vdevs"));
+ (void) zfs_error(hdl, EZFS_BADDEV, msg);
+ break;
+
+ case EOVERFLOW:
+ /*
+ * This occurs when one of the devices is below
+ * SPA_MINDEVSIZE. Unfortunately, we can't detect which
+ * device was the problem device since there's no
+ * reliable way to determine device size from userland.
+ */
+ {
+ char buf[64];
+
+ zfs_nicebytes(SPA_MINDEVSIZE, buf,
+ sizeof (buf));
+
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "device is less than the minimum "
+ "size (%s)"), buf);
+ }
+ (void) zfs_error(hdl, EZFS_BADDEV, msg);
+ break;
+
+ case ENOTSUP:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "pool must be upgraded to add these vdevs"));
+ (void) zfs_error(hdl, EZFS_BADVERSION, msg);
+ break;
+
+ default:
+ (void) zpool_standard_error(hdl, errno, msg);
+ }
+
+ ret = -1;
+ } else {
+ ret = 0;
+ }
+
+ zcmd_free_nvlists(&zc);
+
+ return (ret);
+}
+
+/*
+ * Exports the pool from the system. The caller must ensure that there are no
+ * mounted datasets in the pool.
+ */
+static int
+zpool_export_common(zpool_handle_t *zhp, boolean_t force, boolean_t hardforce,
+ const char *log_str)
+{
+ zfs_cmd_t zc = {"\0"};
+ char msg[1024];
+
+ (void) snprintf(msg, sizeof (msg), dgettext(TEXT_DOMAIN,
+ "cannot export '%s'"), zhp->zpool_name);
+
+ (void) strlcpy(zc.zc_name, zhp->zpool_name, sizeof (zc.zc_name));
+ zc.zc_cookie = force;
+ zc.zc_guid = hardforce;
+ zc.zc_history = (uint64_t)(uintptr_t)log_str;
+
+ if (zfs_ioctl(zhp->zpool_hdl, ZFS_IOC_POOL_EXPORT, &zc) != 0) {
+ switch (errno) {
+ case EXDEV:
+ zfs_error_aux(zhp->zpool_hdl, dgettext(TEXT_DOMAIN,
+ "use '-f' to override the following errors:\n"
+ "'%s' has an active shared spare which could be"
+ " used by other pools once '%s' is exported."),
+ zhp->zpool_name, zhp->zpool_name);
+ return (zfs_error(zhp->zpool_hdl, EZFS_ACTIVE_SPARE,
+ msg));
+ default:
+ return (zpool_standard_error_fmt(zhp->zpool_hdl, errno,
+ msg));
+ }
+ }
+
+ return (0);
+}
+
+int
+zpool_export(zpool_handle_t *zhp, boolean_t force, const char *log_str)
+{
+ return (zpool_export_common(zhp, force, B_FALSE, log_str));
+}
+
+int
+zpool_export_force(zpool_handle_t *zhp, const char *log_str)
+{
+ return (zpool_export_common(zhp, B_TRUE, B_TRUE, log_str));
+}
+
+static void
+zpool_rewind_exclaim(libzfs_handle_t *hdl, const char *name, boolean_t dryrun,
+ nvlist_t *config)
+{
+ nvlist_t *nv = NULL;
+ uint64_t rewindto;
+ int64_t loss = -1;
+ struct tm t;
+ char timestr[128];
+
+ if (!hdl->libzfs_printerr || config == NULL)
+ return;
+
+ if (nvlist_lookup_nvlist(config, ZPOOL_CONFIG_LOAD_INFO, &nv) != 0 ||
+ nvlist_lookup_nvlist(nv, ZPOOL_CONFIG_REWIND_INFO, &nv) != 0) {
+ return;
+ }
+
+ if (nvlist_lookup_uint64(nv, ZPOOL_CONFIG_LOAD_TIME, &rewindto) != 0)
+ return;
+ (void) nvlist_lookup_int64(nv, ZPOOL_CONFIG_REWIND_TIME, &loss);
+
+ if (localtime_r((time_t *)&rewindto, &t) != NULL &&
+ strftime(timestr, 128, "%c", &t) != 0) {
+ if (dryrun) {
+ (void) printf(dgettext(TEXT_DOMAIN,
+ "Would be able to return %s "
+ "to its state as of %s.\n"),
+ name, timestr);
+ } else {
+ (void) printf(dgettext(TEXT_DOMAIN,
+ "Pool %s returned to its state as of %s.\n"),
+ name, timestr);
+ }
+ if (loss > 120) {
+ (void) printf(dgettext(TEXT_DOMAIN,
+ "%s approximately %lld "),
+ dryrun ? "Would discard" : "Discarded",
+ ((longlong_t)loss + 30) / 60);
+ (void) printf(dgettext(TEXT_DOMAIN,
+ "minutes of transactions.\n"));
+ } else if (loss > 0) {
+ (void) printf(dgettext(TEXT_DOMAIN,
+ "%s approximately %lld "),
+ dryrun ? "Would discard" : "Discarded",
+ (longlong_t)loss);
+ (void) printf(dgettext(TEXT_DOMAIN,
+ "seconds of transactions.\n"));
+ }
+ }
+}
+
+void
+zpool_explain_recover(libzfs_handle_t *hdl, const char *name, int reason,
+ nvlist_t *config)
+{
+ nvlist_t *nv = NULL;
+ int64_t loss = -1;
+ uint64_t edata = UINT64_MAX;
+ uint64_t rewindto;
+ struct tm t;
+ char timestr[128];
+
+ if (!hdl->libzfs_printerr)
+ return;
+
+ if (reason >= 0)
+ (void) printf(dgettext(TEXT_DOMAIN, "action: "));
+ else
+ (void) printf(dgettext(TEXT_DOMAIN, "\t"));
+
+ /* All attempted rewinds failed if ZPOOL_CONFIG_LOAD_TIME missing */
+ if (nvlist_lookup_nvlist(config, ZPOOL_CONFIG_LOAD_INFO, &nv) != 0 ||
+ nvlist_lookup_nvlist(nv, ZPOOL_CONFIG_REWIND_INFO, &nv) != 0 ||
+ nvlist_lookup_uint64(nv, ZPOOL_CONFIG_LOAD_TIME, &rewindto) != 0)
+ goto no_info;
+
+ (void) nvlist_lookup_int64(nv, ZPOOL_CONFIG_REWIND_TIME, &loss);
+ (void) nvlist_lookup_uint64(nv, ZPOOL_CONFIG_LOAD_DATA_ERRORS,
+ &edata);
+
+ (void) printf(dgettext(TEXT_DOMAIN,
+ "Recovery is possible, but will result in some data loss.\n"));
+
+ if (localtime_r((time_t *)&rewindto, &t) != NULL &&
+ strftime(timestr, 128, "%c", &t) != 0) {
+ (void) printf(dgettext(TEXT_DOMAIN,
+ "\tReturning the pool to its state as of %s\n"
+ "\tshould correct the problem. "),
+ timestr);
+ } else {
+ (void) printf(dgettext(TEXT_DOMAIN,
+ "\tReverting the pool to an earlier state "
+ "should correct the problem.\n\t"));
+ }
+
+ if (loss > 120) {
+ (void) printf(dgettext(TEXT_DOMAIN,
+ "Approximately %lld minutes of data\n"
+ "\tmust be discarded, irreversibly. "),
+ ((longlong_t)loss + 30) / 60);
+ } else if (loss > 0) {
+ (void) printf(dgettext(TEXT_DOMAIN,
+ "Approximately %lld seconds of data\n"
+ "\tmust be discarded, irreversibly. "),
+ (longlong_t)loss);
+ }
+ if (edata != 0 && edata != UINT64_MAX) {
+ if (edata == 1) {
+ (void) printf(dgettext(TEXT_DOMAIN,
+ "After rewind, at least\n"
+ "\tone persistent user-data error will remain. "));
+ } else {
+ (void) printf(dgettext(TEXT_DOMAIN,
+ "After rewind, several\n"
+ "\tpersistent user-data errors will remain. "));
+ }
+ }
+ (void) printf(dgettext(TEXT_DOMAIN,
+ "Recovery can be attempted\n\tby executing 'zpool %s -F %s'. "),
+ reason >= 0 ? "clear" : "import", name);
+
+ (void) printf(dgettext(TEXT_DOMAIN,
+ "A scrub of the pool\n"
+ "\tis strongly recommended after recovery.\n"));
+ return;
+
+no_info:
+ (void) printf(dgettext(TEXT_DOMAIN,
+ "Destroy and re-create the pool from\n\ta backup source.\n"));
+}
+
+/*
+ * zpool_import() is a contracted interface. Should be kept the same
+ * if possible.
+ *
+ * Applications should use zpool_import_props() to import a pool with
+ * new properties value to be set.
+ */
+int
+zpool_import(libzfs_handle_t *hdl, nvlist_t *config, const char *newname,
+ char *altroot)
+{
+ nvlist_t *props = NULL;
+ int ret;
+
+ if (altroot != NULL) {
+ if (nvlist_alloc(&props, NV_UNIQUE_NAME, 0) != 0) {
+ return (zfs_error_fmt(hdl, EZFS_NOMEM,
+ dgettext(TEXT_DOMAIN, "cannot import '%s'"),
+ newname));
+ }
+
+ if (nvlist_add_string(props,
+ zpool_prop_to_name(ZPOOL_PROP_ALTROOT), altroot) != 0 ||
+ nvlist_add_string(props,
+ zpool_prop_to_name(ZPOOL_PROP_CACHEFILE), "none") != 0) {
+ nvlist_free(props);
+ return (zfs_error_fmt(hdl, EZFS_NOMEM,
+ dgettext(TEXT_DOMAIN, "cannot import '%s'"),
+ newname));
+ }
+ }
+
+ ret = zpool_import_props(hdl, config, newname, props,
+ ZFS_IMPORT_NORMAL);
+ nvlist_free(props);
+ return (ret);
+}
+
+static void
+print_vdev_tree(libzfs_handle_t *hdl, const char *name, nvlist_t *nv,
+ int indent)
+{
+ nvlist_t **child;
+ uint_t c, children;
+ char *vname;
+ uint64_t is_log = 0;
+
+ (void) nvlist_lookup_uint64(nv, ZPOOL_CONFIG_IS_LOG,
+ &is_log);
+
+ if (name != NULL)
+ (void) printf("\t%*s%s%s\n", indent, "", name,
+ is_log ? " [log]" : "");
+
+ if (nvlist_lookup_nvlist_array(nv, ZPOOL_CONFIG_CHILDREN,
+ &child, &children) != 0)
+ return;
+
+ for (c = 0; c < children; c++) {
+ vname = zpool_vdev_name(hdl, NULL, child[c], VDEV_NAME_TYPE_ID);
+ print_vdev_tree(hdl, vname, child[c], indent + 2);
+ free(vname);
+ }
+}
+
+void
+zpool_print_unsup_feat(nvlist_t *config)
+{
+ nvlist_t *nvinfo, *unsup_feat;
+ nvpair_t *nvp;
+
+ verify(nvlist_lookup_nvlist(config, ZPOOL_CONFIG_LOAD_INFO, &nvinfo) ==
+ 0);
+ verify(nvlist_lookup_nvlist(nvinfo, ZPOOL_CONFIG_UNSUP_FEAT,
+ &unsup_feat) == 0);
+
+ for (nvp = nvlist_next_nvpair(unsup_feat, NULL); nvp != NULL;
+ nvp = nvlist_next_nvpair(unsup_feat, nvp)) {
+ char *desc;
+
+ verify(nvpair_type(nvp) == DATA_TYPE_STRING);
+ verify(nvpair_value_string(nvp, &desc) == 0);
+
+ if (strlen(desc) > 0)
+ (void) printf("\t%s (%s)\n", nvpair_name(nvp), desc);
+ else
+ (void) printf("\t%s\n", nvpair_name(nvp));
+ }
+}
+
+/*
+ * Import the given pool using the known configuration and a list of
+ * properties to be set. The configuration should have come from
+ * zpool_find_import(). The 'newname' parameters control whether the pool
+ * is imported with a different name.
+ */
+int
+zpool_import_props(libzfs_handle_t *hdl, nvlist_t *config, const char *newname,
+ nvlist_t *props, int flags)
+{
+ zfs_cmd_t zc = {"\0"};
+ zpool_load_policy_t policy;
+ nvlist_t *nv = NULL;
+ nvlist_t *nvinfo = NULL;
+ nvlist_t *missing = NULL;
+ char *thename;
+ char *origname;
+ int ret;
+ int error = 0;
+ char errbuf[1024];
+
+ verify(nvlist_lookup_string(config, ZPOOL_CONFIG_POOL_NAME,
+ &origname) == 0);
+
+ (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
+ "cannot import pool '%s'"), origname);
+
+ if (newname != NULL) {
+ if (!zpool_name_valid(hdl, B_FALSE, newname))
+ return (zfs_error_fmt(hdl, EZFS_INVALIDNAME,
+ dgettext(TEXT_DOMAIN, "cannot import '%s'"),
+ newname));
+ thename = (char *)newname;
+ } else {
+ thename = origname;
+ }
+
+ if (props != NULL) {
+ uint64_t version;
+ prop_flags_t flags = { .create = B_FALSE, .import = B_TRUE };
+
+ verify(nvlist_lookup_uint64(config, ZPOOL_CONFIG_VERSION,
+ &version) == 0);
+
+ if ((props = zpool_valid_proplist(hdl, origname,
+ props, version, flags, errbuf)) == NULL)
+ return (-1);
+ if (zcmd_write_src_nvlist(hdl, &zc, props) != 0) {
+ nvlist_free(props);
+ return (-1);
+ }
+ nvlist_free(props);
+ }
+
+ (void) strlcpy(zc.zc_name, thename, sizeof (zc.zc_name));
+
+ verify(nvlist_lookup_uint64(config, ZPOOL_CONFIG_POOL_GUID,
+ &zc.zc_guid) == 0);
+
+ if (zcmd_write_conf_nvlist(hdl, &zc, config) != 0) {
+ zcmd_free_nvlists(&zc);
+ return (-1);
+ }
+ if (zcmd_alloc_dst_nvlist(hdl, &zc, zc.zc_nvlist_conf_size * 2) != 0) {
+ zcmd_free_nvlists(&zc);
+ return (-1);
+ }
+
+ zc.zc_cookie = flags;
+ while ((ret = zfs_ioctl(hdl, ZFS_IOC_POOL_IMPORT, &zc)) != 0 &&
+ errno == ENOMEM) {
+ if (zcmd_expand_dst_nvlist(hdl, &zc) != 0) {
+ zcmd_free_nvlists(&zc);
+ return (-1);
+ }
+ }
+ if (ret != 0)
+ error = errno;
+
+ (void) zcmd_read_dst_nvlist(hdl, &zc, &nv);
+
+ zcmd_free_nvlists(&zc);
+
+ zpool_get_load_policy(config, &policy);
+
+ if (error) {
+ char desc[1024];
+ char aux[256];
+
+ /*
+ * Dry-run failed, but we print out what success
+ * looks like if we found a best txg
+ */
+ if (policy.zlp_rewind & ZPOOL_TRY_REWIND) {
+ zpool_rewind_exclaim(hdl, newname ? origname : thename,
+ B_TRUE, nv);
+ nvlist_free(nv);
+ return (-1);
+ }
+
+ if (newname == NULL)
+ (void) snprintf(desc, sizeof (desc),
+ dgettext(TEXT_DOMAIN, "cannot import '%s'"),
+ thename);
+ else
+ (void) snprintf(desc, sizeof (desc),
+ dgettext(TEXT_DOMAIN, "cannot import '%s' as '%s'"),
+ origname, thename);
+
+ switch (error) {
+ case ENOTSUP:
+ if (nv != NULL && nvlist_lookup_nvlist(nv,
+ ZPOOL_CONFIG_LOAD_INFO, &nvinfo) == 0 &&
+ nvlist_exists(nvinfo, ZPOOL_CONFIG_UNSUP_FEAT)) {
+ (void) printf(dgettext(TEXT_DOMAIN, "This "
+ "pool uses the following feature(s) not "
+ "supported by this system:\n"));
+ zpool_print_unsup_feat(nv);
+ if (nvlist_exists(nvinfo,
+ ZPOOL_CONFIG_CAN_RDONLY)) {
+ (void) printf(dgettext(TEXT_DOMAIN,
+ "All unsupported features are only "
+ "required for writing to the pool."
+ "\nThe pool can be imported using "
+ "'-o readonly=on'.\n"));
+ }
+ }
+ /*
+ * Unsupported version.
+ */
+ (void) zfs_error(hdl, EZFS_BADVERSION, desc);
+ break;
+
+ case EREMOTEIO:
+ if (nv != NULL && nvlist_lookup_nvlist(nv,
+ ZPOOL_CONFIG_LOAD_INFO, &nvinfo) == 0) {
+ char *hostname = "<unknown>";
+ uint64_t hostid = 0;
+ mmp_state_t mmp_state;
+
+ mmp_state = fnvlist_lookup_uint64(nvinfo,
+ ZPOOL_CONFIG_MMP_STATE);
+
+ if (nvlist_exists(nvinfo,
+ ZPOOL_CONFIG_MMP_HOSTNAME))
+ hostname = fnvlist_lookup_string(nvinfo,
+ ZPOOL_CONFIG_MMP_HOSTNAME);
+
+ if (nvlist_exists(nvinfo,
+ ZPOOL_CONFIG_MMP_HOSTID))
+ hostid = fnvlist_lookup_uint64(nvinfo,
+ ZPOOL_CONFIG_MMP_HOSTID);
+
+ if (mmp_state == MMP_STATE_ACTIVE) {
+ (void) snprintf(aux, sizeof (aux),
+ dgettext(TEXT_DOMAIN, "pool is imp"
+ "orted on host '%s' (hostid=%lx).\n"
+ "Export the pool on the other "
+ "system, then run 'zpool import'."),
+ hostname, (unsigned long) hostid);
+ } else if (mmp_state == MMP_STATE_NO_HOSTID) {
+ (void) snprintf(aux, sizeof (aux),
+ dgettext(TEXT_DOMAIN, "pool has "
+ "the multihost property on and "
+ "the\nsystem's hostid is not set. "
+ "Set a unique system hostid with "
+ "the zgenhostid(8) command.\n"));
+ }
+
+ (void) zfs_error_aux(hdl, aux);
+ }
+ (void) zfs_error(hdl, EZFS_ACTIVE_POOL, desc);
+ break;
+
+ case EINVAL:
+ (void) zfs_error(hdl, EZFS_INVALCONFIG, desc);
+ break;
+
+ case EROFS:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "one or more devices is read only"));
+ (void) zfs_error(hdl, EZFS_BADDEV, desc);
+ break;
+
+ case ENXIO:
+ if (nv && nvlist_lookup_nvlist(nv,
+ ZPOOL_CONFIG_LOAD_INFO, &nvinfo) == 0 &&
+ nvlist_lookup_nvlist(nvinfo,
+ ZPOOL_CONFIG_MISSING_DEVICES, &missing) == 0) {
+ (void) printf(dgettext(TEXT_DOMAIN,
+ "The devices below are missing or "
+ "corrupted, use '-m' to import the pool "
+ "anyway:\n"));
+ print_vdev_tree(hdl, NULL, missing, 2);
+ (void) printf("\n");
+ }
+ (void) zpool_standard_error(hdl, error, desc);
+ break;
+
+ case EEXIST:
+ (void) zpool_standard_error(hdl, error, desc);
+ break;
+
+ case EBUSY:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "one or more devices are already in use\n"));
+ (void) zfs_error(hdl, EZFS_BADDEV, desc);
+ break;
+ case ENAMETOOLONG:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "new name of at least one dataset is longer than "
+ "the maximum allowable length"));
+ (void) zfs_error(hdl, EZFS_NAMETOOLONG, desc);
+ break;
+ default:
+ (void) zpool_standard_error(hdl, error, desc);
+ zpool_explain_recover(hdl,
+ newname ? origname : thename, -error, nv);
+ break;
+ }
+
+ nvlist_free(nv);
+ ret = -1;
+ } else {
+ zpool_handle_t *zhp;
+
+ /*
+ * This should never fail, but play it safe anyway.
+ */
+ if (zpool_open_silent(hdl, thename, &zhp) != 0)
+ ret = -1;
+ else if (zhp != NULL)
+ zpool_close(zhp);
+ if (policy.zlp_rewind &
+ (ZPOOL_DO_REWIND | ZPOOL_TRY_REWIND)) {
+ zpool_rewind_exclaim(hdl, newname ? origname : thename,
+ ((policy.zlp_rewind & ZPOOL_TRY_REWIND) != 0), nv);
+ }
+ nvlist_free(nv);
+ return (0);
+ }
+
+ return (ret);
+}
+
+/*
+ * Translate vdev names to guids. If a vdev_path is determined to be
+ * unsuitable then a vd_errlist is allocated and the vdev path and errno
+ * are added to it.
+ */
+static int
+zpool_translate_vdev_guids(zpool_handle_t *zhp, nvlist_t *vds,
+ nvlist_t *vdev_guids, nvlist_t *guids_to_paths, nvlist_t **vd_errlist)
+{
+ nvlist_t *errlist = NULL;
+ int error = 0;
+
+ for (nvpair_t *elem = nvlist_next_nvpair(vds, NULL); elem != NULL;
+ elem = nvlist_next_nvpair(vds, elem)) {
+ boolean_t spare, cache;
+
+ char *vd_path = nvpair_name(elem);
+ nvlist_t *tgt = zpool_find_vdev(zhp, vd_path, &spare, &cache,
+ NULL);
+
+ if ((tgt == NULL) || cache || spare) {
+ if (errlist == NULL) {
+ errlist = fnvlist_alloc();
+ error = EINVAL;
+ }
+
+ uint64_t err = (tgt == NULL) ? EZFS_NODEVICE :
+ (spare ? EZFS_ISSPARE : EZFS_ISL2CACHE);
+ fnvlist_add_int64(errlist, vd_path, err);
+ continue;
+ }
+
+ uint64_t guid = fnvlist_lookup_uint64(tgt, ZPOOL_CONFIG_GUID);
+ fnvlist_add_uint64(vdev_guids, vd_path, guid);
+
+ char msg[MAXNAMELEN];
+ (void) snprintf(msg, sizeof (msg), "%llu", (u_longlong_t)guid);
+ fnvlist_add_string(guids_to_paths, msg, vd_path);
+ }
+
+ if (error != 0) {
+ verify(errlist != NULL);
+ if (vd_errlist != NULL)
+ *vd_errlist = errlist;
+ else
+ fnvlist_free(errlist);
+ }
+
+ return (error);
+}
+
+static int
+xlate_init_err(int err)
+{
+ switch (err) {
+ case ENODEV:
+ return (EZFS_NODEVICE);
+ case EINVAL:
+ case EROFS:
+ return (EZFS_BADDEV);
+ case EBUSY:
+ return (EZFS_INITIALIZING);
+ case ESRCH:
+ return (EZFS_NO_INITIALIZE);
+ }
+ return (err);
+}
+
+/*
+ * Begin, suspend, or cancel the initialization (initializing of all free
+ * blocks) for the given vdevs in the given pool.
+ */
+static int
+zpool_initialize_impl(zpool_handle_t *zhp, pool_initialize_func_t cmd_type,
+ nvlist_t *vds, boolean_t wait)
+{
+ int err;
+
+ nvlist_t *vdev_guids = fnvlist_alloc();
+ nvlist_t *guids_to_paths = fnvlist_alloc();
+ nvlist_t *vd_errlist = NULL;
+ nvlist_t *errlist;
+ nvpair_t *elem;
+
+ err = zpool_translate_vdev_guids(zhp, vds, vdev_guids,
+ guids_to_paths, &vd_errlist);
+
+ if (err != 0) {
+ verify(vd_errlist != NULL);
+ goto list_errors;
+ }
+
+ err = lzc_initialize(zhp->zpool_name, cmd_type,
+ vdev_guids, &errlist);
+
+ if (err != 0) {
+ if (errlist != NULL) {
+ vd_errlist = fnvlist_lookup_nvlist(errlist,
+ ZPOOL_INITIALIZE_VDEVS);
+ goto list_errors;
+ }
+ (void) zpool_standard_error(zhp->zpool_hdl, err,
+ dgettext(TEXT_DOMAIN, "operation failed"));
+ goto out;
+ }
+
+ if (wait) {
+ for (elem = nvlist_next_nvpair(vdev_guids, NULL); elem != NULL;
+ elem = nvlist_next_nvpair(vdev_guids, elem)) {
+
+ uint64_t guid = fnvpair_value_uint64(elem);
+
+ err = lzc_wait_tag(zhp->zpool_name,
+ ZPOOL_WAIT_INITIALIZE, guid, NULL);
+ if (err != 0) {
+ (void) zpool_standard_error_fmt(zhp->zpool_hdl,
+ err, dgettext(TEXT_DOMAIN, "error "
+ "waiting for '%s' to initialize"),
+ nvpair_name(elem));
+
+ goto out;
+ }
+ }
+ }
+ goto out;
+
+list_errors:
+ for (elem = nvlist_next_nvpair(vd_errlist, NULL); elem != NULL;
+ elem = nvlist_next_nvpair(vd_errlist, elem)) {
+ int64_t vd_error = xlate_init_err(fnvpair_value_int64(elem));
+ char *path;
+
+ if (nvlist_lookup_string(guids_to_paths, nvpair_name(elem),
+ &path) != 0)
+ path = nvpair_name(elem);
+
+ (void) zfs_error_fmt(zhp->zpool_hdl, vd_error,
+ "cannot initialize '%s'", path);
+ }
+
+out:
+ fnvlist_free(vdev_guids);
+ fnvlist_free(guids_to_paths);
+
+ if (vd_errlist != NULL)
+ fnvlist_free(vd_errlist);
+
+ return (err == 0 ? 0 : -1);
+}
+
+int
+zpool_initialize(zpool_handle_t *zhp, pool_initialize_func_t cmd_type,
+ nvlist_t *vds)
+{
+ return (zpool_initialize_impl(zhp, cmd_type, vds, B_FALSE));
+}
+
+int
+zpool_initialize_wait(zpool_handle_t *zhp, pool_initialize_func_t cmd_type,
+ nvlist_t *vds)
+{
+ return (zpool_initialize_impl(zhp, cmd_type, vds, B_TRUE));
+}
+
+static int
+xlate_trim_err(int err)
+{
+ switch (err) {
+ case ENODEV:
+ return (EZFS_NODEVICE);
+ case EINVAL:
+ case EROFS:
+ return (EZFS_BADDEV);
+ case EBUSY:
+ return (EZFS_TRIMMING);
+ case ESRCH:
+ return (EZFS_NO_TRIM);
+ case EOPNOTSUPP:
+ return (EZFS_TRIM_NOTSUP);
+ }
+ return (err);
+}
+
+static int
+zpool_trim_wait(zpool_handle_t *zhp, nvlist_t *vdev_guids)
+{
+ int err;
+ nvpair_t *elem;
+
+ for (elem = nvlist_next_nvpair(vdev_guids, NULL); elem != NULL;
+ elem = nvlist_next_nvpair(vdev_guids, elem)) {
+
+ uint64_t guid = fnvpair_value_uint64(elem);
+
+ err = lzc_wait_tag(zhp->zpool_name,
+ ZPOOL_WAIT_TRIM, guid, NULL);
+ if (err != 0) {
+ (void) zpool_standard_error_fmt(zhp->zpool_hdl,
+ err, dgettext(TEXT_DOMAIN, "error "
+ "waiting to trim '%s'"), nvpair_name(elem));
+
+ return (err);
+ }
+ }
+ return (0);
+}
+
+/*
+ * Check errlist and report any errors, omitting ones which should be
+ * suppressed. Returns B_TRUE if any errors were reported.
+ */
+static boolean_t
+check_trim_errs(zpool_handle_t *zhp, trimflags_t *trim_flags,
+ nvlist_t *guids_to_paths, nvlist_t *vds, nvlist_t *errlist)
+{
+ nvpair_t *elem;
+ boolean_t reported_errs = B_FALSE;
+ int num_vds = 0;
+ int num_suppressed_errs = 0;
+
+ for (elem = nvlist_next_nvpair(vds, NULL);
+ elem != NULL; elem = nvlist_next_nvpair(vds, elem)) {
+ num_vds++;
+ }
+
+ for (elem = nvlist_next_nvpair(errlist, NULL);
+ elem != NULL; elem = nvlist_next_nvpair(errlist, elem)) {
+ int64_t vd_error = xlate_trim_err(fnvpair_value_int64(elem));
+ char *path;
+
+ /*
+ * If only the pool was specified, and it was not a secure
+ * trim then suppress warnings for individual vdevs which
+ * do not support trimming.
+ */
+ if (vd_error == EZFS_TRIM_NOTSUP &&
+ trim_flags->fullpool &&
+ !trim_flags->secure) {
+ num_suppressed_errs++;
+ continue;
+ }
+
+ reported_errs = B_TRUE;
+ if (nvlist_lookup_string(guids_to_paths, nvpair_name(elem),
+ &path) != 0)
+ path = nvpair_name(elem);
+
+ (void) zfs_error_fmt(zhp->zpool_hdl, vd_error,
+ "cannot trim '%s'", path);
+ }
+
+ if (num_suppressed_errs == num_vds) {
+ (void) zfs_error_aux(zhp->zpool_hdl, dgettext(TEXT_DOMAIN,
+ "no devices in pool support trim operations"));
+ (void) (zfs_error(zhp->zpool_hdl, EZFS_TRIM_NOTSUP,
+ dgettext(TEXT_DOMAIN, "cannot trim")));
+ reported_errs = B_TRUE;
+ }
+
+ return (reported_errs);
+}
+
+/*
+ * Begin, suspend, or cancel the TRIM (discarding of all free blocks) for
+ * the given vdevs in the given pool.
+ */
+int
+zpool_trim(zpool_handle_t *zhp, pool_trim_func_t cmd_type, nvlist_t *vds,
+ trimflags_t *trim_flags)
+{
+ int err;
+ int retval = 0;
+
+ nvlist_t *vdev_guids = fnvlist_alloc();
+ nvlist_t *guids_to_paths = fnvlist_alloc();
+ nvlist_t *errlist = NULL;
+
+ err = zpool_translate_vdev_guids(zhp, vds, vdev_guids,
+ guids_to_paths, &errlist);
+ if (err != 0) {
+ check_trim_errs(zhp, trim_flags, guids_to_paths, vds, errlist);
+ retval = -1;
+ goto out;
+ }
+
+ err = lzc_trim(zhp->zpool_name, cmd_type, trim_flags->rate,
+ trim_flags->secure, vdev_guids, &errlist);
+ if (err != 0) {
+ nvlist_t *vd_errlist;
+ if (errlist != NULL && nvlist_lookup_nvlist(errlist,
+ ZPOOL_TRIM_VDEVS, &vd_errlist) == 0) {
+ if (check_trim_errs(zhp, trim_flags, guids_to_paths,
+ vds, vd_errlist)) {
+ retval = -1;
+ goto out;
+ }
+ } else {
+ char msg[1024];
+
+ (void) snprintf(msg, sizeof (msg),
+ dgettext(TEXT_DOMAIN, "operation failed"));
+ zpool_standard_error(zhp->zpool_hdl, err, msg);
+ retval = -1;
+ goto out;
+ }
+ }
+
+
+ if (trim_flags->wait)
+ retval = zpool_trim_wait(zhp, vdev_guids);
+
+out:
+ if (errlist != NULL)
+ fnvlist_free(errlist);
+ fnvlist_free(vdev_guids);
+ fnvlist_free(guids_to_paths);
+ return (retval);
+}
+
+/*
+ * Scan the pool.
+ */
+int
+zpool_scan(zpool_handle_t *zhp, pool_scan_func_t func, pool_scrub_cmd_t cmd)
+{
+ zfs_cmd_t zc = {"\0"};
+ char msg[1024];
+ int err;
+ libzfs_handle_t *hdl = zhp->zpool_hdl;
+
+ (void) strlcpy(zc.zc_name, zhp->zpool_name, sizeof (zc.zc_name));
+ zc.zc_cookie = func;
+ zc.zc_flags = cmd;
+
+ if (zfs_ioctl(hdl, ZFS_IOC_POOL_SCAN, &zc) == 0)
+ return (0);
+
+ err = errno;
+
+ /* ECANCELED on a scrub means we resumed a paused scrub */
+ if (err == ECANCELED && func == POOL_SCAN_SCRUB &&
+ cmd == POOL_SCRUB_NORMAL)
+ return (0);
+
+ if (err == ENOENT && func != POOL_SCAN_NONE && cmd == POOL_SCRUB_NORMAL)
+ return (0);
+
+ if (func == POOL_SCAN_SCRUB) {
+ if (cmd == POOL_SCRUB_PAUSE) {
+ (void) snprintf(msg, sizeof (msg), dgettext(TEXT_DOMAIN,
+ "cannot pause scrubbing %s"), zc.zc_name);
+ } else {
+ assert(cmd == POOL_SCRUB_NORMAL);
+ (void) snprintf(msg, sizeof (msg), dgettext(TEXT_DOMAIN,
+ "cannot scrub %s"), zc.zc_name);
+ }
+ } else if (func == POOL_SCAN_RESILVER) {
+ assert(cmd == POOL_SCRUB_NORMAL);
+ (void) snprintf(msg, sizeof (msg), dgettext(TEXT_DOMAIN,
+ "cannot restart resilver on %s"), zc.zc_name);
+ } else if (func == POOL_SCAN_NONE) {
+ (void) snprintf(msg, sizeof (msg),
+ dgettext(TEXT_DOMAIN, "cannot cancel scrubbing %s"),
+ zc.zc_name);
+ } else {
+ assert(!"unexpected result");
+ }
+
+ if (err == EBUSY) {
+ nvlist_t *nvroot;
+ pool_scan_stat_t *ps = NULL;
+ uint_t psc;
+
+ verify(nvlist_lookup_nvlist(zhp->zpool_config,
+ ZPOOL_CONFIG_VDEV_TREE, &nvroot) == 0);
+ (void) nvlist_lookup_uint64_array(nvroot,
+ ZPOOL_CONFIG_SCAN_STATS, (uint64_t **)&ps, &psc);
+ if (ps && ps->pss_func == POOL_SCAN_SCRUB &&
+ ps->pss_state == DSS_SCANNING) {
+ if (cmd == POOL_SCRUB_PAUSE)
+ return (zfs_error(hdl, EZFS_SCRUB_PAUSED, msg));
+ else
+ return (zfs_error(hdl, EZFS_SCRUBBING, msg));
+ } else {
+ return (zfs_error(hdl, EZFS_RESILVERING, msg));
+ }
+ } else if (err == ENOENT) {
+ return (zfs_error(hdl, EZFS_NO_SCRUB, msg));
+ } else if (err == ENOTSUP && func == POOL_SCAN_RESILVER) {
+ return (zfs_error(hdl, EZFS_NO_RESILVER_DEFER, msg));
+ } else {
+ return (zpool_standard_error(hdl, err, msg));
+ }
+}
+
+/*
+ * Find a vdev that matches the search criteria specified. We use the
+ * the nvpair name to determine how we should look for the device.
+ * 'avail_spare' is set to TRUE if the provided guid refers to an AVAIL
+ * spare; but FALSE if its an INUSE spare.
+ */
+static nvlist_t *
+vdev_to_nvlist_iter(nvlist_t *nv, nvlist_t *search, boolean_t *avail_spare,
+ boolean_t *l2cache, boolean_t *log)
+{
+ uint_t c, children;
+ nvlist_t **child;
+ nvlist_t *ret;
+ uint64_t is_log;
+ char *srchkey;
+ nvpair_t *pair = nvlist_next_nvpair(search, NULL);
+
+ /* Nothing to look for */
+ if (search == NULL || pair == NULL)
+ return (NULL);
+
+ /* Obtain the key we will use to search */
+ srchkey = nvpair_name(pair);
+
+ switch (nvpair_type(pair)) {
+ case DATA_TYPE_UINT64:
+ if (strcmp(srchkey, ZPOOL_CONFIG_GUID) == 0) {
+ uint64_t srchval, theguid;
+
+ verify(nvpair_value_uint64(pair, &srchval) == 0);
+ verify(nvlist_lookup_uint64(nv, ZPOOL_CONFIG_GUID,
+ &theguid) == 0);
+ if (theguid == srchval)
+ return (nv);
+ }
+ break;
+
+ case DATA_TYPE_STRING: {
+ char *srchval, *val;
+
+ verify(nvpair_value_string(pair, &srchval) == 0);
+ if (nvlist_lookup_string(nv, srchkey, &val) != 0)
+ break;
+
+ /*
+ * Search for the requested value. Special cases:
+ *
+ * - ZPOOL_CONFIG_PATH for whole disk entries. These end in
+ * "-part1", or "p1". The suffix is hidden from the user,
+ * but included in the string, so this matches around it.
+ * - ZPOOL_CONFIG_PATH for short names zfs_strcmp_shortname()
+ * is used to check all possible expanded paths.
+ * - looking for a top-level vdev name (i.e. ZPOOL_CONFIG_TYPE).
+ *
+ * Otherwise, all other searches are simple string compares.
+ */
+ if (strcmp(srchkey, ZPOOL_CONFIG_PATH) == 0) {
+ uint64_t wholedisk = 0;
+
+ (void) nvlist_lookup_uint64(nv, ZPOOL_CONFIG_WHOLE_DISK,
+ &wholedisk);
+ if (zfs_strcmp_pathname(srchval, val, wholedisk) == 0)
+ return (nv);
+
+ } else if (strcmp(srchkey, ZPOOL_CONFIG_TYPE) == 0 && val) {
+ char *type, *idx, *end, *p;
+ uint64_t id, vdev_id;
+
+ /*
+ * Determine our vdev type, keeping in mind
+ * that the srchval is composed of a type and
+ * vdev id pair (i.e. mirror-4).
+ */
+ if ((type = strdup(srchval)) == NULL)
+ return (NULL);
+
+ if ((p = strrchr(type, '-')) == NULL) {
+ free(type);
+ break;
+ }
+ idx = p + 1;
+ *p = '\0';
+
+ /*
+ * If the types don't match then keep looking.
+ */
+ if (strncmp(val, type, strlen(val)) != 0) {
+ free(type);
+ break;
+ }
+
+ verify(zpool_vdev_is_interior(type));
+ verify(nvlist_lookup_uint64(nv, ZPOOL_CONFIG_ID,
+ &id) == 0);
+
+ errno = 0;
+ vdev_id = strtoull(idx, &end, 10);
+
+ free(type);
+ if (errno != 0)
+ return (NULL);
+
+ /*
+ * Now verify that we have the correct vdev id.
+ */
+ if (vdev_id == id)
+ return (nv);
+ }
+
+ /*
+ * Common case
+ */
+ if (strcmp(srchval, val) == 0)
+ return (nv);
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ if (nvlist_lookup_nvlist_array(nv, ZPOOL_CONFIG_CHILDREN,
+ &child, &children) != 0)
+ return (NULL);
+
+ for (c = 0; c < children; c++) {
+ if ((ret = vdev_to_nvlist_iter(child[c], search,
+ avail_spare, l2cache, NULL)) != NULL) {
+ /*
+ * The 'is_log' value is only set for the toplevel
+ * vdev, not the leaf vdevs. So we always lookup the
+ * log device from the root of the vdev tree (where
+ * 'log' is non-NULL).
+ */
+ if (log != NULL &&
+ nvlist_lookup_uint64(child[c],
+ ZPOOL_CONFIG_IS_LOG, &is_log) == 0 &&
+ is_log) {
+ *log = B_TRUE;
+ }
+ return (ret);
+ }
+ }
+
+ if (nvlist_lookup_nvlist_array(nv, ZPOOL_CONFIG_SPARES,
+ &child, &children) == 0) {
+ for (c = 0; c < children; c++) {
+ if ((ret = vdev_to_nvlist_iter(child[c], search,
+ avail_spare, l2cache, NULL)) != NULL) {
+ *avail_spare = B_TRUE;
+ return (ret);
+ }
+ }
+ }
+
+ if (nvlist_lookup_nvlist_array(nv, ZPOOL_CONFIG_L2CACHE,
+ &child, &children) == 0) {
+ for (c = 0; c < children; c++) {
+ if ((ret = vdev_to_nvlist_iter(child[c], search,
+ avail_spare, l2cache, NULL)) != NULL) {
+ *l2cache = B_TRUE;
+ return (ret);
+ }
+ }
+ }
+
+ return (NULL);
+}
+
+/*
+ * Given a physical path or guid, find the associated vdev.
+ */
+nvlist_t *
+zpool_find_vdev_by_physpath(zpool_handle_t *zhp, const char *ppath,
+ boolean_t *avail_spare, boolean_t *l2cache, boolean_t *log)
+{
+ nvlist_t *search, *nvroot, *ret;
+ uint64_t guid;
+ char *end;
+
+ verify(nvlist_alloc(&search, NV_UNIQUE_NAME, KM_SLEEP) == 0);
+
+ guid = strtoull(ppath, &end, 0);
+ if (guid != 0 && *end == '\0') {
+ verify(nvlist_add_uint64(search, ZPOOL_CONFIG_GUID, guid) == 0);
+ } else {
+ verify(nvlist_add_string(search, ZPOOL_CONFIG_PHYS_PATH,
+ ppath) == 0);
+ }
+
+ verify(nvlist_lookup_nvlist(zhp->zpool_config, ZPOOL_CONFIG_VDEV_TREE,
+ &nvroot) == 0);
+
+ *avail_spare = B_FALSE;
+ *l2cache = B_FALSE;
+ if (log != NULL)
+ *log = B_FALSE;
+ ret = vdev_to_nvlist_iter(nvroot, search, avail_spare, l2cache, log);
+ nvlist_free(search);
+
+ return (ret);
+}
+
+/*
+ * Determine if we have an "interior" top-level vdev (i.e mirror/raidz).
+ */
+static boolean_t
+zpool_vdev_is_interior(const char *name)
+{
+ if (strncmp(name, VDEV_TYPE_RAIDZ, strlen(VDEV_TYPE_RAIDZ)) == 0 ||
+ strncmp(name, VDEV_TYPE_SPARE, strlen(VDEV_TYPE_SPARE)) == 0 ||
+ strncmp(name,
+ VDEV_TYPE_REPLACING, strlen(VDEV_TYPE_REPLACING)) == 0 ||
+ strncmp(name, VDEV_TYPE_MIRROR, strlen(VDEV_TYPE_MIRROR)) == 0)
+ return (B_TRUE);
+ return (B_FALSE);
+}
+
+nvlist_t *
+zpool_find_vdev(zpool_handle_t *zhp, const char *path, boolean_t *avail_spare,
+ boolean_t *l2cache, boolean_t *log)
+{
+ char *end;
+ nvlist_t *nvroot, *search, *ret;
+ uint64_t guid;
+
+ verify(nvlist_alloc(&search, NV_UNIQUE_NAME, KM_SLEEP) == 0);
+
+ guid = strtoull(path, &end, 0);
+ if (guid != 0 && *end == '\0') {
+ verify(nvlist_add_uint64(search, ZPOOL_CONFIG_GUID, guid) == 0);
+ } else if (zpool_vdev_is_interior(path)) {
+ verify(nvlist_add_string(search, ZPOOL_CONFIG_TYPE, path) == 0);
+ } else {
+ verify(nvlist_add_string(search, ZPOOL_CONFIG_PATH, path) == 0);
+ }
+
+ verify(nvlist_lookup_nvlist(zhp->zpool_config, ZPOOL_CONFIG_VDEV_TREE,
+ &nvroot) == 0);
+
+ *avail_spare = B_FALSE;
+ *l2cache = B_FALSE;
+ if (log != NULL)
+ *log = B_FALSE;
+ ret = vdev_to_nvlist_iter(nvroot, search, avail_spare, l2cache, log);
+ nvlist_free(search);
+
+ return (ret);
+}
+
+static int
+vdev_is_online(nvlist_t *nv)
+{
+ uint64_t ival;
+
+ if (nvlist_lookup_uint64(nv, ZPOOL_CONFIG_OFFLINE, &ival) == 0 ||
+ nvlist_lookup_uint64(nv, ZPOOL_CONFIG_FAULTED, &ival) == 0 ||
+ nvlist_lookup_uint64(nv, ZPOOL_CONFIG_REMOVED, &ival) == 0)
+ return (0);
+
+ return (1);
+}
+
+/*
+ * Helper function for zpool_get_physpaths().
+ */
+static int
+vdev_get_one_physpath(nvlist_t *config, char *physpath, size_t physpath_size,
+ size_t *bytes_written)
+{
+ size_t bytes_left, pos, rsz;
+ char *tmppath;
+ const char *format;
+
+ if (nvlist_lookup_string(config, ZPOOL_CONFIG_PHYS_PATH,
+ &tmppath) != 0)
+ return (EZFS_NODEVICE);
+
+ pos = *bytes_written;
+ bytes_left = physpath_size - pos;
+ format = (pos == 0) ? "%s" : " %s";
+
+ rsz = snprintf(physpath + pos, bytes_left, format, tmppath);
+ *bytes_written += rsz;
+
+ if (rsz >= bytes_left) {
+ /* if physpath was not copied properly, clear it */
+ if (bytes_left != 0) {
+ physpath[pos] = 0;
+ }
+ return (EZFS_NOSPC);
+ }
+ return (0);
+}
+
+static int
+vdev_get_physpaths(nvlist_t *nv, char *physpath, size_t phypath_size,
+ size_t *rsz, boolean_t is_spare)
+{
+ char *type;
+ int ret;
+
+ if (nvlist_lookup_string(nv, ZPOOL_CONFIG_TYPE, &type) != 0)
+ return (EZFS_INVALCONFIG);
+
+ if (strcmp(type, VDEV_TYPE_DISK) == 0) {
+ /*
+ * An active spare device has ZPOOL_CONFIG_IS_SPARE set.
+ * For a spare vdev, we only want to boot from the active
+ * spare device.
+ */
+ if (is_spare) {
+ uint64_t spare = 0;
+ (void) nvlist_lookup_uint64(nv, ZPOOL_CONFIG_IS_SPARE,
+ &spare);
+ if (!spare)
+ return (EZFS_INVALCONFIG);
+ }
+
+ if (vdev_is_online(nv)) {
+ if ((ret = vdev_get_one_physpath(nv, physpath,
+ phypath_size, rsz)) != 0)
+ return (ret);
+ }
+ } else if (strcmp(type, VDEV_TYPE_MIRROR) == 0 ||
+ strcmp(type, VDEV_TYPE_RAIDZ) == 0 ||
+ strcmp(type, VDEV_TYPE_REPLACING) == 0 ||
+ (is_spare = (strcmp(type, VDEV_TYPE_SPARE) == 0))) {
+ nvlist_t **child;
+ uint_t count;
+ int i, ret;
+
+ if (nvlist_lookup_nvlist_array(nv,
+ ZPOOL_CONFIG_CHILDREN, &child, &count) != 0)
+ return (EZFS_INVALCONFIG);
+
+ for (i = 0; i < count; i++) {
+ ret = vdev_get_physpaths(child[i], physpath,
+ phypath_size, rsz, is_spare);
+ if (ret == EZFS_NOSPC)
+ return (ret);
+ }
+ }
+
+ return (EZFS_POOL_INVALARG);
+}
+
+/*
+ * Get phys_path for a root pool config.
+ * Return 0 on success; non-zero on failure.
+ */
+static int
+zpool_get_config_physpath(nvlist_t *config, char *physpath, size_t phypath_size)
+{
+ size_t rsz;
+ nvlist_t *vdev_root;
+ nvlist_t **child;
+ uint_t count;
+ char *type;
+
+ rsz = 0;
+
+ if (nvlist_lookup_nvlist(config, ZPOOL_CONFIG_VDEV_TREE,
+ &vdev_root) != 0)
+ return (EZFS_INVALCONFIG);
+
+ if (nvlist_lookup_string(vdev_root, ZPOOL_CONFIG_TYPE, &type) != 0 ||
+ nvlist_lookup_nvlist_array(vdev_root, ZPOOL_CONFIG_CHILDREN,
+ &child, &count) != 0)
+ return (EZFS_INVALCONFIG);
+
+ /*
+ * root pool can only have a single top-level vdev.
+ */
+ if (strcmp(type, VDEV_TYPE_ROOT) != 0 || count != 1)
+ return (EZFS_POOL_INVALARG);
+
+ (void) vdev_get_physpaths(child[0], physpath, phypath_size, &rsz,
+ B_FALSE);
+
+ /* No online devices */
+ if (rsz == 0)
+ return (EZFS_NODEVICE);
+
+ return (0);
+}
+
+/*
+ * Get phys_path for a root pool
+ * Return 0 on success; non-zero on failure.
+ */
+int
+zpool_get_physpath(zpool_handle_t *zhp, char *physpath, size_t phypath_size)
+{
+ return (zpool_get_config_physpath(zhp->zpool_config, physpath,
+ phypath_size));
+}
+
+/*
+ * Convert a vdev path to a GUID. Returns GUID or 0 on error.
+ *
+ * If is_spare, is_l2cache, or is_log is non-NULL, then store within it
+ * if the VDEV is a spare, l2cache, or log device. If they're NULL then
+ * ignore them.
+ */
+static uint64_t
+zpool_vdev_path_to_guid_impl(zpool_handle_t *zhp, const char *path,
+ boolean_t *is_spare, boolean_t *is_l2cache, boolean_t *is_log)
+{
+ uint64_t guid;
+ boolean_t spare = B_FALSE, l2cache = B_FALSE, log = B_FALSE;
+ nvlist_t *tgt;
+
+ if ((tgt = zpool_find_vdev(zhp, path, &spare, &l2cache,
+ &log)) == NULL)
+ return (0);
+
+ verify(nvlist_lookup_uint64(tgt, ZPOOL_CONFIG_GUID, &guid) == 0);
+ if (is_spare != NULL)
+ *is_spare = spare;
+ if (is_l2cache != NULL)
+ *is_l2cache = l2cache;
+ if (is_log != NULL)
+ *is_log = log;
+
+ return (guid);
+}
+
+/* Convert a vdev path to a GUID. Returns GUID or 0 on error. */
+uint64_t
+zpool_vdev_path_to_guid(zpool_handle_t *zhp, const char *path)
+{
+ return (zpool_vdev_path_to_guid_impl(zhp, path, NULL, NULL, NULL));
+}
+
+/*
+ * Bring the specified vdev online. The 'flags' parameter is a set of the
+ * ZFS_ONLINE_* flags.
+ */
+int
+zpool_vdev_online(zpool_handle_t *zhp, const char *path, int flags,
+ vdev_state_t *newstate)
+{
+ zfs_cmd_t zc = {"\0"};
+ char msg[1024];
+ char *pathname;
+ nvlist_t *tgt;
+ boolean_t avail_spare, l2cache, islog;
+ libzfs_handle_t *hdl = zhp->zpool_hdl;
+ int error;
+
+ if (flags & ZFS_ONLINE_EXPAND) {
+ (void) snprintf(msg, sizeof (msg),
+ dgettext(TEXT_DOMAIN, "cannot expand %s"), path);
+ } else {
+ (void) snprintf(msg, sizeof (msg),
+ dgettext(TEXT_DOMAIN, "cannot online %s"), path);
+ }
+
+ (void) strlcpy(zc.zc_name, zhp->zpool_name, sizeof (zc.zc_name));
+ if ((tgt = zpool_find_vdev(zhp, path, &avail_spare, &l2cache,
+ &islog)) == NULL)
+ return (zfs_error(hdl, EZFS_NODEVICE, msg));
+
+ verify(nvlist_lookup_uint64(tgt, ZPOOL_CONFIG_GUID, &zc.zc_guid) == 0);
+
+ if (avail_spare)
+ return (zfs_error(hdl, EZFS_ISSPARE, msg));
+
+ if ((flags & ZFS_ONLINE_EXPAND ||
+ zpool_get_prop_int(zhp, ZPOOL_PROP_AUTOEXPAND, NULL)) &&
+ nvlist_lookup_string(tgt, ZPOOL_CONFIG_PATH, &pathname) == 0) {
+ uint64_t wholedisk = 0;
+
+ (void) nvlist_lookup_uint64(tgt, ZPOOL_CONFIG_WHOLE_DISK,
+ &wholedisk);
+
+ /*
+ * XXX - L2ARC 1.0 devices can't support expansion.
+ */
+ if (l2cache) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "cannot expand cache devices"));
+ return (zfs_error(hdl, EZFS_VDEVNOTSUP, msg));
+ }
+
+ if (wholedisk) {
+ const char *fullpath = path;
+ char buf[MAXPATHLEN];
+
+ if (path[0] != '/') {
+ error = zfs_resolve_shortname(path, buf,
+ sizeof (buf));
+ if (error != 0)
+ return (zfs_error(hdl, EZFS_NODEVICE,
+ msg));
+
+ fullpath = buf;
+ }
+
+ error = zpool_relabel_disk(hdl, fullpath, msg);
+ if (error != 0)
+ return (error);
+ }
+ }
+
+ zc.zc_cookie = VDEV_STATE_ONLINE;
+ zc.zc_obj = flags;
+
+ if (zfs_ioctl(hdl, ZFS_IOC_VDEV_SET_STATE, &zc) != 0) {
+ if (errno == EINVAL) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "was split "
+ "from this pool into a new one. Use '%s' "
+ "instead"), "zpool detach");
+ return (zfs_error(hdl, EZFS_POSTSPLIT_ONLINE, msg));
+ }
+ return (zpool_standard_error(hdl, errno, msg));
+ }
+
+ *newstate = zc.zc_cookie;
+ return (0);
+}
+
+/*
+ * Take the specified vdev offline
+ */
+int
+zpool_vdev_offline(zpool_handle_t *zhp, const char *path, boolean_t istmp)
+{
+ zfs_cmd_t zc = {"\0"};
+ char msg[1024];
+ nvlist_t *tgt;
+ boolean_t avail_spare, l2cache;
+ libzfs_handle_t *hdl = zhp->zpool_hdl;
+
+ (void) snprintf(msg, sizeof (msg),
+ dgettext(TEXT_DOMAIN, "cannot offline %s"), path);
+
+ (void) strlcpy(zc.zc_name, zhp->zpool_name, sizeof (zc.zc_name));
+ if ((tgt = zpool_find_vdev(zhp, path, &avail_spare, &l2cache,
+ NULL)) == NULL)
+ return (zfs_error(hdl, EZFS_NODEVICE, msg));
+
+ verify(nvlist_lookup_uint64(tgt, ZPOOL_CONFIG_GUID, &zc.zc_guid) == 0);
+
+ if (avail_spare)
+ return (zfs_error(hdl, EZFS_ISSPARE, msg));
+
+ zc.zc_cookie = VDEV_STATE_OFFLINE;
+ zc.zc_obj = istmp ? ZFS_OFFLINE_TEMPORARY : 0;
+
+ if (zfs_ioctl(hdl, ZFS_IOC_VDEV_SET_STATE, &zc) == 0)
+ return (0);
+
+ switch (errno) {
+ case EBUSY:
+
+ /*
+ * There are no other replicas of this device.
+ */
+ return (zfs_error(hdl, EZFS_NOREPLICAS, msg));
+
+ case EEXIST:
+ /*
+ * The log device has unplayed logs
+ */
+ return (zfs_error(hdl, EZFS_UNPLAYED_LOGS, msg));
+
+ default:
+ return (zpool_standard_error(hdl, errno, msg));
+ }
+}
+
+/*
+ * Mark the given vdev faulted.
+ */
+int
+zpool_vdev_fault(zpool_handle_t *zhp, uint64_t guid, vdev_aux_t aux)
+{
+ zfs_cmd_t zc = {"\0"};
+ char msg[1024];
+ libzfs_handle_t *hdl = zhp->zpool_hdl;
+
+ (void) snprintf(msg, sizeof (msg),
+ dgettext(TEXT_DOMAIN, "cannot fault %llu"), (u_longlong_t)guid);
+
+ (void) strlcpy(zc.zc_name, zhp->zpool_name, sizeof (zc.zc_name));
+ zc.zc_guid = guid;
+ zc.zc_cookie = VDEV_STATE_FAULTED;
+ zc.zc_obj = aux;
+
+ if (zfs_ioctl(hdl, ZFS_IOC_VDEV_SET_STATE, &zc) == 0)
+ return (0);
+
+ switch (errno) {
+ case EBUSY:
+
+ /*
+ * There are no other replicas of this device.
+ */
+ return (zfs_error(hdl, EZFS_NOREPLICAS, msg));
+
+ default:
+ return (zpool_standard_error(hdl, errno, msg));
+ }
+
+}
+
+/*
+ * Mark the given vdev degraded.
+ */
+int
+zpool_vdev_degrade(zpool_handle_t *zhp, uint64_t guid, vdev_aux_t aux)
+{
+ zfs_cmd_t zc = {"\0"};
+ char msg[1024];
+ libzfs_handle_t *hdl = zhp->zpool_hdl;
+
+ (void) snprintf(msg, sizeof (msg),
+ dgettext(TEXT_DOMAIN, "cannot degrade %llu"), (u_longlong_t)guid);
+
+ (void) strlcpy(zc.zc_name, zhp->zpool_name, sizeof (zc.zc_name));
+ zc.zc_guid = guid;
+ zc.zc_cookie = VDEV_STATE_DEGRADED;
+ zc.zc_obj = aux;
+
+ if (zfs_ioctl(hdl, ZFS_IOC_VDEV_SET_STATE, &zc) == 0)
+ return (0);
+
+ return (zpool_standard_error(hdl, errno, msg));
+}
+
+/*
+ * Returns TRUE if the given nvlist is a vdev that was originally swapped in as
+ * a hot spare.
+ */
+static boolean_t
+is_replacing_spare(nvlist_t *search, nvlist_t *tgt, int which)
+{
+ nvlist_t **child;
+ uint_t c, children;
+ char *type;
+
+ if (nvlist_lookup_nvlist_array(search, ZPOOL_CONFIG_CHILDREN, &child,
+ &children) == 0) {
+ verify(nvlist_lookup_string(search, ZPOOL_CONFIG_TYPE,
+ &type) == 0);
+
+ if (strcmp(type, VDEV_TYPE_SPARE) == 0 &&
+ children == 2 && child[which] == tgt)
+ return (B_TRUE);
+
+ for (c = 0; c < children; c++)
+ if (is_replacing_spare(child[c], tgt, which))
+ return (B_TRUE);
+ }
+
+ return (B_FALSE);
+}
+
+/*
+ * Attach new_disk (fully described by nvroot) to old_disk.
+ * If 'replacing' is specified, the new disk will replace the old one.
+ */
+int
+zpool_vdev_attach(zpool_handle_t *zhp, const char *old_disk,
+ const char *new_disk, nvlist_t *nvroot, int replacing, boolean_t rebuild)
+{
+ zfs_cmd_t zc = {"\0"};
+ char msg[1024];
+ int ret;
+ nvlist_t *tgt;
+ boolean_t avail_spare, l2cache, islog;
+ uint64_t val;
+ char *newname;
+ nvlist_t **child;
+ uint_t children;
+ nvlist_t *config_root;
+ libzfs_handle_t *hdl = zhp->zpool_hdl;
+
+ if (replacing)
+ (void) snprintf(msg, sizeof (msg), dgettext(TEXT_DOMAIN,
+ "cannot replace %s with %s"), old_disk, new_disk);
+ else
+ (void) snprintf(msg, sizeof (msg), dgettext(TEXT_DOMAIN,
+ "cannot attach %s to %s"), new_disk, old_disk);
+
+ (void) strlcpy(zc.zc_name, zhp->zpool_name, sizeof (zc.zc_name));
+ if ((tgt = zpool_find_vdev(zhp, old_disk, &avail_spare, &l2cache,
+ &islog)) == NULL)
+ return (zfs_error(hdl, EZFS_NODEVICE, msg));
+
+ if (avail_spare)
+ return (zfs_error(hdl, EZFS_ISSPARE, msg));
+
+ if (l2cache)
+ return (zfs_error(hdl, EZFS_ISL2CACHE, msg));
+
+ verify(nvlist_lookup_uint64(tgt, ZPOOL_CONFIG_GUID, &zc.zc_guid) == 0);
+ zc.zc_cookie = replacing;
+ zc.zc_simple = rebuild;
+
+ if (rebuild &&
+ zfeature_lookup_guid("org.openzfs:device_rebuild", NULL) != 0) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "the loaded zfs module doesn't support device rebuilds"));
+ return (zfs_error(hdl, EZFS_POOL_NOTSUP, msg));
+ }
+
+ if (nvlist_lookup_nvlist_array(nvroot, ZPOOL_CONFIG_CHILDREN,
+ &child, &children) != 0 || children != 1) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "new device must be a single disk"));
+ return (zfs_error(hdl, EZFS_INVALCONFIG, msg));
+ }
+
+ verify(nvlist_lookup_nvlist(zpool_get_config(zhp, NULL),
+ ZPOOL_CONFIG_VDEV_TREE, &config_root) == 0);
+
+ if ((newname = zpool_vdev_name(NULL, NULL, child[0], 0)) == NULL)
+ return (-1);
+
+ /*
+ * If the target is a hot spare that has been swapped in, we can only
+ * replace it with another hot spare.
+ */
+ if (replacing &&
+ nvlist_lookup_uint64(tgt, ZPOOL_CONFIG_IS_SPARE, &val) == 0 &&
+ (zpool_find_vdev(zhp, newname, &avail_spare, &l2cache,
+ NULL) == NULL || !avail_spare) &&
+ is_replacing_spare(config_root, tgt, 1)) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "can only be replaced by another hot spare"));
+ free(newname);
+ return (zfs_error(hdl, EZFS_BADTARGET, msg));
+ }
+
+ free(newname);
+
+ if (zcmd_write_conf_nvlist(hdl, &zc, nvroot) != 0)
+ return (-1);
+
+ ret = zfs_ioctl(hdl, ZFS_IOC_VDEV_ATTACH, &zc);
+
+ zcmd_free_nvlists(&zc);
+
+ if (ret == 0)
+ return (0);
+
+ switch (errno) {
+ case ENOTSUP:
+ /*
+ * Can't attach to or replace this type of vdev.
+ */
+ if (replacing) {
+ uint64_t version = zpool_get_prop_int(zhp,
+ ZPOOL_PROP_VERSION, NULL);
+
+ if (islog) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "cannot replace a log with a spare"));
+ } else if (rebuild) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "only mirror vdevs support sequential "
+ "reconstruction"));
+ } else if (version >= SPA_VERSION_MULTI_REPLACE) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "already in replacing/spare config; wait "
+ "for completion or use 'zpool detach'"));
+ } else {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "cannot replace a replacing device"));
+ }
+ } else {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "can only attach to mirrors and top-level "
+ "disks"));
+ }
+ (void) zfs_error(hdl, EZFS_BADTARGET, msg);
+ break;
+
+ case EINVAL:
+ /*
+ * The new device must be a single disk.
+ */
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "new device must be a single disk"));
+ (void) zfs_error(hdl, EZFS_INVALCONFIG, msg);
+ break;
+
+ case EBUSY:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "%s is busy, "
+ "or device removal is in progress"),
+ new_disk);
+ (void) zfs_error(hdl, EZFS_BADDEV, msg);
+ break;
+
+ case EOVERFLOW:
+ /*
+ * The new device is too small.
+ */
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "device is too small"));
+ (void) zfs_error(hdl, EZFS_BADDEV, msg);
+ break;
+
+ case EDOM:
+ /*
+ * The new device has a different optimal sector size.
+ */
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "new device has a different optimal sector size; use the "
+ "option '-o ashift=N' to override the optimal size"));
+ (void) zfs_error(hdl, EZFS_BADDEV, msg);
+ break;
+
+ case ENAMETOOLONG:
+ /*
+ * The resulting top-level vdev spec won't fit in the label.
+ */
+ (void) zfs_error(hdl, EZFS_DEVOVERFLOW, msg);
+ break;
+
+ default:
+ (void) zpool_standard_error(hdl, errno, msg);
+ }
+
+ return (-1);
+}
+
+/*
+ * Detach the specified device.
+ */
+int
+zpool_vdev_detach(zpool_handle_t *zhp, const char *path)
+{
+ zfs_cmd_t zc = {"\0"};
+ char msg[1024];
+ nvlist_t *tgt;
+ boolean_t avail_spare, l2cache;
+ libzfs_handle_t *hdl = zhp->zpool_hdl;
+
+ (void) snprintf(msg, sizeof (msg),
+ dgettext(TEXT_DOMAIN, "cannot detach %s"), path);
+
+ (void) strlcpy(zc.zc_name, zhp->zpool_name, sizeof (zc.zc_name));
+ if ((tgt = zpool_find_vdev(zhp, path, &avail_spare, &l2cache,
+ NULL)) == NULL)
+ return (zfs_error(hdl, EZFS_NODEVICE, msg));
+
+ if (avail_spare)
+ return (zfs_error(hdl, EZFS_ISSPARE, msg));
+
+ if (l2cache)
+ return (zfs_error(hdl, EZFS_ISL2CACHE, msg));
+
+ verify(nvlist_lookup_uint64(tgt, ZPOOL_CONFIG_GUID, &zc.zc_guid) == 0);
+
+ if (zfs_ioctl(hdl, ZFS_IOC_VDEV_DETACH, &zc) == 0)
+ return (0);
+
+ switch (errno) {
+
+ case ENOTSUP:
+ /*
+ * Can't detach from this type of vdev.
+ */
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "only "
+ "applicable to mirror and replacing vdevs"));
+ (void) zfs_error(hdl, EZFS_BADTARGET, msg);
+ break;
+
+ case EBUSY:
+ /*
+ * There are no other replicas of this device.
+ */
+ (void) zfs_error(hdl, EZFS_NOREPLICAS, msg);
+ break;
+
+ default:
+ (void) zpool_standard_error(hdl, errno, msg);
+ }
+
+ return (-1);
+}
+
+/*
+ * Find a mirror vdev in the source nvlist.
+ *
+ * The mchild array contains a list of disks in one of the top-level mirrors
+ * of the source pool. The schild array contains a list of disks that the
+ * user specified on the command line. We loop over the mchild array to
+ * see if any entry in the schild array matches.
+ *
+ * If a disk in the mchild array is found in the schild array, we return
+ * the index of that entry. Otherwise we return -1.
+ */
+static int
+find_vdev_entry(zpool_handle_t *zhp, nvlist_t **mchild, uint_t mchildren,
+ nvlist_t **schild, uint_t schildren)
+{
+ uint_t mc;
+
+ for (mc = 0; mc < mchildren; mc++) {
+ uint_t sc;
+ char *mpath = zpool_vdev_name(zhp->zpool_hdl, zhp,
+ mchild[mc], 0);
+
+ for (sc = 0; sc < schildren; sc++) {
+ char *spath = zpool_vdev_name(zhp->zpool_hdl, zhp,
+ schild[sc], 0);
+ boolean_t result = (strcmp(mpath, spath) == 0);
+
+ free(spath);
+ if (result) {
+ free(mpath);
+ return (mc);
+ }
+ }
+
+ free(mpath);
+ }
+
+ return (-1);
+}
+
+/*
+ * Split a mirror pool. If newroot points to null, then a new nvlist
+ * is generated and it is the responsibility of the caller to free it.
+ */
+int
+zpool_vdev_split(zpool_handle_t *zhp, char *newname, nvlist_t **newroot,
+ nvlist_t *props, splitflags_t flags)
+{
+ zfs_cmd_t zc = {"\0"};
+ char msg[1024];
+ nvlist_t *tree, *config, **child, **newchild, *newconfig = NULL;
+ nvlist_t **varray = NULL, *zc_props = NULL;
+ uint_t c, children, newchildren, lastlog = 0, vcount, found = 0;
+ libzfs_handle_t *hdl = zhp->zpool_hdl;
+ uint64_t vers, readonly = B_FALSE;
+ boolean_t freelist = B_FALSE, memory_err = B_TRUE;
+ int retval = 0;
+
+ (void) snprintf(msg, sizeof (msg),
+ dgettext(TEXT_DOMAIN, "Unable to split %s"), zhp->zpool_name);
+
+ if (!zpool_name_valid(hdl, B_FALSE, newname))
+ return (zfs_error(hdl, EZFS_INVALIDNAME, msg));
+
+ if ((config = zpool_get_config(zhp, NULL)) == NULL) {
+ (void) fprintf(stderr, gettext("Internal error: unable to "
+ "retrieve pool configuration\n"));
+ return (-1);
+ }
+
+ verify(nvlist_lookup_nvlist(config, ZPOOL_CONFIG_VDEV_TREE, &tree)
+ == 0);
+ verify(nvlist_lookup_uint64(config, ZPOOL_CONFIG_VERSION, &vers) == 0);
+
+ if (props) {
+ prop_flags_t flags = { .create = B_FALSE, .import = B_TRUE };
+ if ((zc_props = zpool_valid_proplist(hdl, zhp->zpool_name,
+ props, vers, flags, msg)) == NULL)
+ return (-1);
+ (void) nvlist_lookup_uint64(zc_props,
+ zpool_prop_to_name(ZPOOL_PROP_READONLY), &readonly);
+ if (readonly) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "property %s can only be set at import time"),
+ zpool_prop_to_name(ZPOOL_PROP_READONLY));
+ return (-1);
+ }
+ }
+
+ if (nvlist_lookup_nvlist_array(tree, ZPOOL_CONFIG_CHILDREN, &child,
+ &children) != 0) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Source pool is missing vdev tree"));
+ nvlist_free(zc_props);
+ return (-1);
+ }
+
+ varray = zfs_alloc(hdl, children * sizeof (nvlist_t *));
+ vcount = 0;
+
+ if (*newroot == NULL ||
+ nvlist_lookup_nvlist_array(*newroot, ZPOOL_CONFIG_CHILDREN,
+ &newchild, &newchildren) != 0)
+ newchildren = 0;
+
+ for (c = 0; c < children; c++) {
+ uint64_t is_log = B_FALSE, is_hole = B_FALSE;
+ char *type;
+ nvlist_t **mchild, *vdev;
+ uint_t mchildren;
+ int entry;
+
+ /*
+ * Unlike cache & spares, slogs are stored in the
+ * ZPOOL_CONFIG_CHILDREN array. We filter them out here.
+ */
+ (void) nvlist_lookup_uint64(child[c], ZPOOL_CONFIG_IS_LOG,
+ &is_log);
+ (void) nvlist_lookup_uint64(child[c], ZPOOL_CONFIG_IS_HOLE,
+ &is_hole);
+ if (is_log || is_hole) {
+ /*
+ * Create a hole vdev and put it in the config.
+ */
+ if (nvlist_alloc(&vdev, NV_UNIQUE_NAME, 0) != 0)
+ goto out;
+ if (nvlist_add_string(vdev, ZPOOL_CONFIG_TYPE,
+ VDEV_TYPE_HOLE) != 0)
+ goto out;
+ if (nvlist_add_uint64(vdev, ZPOOL_CONFIG_IS_HOLE,
+ 1) != 0)
+ goto out;
+ if (lastlog == 0)
+ lastlog = vcount;
+ varray[vcount++] = vdev;
+ continue;
+ }
+ lastlog = 0;
+ verify(nvlist_lookup_string(child[c], ZPOOL_CONFIG_TYPE, &type)
+ == 0);
+
+ if (strcmp(type, VDEV_TYPE_INDIRECT) == 0) {
+ vdev = child[c];
+ if (nvlist_dup(vdev, &varray[vcount++], 0) != 0)
+ goto out;
+ continue;
+ } else if (strcmp(type, VDEV_TYPE_MIRROR) != 0) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Source pool must be composed only of mirrors\n"));
+ retval = zfs_error(hdl, EZFS_INVALCONFIG, msg);
+ goto out;
+ }
+
+ verify(nvlist_lookup_nvlist_array(child[c],
+ ZPOOL_CONFIG_CHILDREN, &mchild, &mchildren) == 0);
+
+ /* find or add an entry for this top-level vdev */
+ if (newchildren > 0 &&
+ (entry = find_vdev_entry(zhp, mchild, mchildren,
+ newchild, newchildren)) >= 0) {
+ /* We found a disk that the user specified. */
+ vdev = mchild[entry];
+ ++found;
+ } else {
+ /* User didn't specify a disk for this vdev. */
+ vdev = mchild[mchildren - 1];
+ }
+
+ if (nvlist_dup(vdev, &varray[vcount++], 0) != 0)
+ goto out;
+ }
+
+ /* did we find every disk the user specified? */
+ if (found != newchildren) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "Device list must "
+ "include at most one disk from each mirror"));
+ retval = zfs_error(hdl, EZFS_INVALCONFIG, msg);
+ goto out;
+ }
+
+ /* Prepare the nvlist for populating. */
+ if (*newroot == NULL) {
+ if (nvlist_alloc(newroot, NV_UNIQUE_NAME, 0) != 0)
+ goto out;
+ freelist = B_TRUE;
+ if (nvlist_add_string(*newroot, ZPOOL_CONFIG_TYPE,
+ VDEV_TYPE_ROOT) != 0)
+ goto out;
+ } else {
+ verify(nvlist_remove_all(*newroot, ZPOOL_CONFIG_CHILDREN) == 0);
+ }
+
+ /* Add all the children we found */
+ if (nvlist_add_nvlist_array(*newroot, ZPOOL_CONFIG_CHILDREN, varray,
+ lastlog == 0 ? vcount : lastlog) != 0)
+ goto out;
+
+ /*
+ * If we're just doing a dry run, exit now with success.
+ */
+ if (flags.dryrun) {
+ memory_err = B_FALSE;
+ freelist = B_FALSE;
+ goto out;
+ }
+
+ /* now build up the config list & call the ioctl */
+ if (nvlist_alloc(&newconfig, NV_UNIQUE_NAME, 0) != 0)
+ goto out;
+
+ if (nvlist_add_nvlist(newconfig,
+ ZPOOL_CONFIG_VDEV_TREE, *newroot) != 0 ||
+ nvlist_add_string(newconfig,
+ ZPOOL_CONFIG_POOL_NAME, newname) != 0 ||
+ nvlist_add_uint64(newconfig, ZPOOL_CONFIG_VERSION, vers) != 0)
+ goto out;
+
+ /*
+ * The new pool is automatically part of the namespace unless we
+ * explicitly export it.
+ */
+ if (!flags.import)
+ zc.zc_cookie = ZPOOL_EXPORT_AFTER_SPLIT;
+ (void) strlcpy(zc.zc_name, zhp->zpool_name, sizeof (zc.zc_name));
+ (void) strlcpy(zc.zc_string, newname, sizeof (zc.zc_string));
+ if (zcmd_write_conf_nvlist(hdl, &zc, newconfig) != 0)
+ goto out;
+ if (zc_props != NULL && zcmd_write_src_nvlist(hdl, &zc, zc_props) != 0)
+ goto out;
+
+ if (zfs_ioctl(hdl, ZFS_IOC_VDEV_SPLIT, &zc) != 0) {
+ retval = zpool_standard_error(hdl, errno, msg);
+ goto out;
+ }
+
+ freelist = B_FALSE;
+ memory_err = B_FALSE;
+
+out:
+ if (varray != NULL) {
+ int v;
+
+ for (v = 0; v < vcount; v++)
+ nvlist_free(varray[v]);
+ free(varray);
+ }
+ zcmd_free_nvlists(&zc);
+ nvlist_free(zc_props);
+ nvlist_free(newconfig);
+ if (freelist) {
+ nvlist_free(*newroot);
+ *newroot = NULL;
+ }
+
+ if (retval != 0)
+ return (retval);
+
+ if (memory_err)
+ return (no_memory(hdl));
+
+ return (0);
+}
+
+/*
+ * Remove the given device.
+ */
+int
+zpool_vdev_remove(zpool_handle_t *zhp, const char *path)
+{
+ zfs_cmd_t zc = {"\0"};
+ char msg[1024];
+ nvlist_t *tgt;
+ boolean_t avail_spare, l2cache, islog;
+ libzfs_handle_t *hdl = zhp->zpool_hdl;
+ uint64_t version;
+
+ (void) snprintf(msg, sizeof (msg),
+ dgettext(TEXT_DOMAIN, "cannot remove %s"), path);
+
+ (void) strlcpy(zc.zc_name, zhp->zpool_name, sizeof (zc.zc_name));
+ if ((tgt = zpool_find_vdev(zhp, path, &avail_spare, &l2cache,
+ &islog)) == NULL)
+ return (zfs_error(hdl, EZFS_NODEVICE, msg));
+
+ version = zpool_get_prop_int(zhp, ZPOOL_PROP_VERSION, NULL);
+ if (islog && version < SPA_VERSION_HOLES) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "pool must be upgraded to support log removal"));
+ return (zfs_error(hdl, EZFS_BADVERSION, msg));
+ }
+
+ zc.zc_guid = fnvlist_lookup_uint64(tgt, ZPOOL_CONFIG_GUID);
+
+ if (zfs_ioctl(hdl, ZFS_IOC_VDEV_REMOVE, &zc) == 0)
+ return (0);
+
+ switch (errno) {
+
+ case EINVAL:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "invalid config; all top-level vdevs must "
+ "have the same sector size and not be raidz."));
+ (void) zfs_error(hdl, EZFS_INVALCONFIG, msg);
+ break;
+
+ case EBUSY:
+ if (islog) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Mount encrypted datasets to replay logs."));
+ } else {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Pool busy; removal may already be in progress"));
+ }
+ (void) zfs_error(hdl, EZFS_BUSY, msg);
+ break;
+
+ case EACCES:
+ if (islog) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Mount encrypted datasets to replay logs."));
+ (void) zfs_error(hdl, EZFS_BUSY, msg);
+ } else {
+ (void) zpool_standard_error(hdl, errno, msg);
+ }
+ break;
+
+ default:
+ (void) zpool_standard_error(hdl, errno, msg);
+ }
+ return (-1);
+}
+
+int
+zpool_vdev_remove_cancel(zpool_handle_t *zhp)
+{
+ zfs_cmd_t zc;
+ char msg[1024];
+ libzfs_handle_t *hdl = zhp->zpool_hdl;
+
+ (void) snprintf(msg, sizeof (msg),
+ dgettext(TEXT_DOMAIN, "cannot cancel removal"));
+
+ bzero(&zc, sizeof (zc));
+ (void) strlcpy(zc.zc_name, zhp->zpool_name, sizeof (zc.zc_name));
+ zc.zc_cookie = 1;
+
+ if (zfs_ioctl(hdl, ZFS_IOC_VDEV_REMOVE, &zc) == 0)
+ return (0);
+
+ return (zpool_standard_error(hdl, errno, msg));
+}
+
+int
+zpool_vdev_indirect_size(zpool_handle_t *zhp, const char *path,
+ uint64_t *sizep)
+{
+ char msg[1024];
+ nvlist_t *tgt;
+ boolean_t avail_spare, l2cache, islog;
+ libzfs_handle_t *hdl = zhp->zpool_hdl;
+
+ (void) snprintf(msg, sizeof (msg),
+ dgettext(TEXT_DOMAIN, "cannot determine indirect size of %s"),
+ path);
+
+ if ((tgt = zpool_find_vdev(zhp, path, &avail_spare, &l2cache,
+ &islog)) == NULL)
+ return (zfs_error(hdl, EZFS_NODEVICE, msg));
+
+ if (avail_spare || l2cache || islog) {
+ *sizep = 0;
+ return (0);
+ }
+
+ if (nvlist_lookup_uint64(tgt, ZPOOL_CONFIG_INDIRECT_SIZE, sizep) != 0) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "indirect size not available"));
+ return (zfs_error(hdl, EINVAL, msg));
+ }
+ return (0);
+}
+
+/*
+ * Clear the errors for the pool, or the particular device if specified.
+ */
+int
+zpool_clear(zpool_handle_t *zhp, const char *path, nvlist_t *rewindnvl)
+{
+ zfs_cmd_t zc = {"\0"};
+ char msg[1024];
+ nvlist_t *tgt;
+ zpool_load_policy_t policy;
+ boolean_t avail_spare, l2cache;
+ libzfs_handle_t *hdl = zhp->zpool_hdl;
+ nvlist_t *nvi = NULL;
+ int error;
+
+ if (path)
+ (void) snprintf(msg, sizeof (msg),
+ dgettext(TEXT_DOMAIN, "cannot clear errors for %s"),
+ path);
+ else
+ (void) snprintf(msg, sizeof (msg),
+ dgettext(TEXT_DOMAIN, "cannot clear errors for %s"),
+ zhp->zpool_name);
+
+ (void) strlcpy(zc.zc_name, zhp->zpool_name, sizeof (zc.zc_name));
+ if (path) {
+ if ((tgt = zpool_find_vdev(zhp, path, &avail_spare,
+ &l2cache, NULL)) == NULL)
+ return (zfs_error(hdl, EZFS_NODEVICE, msg));
+
+ /*
+ * Don't allow error clearing for hot spares. Do allow
+ * error clearing for l2cache devices.
+ */
+ if (avail_spare)
+ return (zfs_error(hdl, EZFS_ISSPARE, msg));
+
+ verify(nvlist_lookup_uint64(tgt, ZPOOL_CONFIG_GUID,
+ &zc.zc_guid) == 0);
+ }
+
+ zpool_get_load_policy(rewindnvl, &policy);
+ zc.zc_cookie = policy.zlp_rewind;
+
+ if (zcmd_alloc_dst_nvlist(hdl, &zc, zhp->zpool_config_size * 2) != 0)
+ return (-1);
+
+ if (zcmd_write_src_nvlist(hdl, &zc, rewindnvl) != 0)
+ return (-1);
+
+ while ((error = zfs_ioctl(hdl, ZFS_IOC_CLEAR, &zc)) != 0 &&
+ errno == ENOMEM) {
+ if (zcmd_expand_dst_nvlist(hdl, &zc) != 0) {
+ zcmd_free_nvlists(&zc);
+ return (-1);
+ }
+ }
+
+ if (!error || ((policy.zlp_rewind & ZPOOL_TRY_REWIND) &&
+ errno != EPERM && errno != EACCES)) {
+ if (policy.zlp_rewind &
+ (ZPOOL_DO_REWIND | ZPOOL_TRY_REWIND)) {
+ (void) zcmd_read_dst_nvlist(hdl, &zc, &nvi);
+ zpool_rewind_exclaim(hdl, zc.zc_name,
+ ((policy.zlp_rewind & ZPOOL_TRY_REWIND) != 0),
+ nvi);
+ nvlist_free(nvi);
+ }
+ zcmd_free_nvlists(&zc);
+ return (0);
+ }
+
+ zcmd_free_nvlists(&zc);
+ return (zpool_standard_error(hdl, errno, msg));
+}
+
+/*
+ * Similar to zpool_clear(), but takes a GUID (used by fmd).
+ */
+int
+zpool_vdev_clear(zpool_handle_t *zhp, uint64_t guid)
+{
+ zfs_cmd_t zc = {"\0"};
+ char msg[1024];
+ libzfs_handle_t *hdl = zhp->zpool_hdl;
+
+ (void) snprintf(msg, sizeof (msg),
+ dgettext(TEXT_DOMAIN, "cannot clear errors for %llx"),
+ (u_longlong_t)guid);
+
+ (void) strlcpy(zc.zc_name, zhp->zpool_name, sizeof (zc.zc_name));
+ zc.zc_guid = guid;
+ zc.zc_cookie = ZPOOL_NO_REWIND;
+
+ if (zfs_ioctl(hdl, ZFS_IOC_CLEAR, &zc) == 0)
+ return (0);
+
+ return (zpool_standard_error(hdl, errno, msg));
+}
+
+/*
+ * Change the GUID for a pool.
+ */
+int
+zpool_reguid(zpool_handle_t *zhp)
+{
+ char msg[1024];
+ libzfs_handle_t *hdl = zhp->zpool_hdl;
+ zfs_cmd_t zc = {"\0"};
+
+ (void) snprintf(msg, sizeof (msg),
+ dgettext(TEXT_DOMAIN, "cannot reguid '%s'"), zhp->zpool_name);
+
+ (void) strlcpy(zc.zc_name, zhp->zpool_name, sizeof (zc.zc_name));
+ if (zfs_ioctl(hdl, ZFS_IOC_POOL_REGUID, &zc) == 0)
+ return (0);
+
+ return (zpool_standard_error(hdl, errno, msg));
+}
+
+/*
+ * Reopen the pool.
+ */
+int
+zpool_reopen_one(zpool_handle_t *zhp, void *data)
+{
+ libzfs_handle_t *hdl = zpool_get_handle(zhp);
+ const char *pool_name = zpool_get_name(zhp);
+ boolean_t *scrub_restart = data;
+ int error;
+
+ error = lzc_reopen(pool_name, *scrub_restart);
+ if (error) {
+ return (zpool_standard_error_fmt(hdl, error,
+ dgettext(TEXT_DOMAIN, "cannot reopen '%s'"), pool_name));
+ }
+
+ return (0);
+}
+
+/* call into libzfs_core to execute the sync IOCTL per pool */
+int
+zpool_sync_one(zpool_handle_t *zhp, void *data)
+{
+ int ret;
+ libzfs_handle_t *hdl = zpool_get_handle(zhp);
+ const char *pool_name = zpool_get_name(zhp);
+ boolean_t *force = data;
+ nvlist_t *innvl = fnvlist_alloc();
+
+ fnvlist_add_boolean_value(innvl, "force", *force);
+ if ((ret = lzc_sync(pool_name, innvl, NULL)) != 0) {
+ nvlist_free(innvl);
+ return (zpool_standard_error_fmt(hdl, ret,
+ dgettext(TEXT_DOMAIN, "sync '%s' failed"), pool_name));
+ }
+ nvlist_free(innvl);
+
+ return (0);
+}
+
+#define PATH_BUF_LEN 64
+
+/*
+ * Given a vdev, return the name to display in iostat. If the vdev has a path,
+ * we use that, stripping off any leading "/dev/dsk/"; if not, we use the type.
+ * We also check if this is a whole disk, in which case we strip off the
+ * trailing 's0' slice name.
+ *
+ * This routine is also responsible for identifying when disks have been
+ * reconfigured in a new location. The kernel will have opened the device by
+ * devid, but the path will still refer to the old location. To catch this, we
+ * first do a path -> devid translation (which is fast for the common case). If
+ * the devid matches, we're done. If not, we do a reverse devid -> path
+ * translation and issue the appropriate ioctl() to update the path of the vdev.
+ * If 'zhp' is NULL, then this is an exported pool, and we don't need to do any
+ * of these checks.
+ */
+char *
+zpool_vdev_name(libzfs_handle_t *hdl, zpool_handle_t *zhp, nvlist_t *nv,
+ int name_flags)
+{
+ char *path, *type, *env;
+ uint64_t value;
+ char buf[PATH_BUF_LEN];
+ char tmpbuf[PATH_BUF_LEN];
+
+ /*
+ * vdev_name will be "root"/"root-0" for the root vdev, but it is the
+ * zpool name that will be displayed to the user.
+ */
+ verify(nvlist_lookup_string(nv, ZPOOL_CONFIG_TYPE, &type) == 0);
+ if (zhp != NULL && strcmp(type, "root") == 0)
+ return (zfs_strdup(hdl, zpool_get_name(zhp)));
+
+ env = getenv("ZPOOL_VDEV_NAME_PATH");
+ if (env && (strtoul(env, NULL, 0) > 0 ||
+ !strncasecmp(env, "YES", 3) || !strncasecmp(env, "ON", 2)))
+ name_flags |= VDEV_NAME_PATH;
+
+ env = getenv("ZPOOL_VDEV_NAME_GUID");
+ if (env && (strtoul(env, NULL, 0) > 0 ||
+ !strncasecmp(env, "YES", 3) || !strncasecmp(env, "ON", 2)))
+ name_flags |= VDEV_NAME_GUID;
+
+ env = getenv("ZPOOL_VDEV_NAME_FOLLOW_LINKS");
+ if (env && (strtoul(env, NULL, 0) > 0 ||
+ !strncasecmp(env, "YES", 3) || !strncasecmp(env, "ON", 2)))
+ name_flags |= VDEV_NAME_FOLLOW_LINKS;
+
+ if (nvlist_lookup_uint64(nv, ZPOOL_CONFIG_NOT_PRESENT, &value) == 0 ||
+ name_flags & VDEV_NAME_GUID) {
+ (void) nvlist_lookup_uint64(nv, ZPOOL_CONFIG_GUID, &value);
+ (void) snprintf(buf, sizeof (buf), "%llu", (u_longlong_t)value);
+ path = buf;
+ } else if (nvlist_lookup_string(nv, ZPOOL_CONFIG_PATH, &path) == 0) {
+ if (name_flags & VDEV_NAME_FOLLOW_LINKS) {
+ char *rp = realpath(path, NULL);
+ if (rp) {
+ strlcpy(buf, rp, sizeof (buf));
+ path = buf;
+ free(rp);
+ }
+ }
+
+ /*
+ * For a block device only use the name.
+ */
+ if ((strcmp(type, VDEV_TYPE_DISK) == 0) &&
+ !(name_flags & VDEV_NAME_PATH)) {
+ path = zfs_strip_path(path);
+ }
+
+ /*
+ * Remove the partition from the path it this is a whole disk.
+ */
+ if (nvlist_lookup_uint64(nv, ZPOOL_CONFIG_WHOLE_DISK, &value)
+ == 0 && value && !(name_flags & VDEV_NAME_PATH)) {
+ return (zfs_strip_partition(path));
+ }
+ } else {
+ path = type;
+
+ /*
+ * If it's a raidz device, we need to stick in the parity level.
+ */
+ if (strcmp(path, VDEV_TYPE_RAIDZ) == 0) {
+ verify(nvlist_lookup_uint64(nv, ZPOOL_CONFIG_NPARITY,
+ &value) == 0);
+ (void) snprintf(buf, sizeof (buf), "%s%llu", path,
+ (u_longlong_t)value);
+ path = buf;
+ }
+
+ /*
+ * We identify each top-level vdev by using a <type-id>
+ * naming convention.
+ */
+ if (name_flags & VDEV_NAME_TYPE_ID) {
+ uint64_t id;
+ verify(nvlist_lookup_uint64(nv, ZPOOL_CONFIG_ID,
+ &id) == 0);
+ (void) snprintf(tmpbuf, sizeof (tmpbuf), "%s-%llu",
+ path, (u_longlong_t)id);
+ path = tmpbuf;
+ }
+ }
+
+ return (zfs_strdup(hdl, path));
+}
+
+static int
+zbookmark_mem_compare(const void *a, const void *b)
+{
+ return (memcmp(a, b, sizeof (zbookmark_phys_t)));
+}
+
+/*
+ * Retrieve the persistent error log, uniquify the members, and return to the
+ * caller.
+ */
+int
+zpool_get_errlog(zpool_handle_t *zhp, nvlist_t **nverrlistp)
+{
+ zfs_cmd_t zc = {"\0"};
+ libzfs_handle_t *hdl = zhp->zpool_hdl;
+ uint64_t count;
+ zbookmark_phys_t *zb = NULL;
+ int i;
+
+ /*
+ * Retrieve the raw error list from the kernel. If the number of errors
+ * has increased, allocate more space and continue until we get the
+ * entire list.
+ */
+ verify(nvlist_lookup_uint64(zhp->zpool_config, ZPOOL_CONFIG_ERRCOUNT,
+ &count) == 0);
+ if (count == 0)
+ return (0);
+ zc.zc_nvlist_dst = (uintptr_t)zfs_alloc(zhp->zpool_hdl,
+ count * sizeof (zbookmark_phys_t));
+ zc.zc_nvlist_dst_size = count;
+ (void) strcpy(zc.zc_name, zhp->zpool_name);
+ for (;;) {
+ if (zfs_ioctl(zhp->zpool_hdl, ZFS_IOC_ERROR_LOG,
+ &zc) != 0) {
+ free((void *)(uintptr_t)zc.zc_nvlist_dst);
+ if (errno == ENOMEM) {
+ void *dst;
+
+ count = zc.zc_nvlist_dst_size;
+ dst = zfs_alloc(zhp->zpool_hdl, count *
+ sizeof (zbookmark_phys_t));
+ zc.zc_nvlist_dst = (uintptr_t)dst;
+ } else {
+ return (zpool_standard_error_fmt(hdl, errno,
+ dgettext(TEXT_DOMAIN, "errors: List of "
+ "errors unavailable")));
+ }
+ } else {
+ break;
+ }
+ }
+
+ /*
+ * Sort the resulting bookmarks. This is a little confusing due to the
+ * implementation of ZFS_IOC_ERROR_LOG. The bookmarks are copied last
+ * to first, and 'zc_nvlist_dst_size' indicates the number of bookmarks
+ * _not_ copied as part of the process. So we point the start of our
+ * array appropriate and decrement the total number of elements.
+ */
+ zb = ((zbookmark_phys_t *)(uintptr_t)zc.zc_nvlist_dst) +
+ zc.zc_nvlist_dst_size;
+ count -= zc.zc_nvlist_dst_size;
+
+ qsort(zb, count, sizeof (zbookmark_phys_t), zbookmark_mem_compare);
+
+ verify(nvlist_alloc(nverrlistp, 0, KM_SLEEP) == 0);
+
+ /*
+ * Fill in the nverrlistp with nvlist's of dataset and object numbers.
+ */
+ for (i = 0; i < count; i++) {
+ nvlist_t *nv;
+
+ /* ignoring zb_blkid and zb_level for now */
+ if (i > 0 && zb[i-1].zb_objset == zb[i].zb_objset &&
+ zb[i-1].zb_object == zb[i].zb_object)
+ continue;
+
+ if (nvlist_alloc(&nv, NV_UNIQUE_NAME, KM_SLEEP) != 0)
+ goto nomem;
+ if (nvlist_add_uint64(nv, ZPOOL_ERR_DATASET,
+ zb[i].zb_objset) != 0) {
+ nvlist_free(nv);
+ goto nomem;
+ }
+ if (nvlist_add_uint64(nv, ZPOOL_ERR_OBJECT,
+ zb[i].zb_object) != 0) {
+ nvlist_free(nv);
+ goto nomem;
+ }
+ if (nvlist_add_nvlist(*nverrlistp, "ejk", nv) != 0) {
+ nvlist_free(nv);
+ goto nomem;
+ }
+ nvlist_free(nv);
+ }
+
+ free((void *)(uintptr_t)zc.zc_nvlist_dst);
+ return (0);
+
+nomem:
+ free((void *)(uintptr_t)zc.zc_nvlist_dst);
+ return (no_memory(zhp->zpool_hdl));
+}
+
+/*
+ * Upgrade a ZFS pool to the latest on-disk version.
+ */
+int
+zpool_upgrade(zpool_handle_t *zhp, uint64_t new_version)
+{
+ zfs_cmd_t zc = {"\0"};
+ libzfs_handle_t *hdl = zhp->zpool_hdl;
+
+ (void) strcpy(zc.zc_name, zhp->zpool_name);
+ zc.zc_cookie = new_version;
+
+ if (zfs_ioctl(hdl, ZFS_IOC_POOL_UPGRADE, &zc) != 0)
+ return (zpool_standard_error_fmt(hdl, errno,
+ dgettext(TEXT_DOMAIN, "cannot upgrade '%s'"),
+ zhp->zpool_name));
+ return (0);
+}
+
+void
+zfs_save_arguments(int argc, char **argv, char *string, int len)
+{
+ int i;
+
+ (void) strlcpy(string, basename(argv[0]), len);
+ for (i = 1; i < argc; i++) {
+ (void) strlcat(string, " ", len);
+ (void) strlcat(string, argv[i], len);
+ }
+}
+
+int
+zpool_log_history(libzfs_handle_t *hdl, const char *message)
+{
+ zfs_cmd_t zc = {"\0"};
+ nvlist_t *args;
+ int err;
+
+ args = fnvlist_alloc();
+ fnvlist_add_string(args, "message", message);
+ err = zcmd_write_src_nvlist(hdl, &zc, args);
+ if (err == 0)
+ err = zfs_ioctl(hdl, ZFS_IOC_LOG_HISTORY, &zc);
+ nvlist_free(args);
+ zcmd_free_nvlists(&zc);
+ return (err);
+}
+
+/*
+ * Perform ioctl to get some command history of a pool.
+ *
+ * 'buf' is the buffer to fill up to 'len' bytes. 'off' is the
+ * logical offset of the history buffer to start reading from.
+ *
+ * Upon return, 'off' is the next logical offset to read from and
+ * 'len' is the actual amount of bytes read into 'buf'.
+ */
+static int
+get_history(zpool_handle_t *zhp, char *buf, uint64_t *off, uint64_t *len)
+{
+ zfs_cmd_t zc = {"\0"};
+ libzfs_handle_t *hdl = zhp->zpool_hdl;
+
+ (void) strlcpy(zc.zc_name, zhp->zpool_name, sizeof (zc.zc_name));
+
+ zc.zc_history = (uint64_t)(uintptr_t)buf;
+ zc.zc_history_len = *len;
+ zc.zc_history_offset = *off;
+
+ if (zfs_ioctl(hdl, ZFS_IOC_POOL_GET_HISTORY, &zc) != 0) {
+ switch (errno) {
+ case EPERM:
+ return (zfs_error_fmt(hdl, EZFS_PERM,
+ dgettext(TEXT_DOMAIN,
+ "cannot show history for pool '%s'"),
+ zhp->zpool_name));
+ case ENOENT:
+ return (zfs_error_fmt(hdl, EZFS_NOHISTORY,
+ dgettext(TEXT_DOMAIN, "cannot get history for pool "
+ "'%s'"), zhp->zpool_name));
+ case ENOTSUP:
+ return (zfs_error_fmt(hdl, EZFS_BADVERSION,
+ dgettext(TEXT_DOMAIN, "cannot get history for pool "
+ "'%s', pool must be upgraded"), zhp->zpool_name));
+ default:
+ return (zpool_standard_error_fmt(hdl, errno,
+ dgettext(TEXT_DOMAIN,
+ "cannot get history for '%s'"), zhp->zpool_name));
+ }
+ }
+
+ *len = zc.zc_history_len;
+ *off = zc.zc_history_offset;
+
+ return (0);
+}
+
+/*
+ * Retrieve the command history of a pool.
+ */
+int
+zpool_get_history(zpool_handle_t *zhp, nvlist_t **nvhisp, uint64_t *off,
+ boolean_t *eof)
+{
+ char *buf;
+ int buflen = 128 * 1024;
+ nvlist_t **records = NULL;
+ uint_t numrecords = 0;
+ int err, i;
+ uint64_t start = *off;
+
+ buf = malloc(buflen);
+ if (buf == NULL)
+ return (ENOMEM);
+ /* process about 1MB a time */
+ while (*off - start < 1024 * 1024) {
+ uint64_t bytes_read = buflen;
+ uint64_t leftover;
+
+ if ((err = get_history(zhp, buf, off, &bytes_read)) != 0)
+ break;
+
+ /* if nothing else was read in, we're at EOF, just return */
+ if (!bytes_read) {
+ *eof = B_TRUE;
+ break;
+ }
+
+ if ((err = zpool_history_unpack(buf, bytes_read,
+ &leftover, &records, &numrecords)) != 0)
+ break;
+ *off -= leftover;
+ if (leftover == bytes_read) {
+ /*
+ * no progress made, because buffer is not big enough
+ * to hold this record; resize and retry.
+ */
+ buflen *= 2;
+ free(buf);
+ buf = malloc(buflen);
+ if (buf == NULL)
+ return (ENOMEM);
+ }
+ }
+
+ free(buf);
+
+ if (!err) {
+ verify(nvlist_alloc(nvhisp, NV_UNIQUE_NAME, 0) == 0);
+ verify(nvlist_add_nvlist_array(*nvhisp, ZPOOL_HIST_RECORD,
+ records, numrecords) == 0);
+ }
+ for (i = 0; i < numrecords; i++)
+ nvlist_free(records[i]);
+ free(records);
+
+ return (err);
+}
+
+/*
+ * Retrieve the next event given the passed 'zevent_fd' file descriptor.
+ * If there is a new event available 'nvp' will contain a newly allocated
+ * nvlist and 'dropped' will be set to the number of missed events since
+ * the last call to this function. When 'nvp' is set to NULL it indicates
+ * no new events are available. In either case the function returns 0 and
+ * it is up to the caller to free 'nvp'. In the case of a fatal error the
+ * function will return a non-zero value. When the function is called in
+ * blocking mode (the default, unless the ZEVENT_NONBLOCK flag is passed),
+ * it will not return until a new event is available.
+ */
+int
+zpool_events_next(libzfs_handle_t *hdl, nvlist_t **nvp,
+ int *dropped, unsigned flags, int zevent_fd)
+{
+ zfs_cmd_t zc = {"\0"};
+ int error = 0;
+
+ *nvp = NULL;
+ *dropped = 0;
+ zc.zc_cleanup_fd = zevent_fd;
+
+ if (flags & ZEVENT_NONBLOCK)
+ zc.zc_guid = ZEVENT_NONBLOCK;
+
+ if (zcmd_alloc_dst_nvlist(hdl, &zc, ZEVENT_SIZE) != 0)
+ return (-1);
+
+retry:
+ if (zfs_ioctl(hdl, ZFS_IOC_EVENTS_NEXT, &zc) != 0) {
+ switch (errno) {
+ case ESHUTDOWN:
+ error = zfs_error_fmt(hdl, EZFS_POOLUNAVAIL,
+ dgettext(TEXT_DOMAIN, "zfs shutdown"));
+ goto out;
+ case ENOENT:
+ /* Blocking error case should not occur */
+ if (!(flags & ZEVENT_NONBLOCK))
+ error = zpool_standard_error_fmt(hdl, errno,
+ dgettext(TEXT_DOMAIN, "cannot get event"));
+
+ goto out;
+ case ENOMEM:
+ if (zcmd_expand_dst_nvlist(hdl, &zc) != 0) {
+ error = zfs_error_fmt(hdl, EZFS_NOMEM,
+ dgettext(TEXT_DOMAIN, "cannot get event"));
+ goto out;
+ } else {
+ goto retry;
+ }
+ default:
+ error = zpool_standard_error_fmt(hdl, errno,
+ dgettext(TEXT_DOMAIN, "cannot get event"));
+ goto out;
+ }
+ }
+
+ error = zcmd_read_dst_nvlist(hdl, &zc, nvp);
+ if (error != 0)
+ goto out;
+
+ *dropped = (int)zc.zc_cookie;
+out:
+ zcmd_free_nvlists(&zc);
+
+ return (error);
+}
+
+/*
+ * Clear all events.
+ */
+int
+zpool_events_clear(libzfs_handle_t *hdl, int *count)
+{
+ zfs_cmd_t zc = {"\0"};
+ char msg[1024];
+
+ (void) snprintf(msg, sizeof (msg), dgettext(TEXT_DOMAIN,
+ "cannot clear events"));
+
+ if (zfs_ioctl(hdl, ZFS_IOC_EVENTS_CLEAR, &zc) != 0)
+ return (zpool_standard_error_fmt(hdl, errno, msg));
+
+ if (count != NULL)
+ *count = (int)zc.zc_cookie; /* # of events cleared */
+
+ return (0);
+}
+
+/*
+ * Seek to a specific EID, ZEVENT_SEEK_START, or ZEVENT_SEEK_END for
+ * the passed zevent_fd file handle. On success zero is returned,
+ * otherwise -1 is returned and hdl->libzfs_error is set to the errno.
+ */
+int
+zpool_events_seek(libzfs_handle_t *hdl, uint64_t eid, int zevent_fd)
+{
+ zfs_cmd_t zc = {"\0"};
+ int error = 0;
+
+ zc.zc_guid = eid;
+ zc.zc_cleanup_fd = zevent_fd;
+
+ if (zfs_ioctl(hdl, ZFS_IOC_EVENTS_SEEK, &zc) != 0) {
+ switch (errno) {
+ case ENOENT:
+ error = zfs_error_fmt(hdl, EZFS_NOENT,
+ dgettext(TEXT_DOMAIN, "cannot get event"));
+ break;
+
+ case ENOMEM:
+ error = zfs_error_fmt(hdl, EZFS_NOMEM,
+ dgettext(TEXT_DOMAIN, "cannot get event"));
+ break;
+
+ default:
+ error = zpool_standard_error_fmt(hdl, errno,
+ dgettext(TEXT_DOMAIN, "cannot get event"));
+ break;
+ }
+ }
+
+ return (error);
+}
+
+static void
+zpool_obj_to_path_impl(zpool_handle_t *zhp, uint64_t dsobj, uint64_t obj,
+ char *pathname, size_t len, boolean_t always_unmounted)
+{
+ zfs_cmd_t zc = {"\0"};
+ boolean_t mounted = B_FALSE;
+ char *mntpnt = NULL;
+ char dsname[ZFS_MAX_DATASET_NAME_LEN];
+
+ if (dsobj == 0) {
+ /* special case for the MOS */
+ (void) snprintf(pathname, len, "<metadata>:<0x%llx>",
+ (longlong_t)obj);
+ return;
+ }
+
+ /* get the dataset's name */
+ (void) strlcpy(zc.zc_name, zhp->zpool_name, sizeof (zc.zc_name));
+ zc.zc_obj = dsobj;
+ if (zfs_ioctl(zhp->zpool_hdl,
+ ZFS_IOC_DSOBJ_TO_DSNAME, &zc) != 0) {
+ /* just write out a path of two object numbers */
+ (void) snprintf(pathname, len, "<0x%llx>:<0x%llx>",
+ (longlong_t)dsobj, (longlong_t)obj);
+ return;
+ }
+ (void) strlcpy(dsname, zc.zc_value, sizeof (dsname));
+
+ /* find out if the dataset is mounted */
+ mounted = !always_unmounted && is_mounted(zhp->zpool_hdl, dsname,
+ &mntpnt);
+
+ /* get the corrupted object's path */
+ (void) strlcpy(zc.zc_name, dsname, sizeof (zc.zc_name));
+ zc.zc_obj = obj;
+ if (zfs_ioctl(zhp->zpool_hdl, ZFS_IOC_OBJ_TO_PATH,
+ &zc) == 0) {
+ if (mounted) {
+ (void) snprintf(pathname, len, "%s%s", mntpnt,
+ zc.zc_value);
+ } else {
+ (void) snprintf(pathname, len, "%s:%s",
+ dsname, zc.zc_value);
+ }
+ } else {
+ (void) snprintf(pathname, len, "%s:<0x%llx>", dsname,
+ (longlong_t)obj);
+ }
+ free(mntpnt);
+}
+
+void
+zpool_obj_to_path(zpool_handle_t *zhp, uint64_t dsobj, uint64_t obj,
+ char *pathname, size_t len)
+{
+ zpool_obj_to_path_impl(zhp, dsobj, obj, pathname, len, B_FALSE);
+}
+
+void
+zpool_obj_to_path_ds(zpool_handle_t *zhp, uint64_t dsobj, uint64_t obj,
+ char *pathname, size_t len)
+{
+ zpool_obj_to_path_impl(zhp, dsobj, obj, pathname, len, B_TRUE);
+}
+/*
+ * Wait while the specified activity is in progress in the pool.
+ */
+int
+zpool_wait(zpool_handle_t *zhp, zpool_wait_activity_t activity)
+{
+ boolean_t missing;
+
+ int error = zpool_wait_status(zhp, activity, &missing, NULL);
+
+ if (missing) {
+ (void) zpool_standard_error_fmt(zhp->zpool_hdl, ENOENT,
+ dgettext(TEXT_DOMAIN, "error waiting in pool '%s'"),
+ zhp->zpool_name);
+ return (ENOENT);
+ } else {
+ return (error);
+ }
+}
+
+/*
+ * Wait for the given activity and return the status of the wait (whether or not
+ * any waiting was done) in the 'waited' parameter. Non-existent pools are
+ * reported via the 'missing' parameter, rather than by printing an error
+ * message. This is convenient when this function is called in a loop over a
+ * long period of time (as it is, for example, by zpool's wait cmd). In that
+ * scenario, a pool being exported or destroyed should be considered a normal
+ * event, so we don't want to print an error when we find that the pool doesn't
+ * exist.
+ */
+int
+zpool_wait_status(zpool_handle_t *zhp, zpool_wait_activity_t activity,
+ boolean_t *missing, boolean_t *waited)
+{
+ int error = lzc_wait(zhp->zpool_name, activity, waited);
+ *missing = (error == ENOENT);
+ if (*missing)
+ return (0);
+
+ if (error != 0) {
+ (void) zpool_standard_error_fmt(zhp->zpool_hdl, error,
+ dgettext(TEXT_DOMAIN, "error waiting in pool '%s'"),
+ zhp->zpool_name);
+ }
+
+ return (error);
+}
+
+int
+zpool_set_bootenv(zpool_handle_t *zhp, const char *envmap)
+{
+ int error = lzc_set_bootenv(zhp->zpool_name, envmap);
+ if (error != 0) {
+ (void) zpool_standard_error_fmt(zhp->zpool_hdl, error,
+ dgettext(TEXT_DOMAIN,
+ "error setting bootenv in pool '%s'"), zhp->zpool_name);
+ }
+
+ return (error);
+}
+
+int
+zpool_get_bootenv(zpool_handle_t *zhp, char *outbuf, size_t size, off_t offset)
+{
+ nvlist_t *nvl = NULL;
+ int error = lzc_get_bootenv(zhp->zpool_name, &nvl);
+ if (error != 0) {
+ (void) zpool_standard_error_fmt(zhp->zpool_hdl, error,
+ dgettext(TEXT_DOMAIN,
+ "error getting bootenv in pool '%s'"), zhp->zpool_name);
+ return (-1);
+ }
+ char *envmap = fnvlist_lookup_string(nvl, "envmap");
+ if (offset >= strlen(envmap)) {
+ fnvlist_free(nvl);
+ return (0);
+ }
+
+ strncpy(outbuf, envmap + offset, size);
+ int bytes = MIN(strlen(envmap + offset), size);
+ fnvlist_free(nvl);
+ return (bytes);
+}
diff --git a/lib/libzfs/libzfs_sendrecv.c b/lib/libzfs/libzfs_sendrecv.c
new file mode 100644
index 000000000000..f1524c384feb
--- /dev/null
+++ b/lib/libzfs/libzfs_sendrecv.c
@@ -0,0 +1,5170 @@
+/*
+ * 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) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 2020 by Delphix. All rights reserved.
+ * Copyright (c) 2012, Joyent, Inc. All rights reserved.
+ * Copyright (c) 2012 Pawel Jakub Dawidek <pawel@dawidek.net>.
+ * All rights reserved
+ * Copyright (c) 2013 Steven Hartland. All rights reserved.
+ * Copyright 2015, OmniTI Computer Consulting, Inc. All rights reserved.
+ * Copyright 2016 Igor Kozhukhov <ikozhukhov@gmail.com>
+ * Copyright (c) 2018, loli10K <ezomori.nozomu@gmail.com>. All rights reserved.
+ * Copyright (c) 2019 Datto Inc.
+ */
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <libintl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <unistd.h>
+#include <stddef.h>
+#include <fcntl.h>
+#include <sys/mount.h>
+#include <sys/mntent.h>
+#include <sys/mnttab.h>
+#include <sys/avl.h>
+#include <sys/debug.h>
+#include <sys/stat.h>
+#include <stddef.h>
+#include <pthread.h>
+#include <umem.h>
+#include <time.h>
+
+#include <libzfs.h>
+#include <libzfs_core.h>
+#include <libzutil.h>
+
+#include "zfs_namecheck.h"
+#include "zfs_prop.h"
+#include "zfs_fletcher.h"
+#include "libzfs_impl.h"
+#include <cityhash.h>
+#include <zlib.h>
+#include <sys/zio_checksum.h>
+#include <sys/dsl_crypt.h>
+#include <sys/ddt.h>
+#include <sys/socket.h>
+#include <sys/sha2.h>
+
+static int zfs_receive_impl(libzfs_handle_t *, const char *, const char *,
+ recvflags_t *, int, const char *, nvlist_t *, avl_tree_t *, char **,
+ const char *, nvlist_t *);
+static int guid_to_name_redact_snaps(libzfs_handle_t *hdl, const char *parent,
+ uint64_t guid, boolean_t bookmark_ok, uint64_t *redact_snap_guids,
+ uint64_t num_redact_snaps, char *name);
+static int guid_to_name(libzfs_handle_t *, const char *,
+ uint64_t, boolean_t, char *);
+
+typedef struct progress_arg {
+ zfs_handle_t *pa_zhp;
+ int pa_fd;
+ boolean_t pa_parsable;
+ boolean_t pa_estimate;
+ int pa_verbosity;
+} progress_arg_t;
+
+static int
+dump_record(dmu_replay_record_t *drr, void *payload, int payload_len,
+ zio_cksum_t *zc, int outfd)
+{
+ ASSERT3U(offsetof(dmu_replay_record_t, drr_u.drr_checksum.drr_checksum),
+ ==, sizeof (dmu_replay_record_t) - sizeof (zio_cksum_t));
+ fletcher_4_incremental_native(drr,
+ offsetof(dmu_replay_record_t, drr_u.drr_checksum.drr_checksum), zc);
+ if (drr->drr_type != DRR_BEGIN) {
+ ASSERT(ZIO_CHECKSUM_IS_ZERO(&drr->drr_u.
+ drr_checksum.drr_checksum));
+ drr->drr_u.drr_checksum.drr_checksum = *zc;
+ }
+ fletcher_4_incremental_native(&drr->drr_u.drr_checksum.drr_checksum,
+ sizeof (zio_cksum_t), zc);
+ if (write(outfd, drr, sizeof (*drr)) == -1)
+ return (errno);
+ if (payload_len != 0) {
+ fletcher_4_incremental_native(payload, payload_len, zc);
+ if (write(outfd, payload, payload_len) == -1)
+ return (errno);
+ }
+ return (0);
+}
+
+/*
+ * Routines for dealing with the AVL tree of fs-nvlists
+ */
+typedef struct fsavl_node {
+ avl_node_t fn_node;
+ nvlist_t *fn_nvfs;
+ char *fn_snapname;
+ uint64_t fn_guid;
+} fsavl_node_t;
+
+static int
+fsavl_compare(const void *arg1, const void *arg2)
+{
+ const fsavl_node_t *fn1 = (const fsavl_node_t *)arg1;
+ const fsavl_node_t *fn2 = (const fsavl_node_t *)arg2;
+
+ return (TREE_CMP(fn1->fn_guid, fn2->fn_guid));
+}
+
+/*
+ * Given the GUID of a snapshot, find its containing filesystem and
+ * (optionally) name.
+ */
+static nvlist_t *
+fsavl_find(avl_tree_t *avl, uint64_t snapguid, char **snapname)
+{
+ fsavl_node_t fn_find;
+ fsavl_node_t *fn;
+
+ fn_find.fn_guid = snapguid;
+
+ fn = avl_find(avl, &fn_find, NULL);
+ if (fn) {
+ if (snapname)
+ *snapname = fn->fn_snapname;
+ return (fn->fn_nvfs);
+ }
+ return (NULL);
+}
+
+static void
+fsavl_destroy(avl_tree_t *avl)
+{
+ fsavl_node_t *fn;
+ void *cookie;
+
+ if (avl == NULL)
+ return;
+
+ cookie = NULL;
+ while ((fn = avl_destroy_nodes(avl, &cookie)) != NULL)
+ free(fn);
+ avl_destroy(avl);
+ free(avl);
+}
+
+/*
+ * Given an nvlist, produce an avl tree of snapshots, ordered by guid
+ */
+static avl_tree_t *
+fsavl_create(nvlist_t *fss)
+{
+ avl_tree_t *fsavl;
+ nvpair_t *fselem = NULL;
+
+ if ((fsavl = malloc(sizeof (avl_tree_t))) == NULL)
+ return (NULL);
+
+ avl_create(fsavl, fsavl_compare, sizeof (fsavl_node_t),
+ offsetof(fsavl_node_t, fn_node));
+
+ while ((fselem = nvlist_next_nvpair(fss, fselem)) != NULL) {
+ nvlist_t *nvfs, *snaps;
+ nvpair_t *snapelem = NULL;
+
+ VERIFY(0 == nvpair_value_nvlist(fselem, &nvfs));
+ VERIFY(0 == nvlist_lookup_nvlist(nvfs, "snaps", &snaps));
+
+ while ((snapelem =
+ nvlist_next_nvpair(snaps, snapelem)) != NULL) {
+ fsavl_node_t *fn;
+ uint64_t guid;
+
+ VERIFY(0 == nvpair_value_uint64(snapelem, &guid));
+ if ((fn = malloc(sizeof (fsavl_node_t))) == NULL) {
+ fsavl_destroy(fsavl);
+ return (NULL);
+ }
+ fn->fn_nvfs = nvfs;
+ fn->fn_snapname = nvpair_name(snapelem);
+ fn->fn_guid = guid;
+
+ /*
+ * Note: if there are multiple snaps with the
+ * same GUID, we ignore all but one.
+ */
+ if (avl_find(fsavl, fn, NULL) == NULL)
+ avl_add(fsavl, fn);
+ else
+ free(fn);
+ }
+ }
+
+ return (fsavl);
+}
+
+/*
+ * Routines for dealing with the giant nvlist of fs-nvlists, etc.
+ */
+typedef struct send_data {
+ /*
+ * assigned inside every recursive call,
+ * restored from *_save on return:
+ *
+ * guid of fromsnap snapshot in parent dataset
+ * txg of fromsnap snapshot in current dataset
+ * txg of tosnap snapshot in current dataset
+ */
+
+ uint64_t parent_fromsnap_guid;
+ uint64_t fromsnap_txg;
+ uint64_t tosnap_txg;
+
+ /* the nvlists get accumulated during depth-first traversal */
+ nvlist_t *parent_snaps;
+ nvlist_t *fss;
+ nvlist_t *snapprops;
+ nvlist_t *snapholds; /* user holds */
+
+ /* send-receive configuration, does not change during traversal */
+ const char *fsname;
+ const char *fromsnap;
+ const char *tosnap;
+ boolean_t recursive;
+ boolean_t raw;
+ boolean_t doall;
+ boolean_t replicate;
+ boolean_t verbose;
+ boolean_t backup;
+ boolean_t seenfrom;
+ boolean_t seento;
+ boolean_t holds; /* were holds requested with send -h */
+ boolean_t props;
+
+ /*
+ * The header nvlist is of the following format:
+ * {
+ * "tosnap" -> string
+ * "fromsnap" -> string (if incremental)
+ * "fss" -> {
+ * id -> {
+ *
+ * "name" -> string (full name; for debugging)
+ * "parentfromsnap" -> number (guid of fromsnap in parent)
+ *
+ * "props" -> { name -> value (only if set here) }
+ * "snaps" -> { name (lastname) -> number (guid) }
+ * "snapprops" -> { name (lastname) -> { name -> value } }
+ * "snapholds" -> { name (lastname) -> { holdname -> crtime } }
+ *
+ * "origin" -> number (guid) (if clone)
+ * "is_encroot" -> boolean
+ * "sent" -> boolean (not on-disk)
+ * }
+ * }
+ * }
+ *
+ */
+} send_data_t;
+
+static void
+send_iterate_prop(zfs_handle_t *zhp, boolean_t received_only, nvlist_t *nv);
+
+static int
+send_iterate_snap(zfs_handle_t *zhp, void *arg)
+{
+ send_data_t *sd = arg;
+ uint64_t guid = zhp->zfs_dmustats.dds_guid;
+ uint64_t txg = zhp->zfs_dmustats.dds_creation_txg;
+ char *snapname;
+ nvlist_t *nv;
+ boolean_t isfromsnap, istosnap, istosnapwithnofrom;
+
+ snapname = strrchr(zhp->zfs_name, '@')+1;
+ isfromsnap = (sd->fromsnap != NULL &&
+ strcmp(sd->fromsnap, snapname) == 0);
+ istosnap = (sd->tosnap != NULL && (strcmp(sd->tosnap, snapname) == 0));
+ istosnapwithnofrom = (istosnap && sd->fromsnap == NULL);
+
+ if (sd->tosnap_txg != 0 && txg > sd->tosnap_txg) {
+ if (sd->verbose) {
+ (void) fprintf(stderr, dgettext(TEXT_DOMAIN,
+ "skipping snapshot %s because it was created "
+ "after the destination snapshot (%s)\n"),
+ zhp->zfs_name, sd->tosnap);
+ }
+ zfs_close(zhp);
+ return (0);
+ }
+
+ VERIFY(0 == nvlist_add_uint64(sd->parent_snaps, snapname, guid));
+ /*
+ * NB: if there is no fromsnap here (it's a newly created fs in
+ * an incremental replication), we will substitute the tosnap.
+ */
+ if (isfromsnap || (sd->parent_fromsnap_guid == 0 && istosnap)) {
+ sd->parent_fromsnap_guid = guid;
+ }
+
+ if (!sd->recursive) {
+ if (!sd->seenfrom && isfromsnap) {
+ sd->seenfrom = B_TRUE;
+ zfs_close(zhp);
+ return (0);
+ }
+
+ if ((sd->seento || !sd->seenfrom) && !istosnapwithnofrom) {
+ zfs_close(zhp);
+ return (0);
+ }
+
+ if (istosnap)
+ sd->seento = B_TRUE;
+ }
+
+ VERIFY(0 == nvlist_alloc(&nv, NV_UNIQUE_NAME, 0));
+ send_iterate_prop(zhp, sd->backup, nv);
+ VERIFY(0 == nvlist_add_nvlist(sd->snapprops, snapname, nv));
+ nvlist_free(nv);
+ if (sd->holds) {
+ nvlist_t *holds = fnvlist_alloc();
+ int err = lzc_get_holds(zhp->zfs_name, &holds);
+ if (err == 0) {
+ VERIFY(0 == nvlist_add_nvlist(sd->snapholds,
+ snapname, holds));
+ }
+ fnvlist_free(holds);
+ }
+
+ zfs_close(zhp);
+ return (0);
+}
+
+static void
+send_iterate_prop(zfs_handle_t *zhp, boolean_t received_only, nvlist_t *nv)
+{
+ nvlist_t *props = NULL;
+ nvpair_t *elem = NULL;
+
+ if (received_only)
+ props = zfs_get_recvd_props(zhp);
+ else
+ props = zhp->zfs_props;
+
+ while ((elem = nvlist_next_nvpair(props, elem)) != NULL) {
+ char *propname = nvpair_name(elem);
+ zfs_prop_t prop = zfs_name_to_prop(propname);
+ nvlist_t *propnv;
+
+ if (!zfs_prop_user(propname)) {
+ /*
+ * Realistically, this should never happen. However,
+ * we want the ability to add DSL properties without
+ * needing to make incompatible version changes. We
+ * need to ignore unknown properties to allow older
+ * software to still send datasets containing these
+ * properties, with the unknown properties elided.
+ */
+ if (prop == ZPROP_INVAL)
+ continue;
+
+ if (zfs_prop_readonly(prop))
+ continue;
+ }
+
+ verify(nvpair_value_nvlist(elem, &propnv) == 0);
+ if (prop == ZFS_PROP_QUOTA || prop == ZFS_PROP_RESERVATION ||
+ prop == ZFS_PROP_REFQUOTA ||
+ prop == ZFS_PROP_REFRESERVATION) {
+ char *source;
+ uint64_t value;
+ verify(nvlist_lookup_uint64(propnv,
+ ZPROP_VALUE, &value) == 0);
+ if (zhp->zfs_type == ZFS_TYPE_SNAPSHOT)
+ continue;
+ /*
+ * May have no source before SPA_VERSION_RECVD_PROPS,
+ * but is still modifiable.
+ */
+ if (nvlist_lookup_string(propnv,
+ ZPROP_SOURCE, &source) == 0) {
+ if ((strcmp(source, zhp->zfs_name) != 0) &&
+ (strcmp(source,
+ ZPROP_SOURCE_VAL_RECVD) != 0))
+ continue;
+ }
+ } else {
+ char *source;
+ if (nvlist_lookup_string(propnv,
+ ZPROP_SOURCE, &source) != 0)
+ continue;
+ if ((strcmp(source, zhp->zfs_name) != 0) &&
+ (strcmp(source, ZPROP_SOURCE_VAL_RECVD) != 0))
+ continue;
+ }
+
+ if (zfs_prop_user(propname) ||
+ zfs_prop_get_type(prop) == PROP_TYPE_STRING) {
+ char *value;
+ verify(nvlist_lookup_string(propnv,
+ ZPROP_VALUE, &value) == 0);
+ VERIFY(0 == nvlist_add_string(nv, propname, value));
+ } else {
+ uint64_t value;
+ verify(nvlist_lookup_uint64(propnv,
+ ZPROP_VALUE, &value) == 0);
+ VERIFY(0 == nvlist_add_uint64(nv, propname, value));
+ }
+ }
+}
+
+/*
+ * returns snapshot creation txg
+ * and returns 0 if the snapshot does not exist
+ */
+static uint64_t
+get_snap_txg(libzfs_handle_t *hdl, const char *fs, const char *snap)
+{
+ char name[ZFS_MAX_DATASET_NAME_LEN];
+ uint64_t txg = 0;
+
+ if (fs == NULL || fs[0] == '\0' || snap == NULL || snap[0] == '\0')
+ return (txg);
+
+ (void) snprintf(name, sizeof (name), "%s@%s", fs, snap);
+ if (zfs_dataset_exists(hdl, name, ZFS_TYPE_SNAPSHOT)) {
+ zfs_handle_t *zhp = zfs_open(hdl, name, ZFS_TYPE_SNAPSHOT);
+ if (zhp != NULL) {
+ txg = zfs_prop_get_int(zhp, ZFS_PROP_CREATETXG);
+ zfs_close(zhp);
+ }
+ }
+
+ return (txg);
+}
+
+/*
+ * recursively generate nvlists describing datasets. See comment
+ * for the data structure send_data_t above for description of contents
+ * of the nvlist.
+ */
+static int
+send_iterate_fs(zfs_handle_t *zhp, void *arg)
+{
+ send_data_t *sd = arg;
+ nvlist_t *nvfs = NULL, *nv = NULL;
+ int rv = 0;
+ uint64_t min_txg = 0, max_txg = 0;
+ uint64_t parent_fromsnap_guid_save = sd->parent_fromsnap_guid;
+ uint64_t fromsnap_txg_save = sd->fromsnap_txg;
+ uint64_t tosnap_txg_save = sd->tosnap_txg;
+ uint64_t txg = zhp->zfs_dmustats.dds_creation_txg;
+ uint64_t guid = zhp->zfs_dmustats.dds_guid;
+ uint64_t fromsnap_txg, tosnap_txg;
+ char guidstring[64];
+
+ fromsnap_txg = get_snap_txg(zhp->zfs_hdl, zhp->zfs_name, sd->fromsnap);
+ if (fromsnap_txg != 0)
+ sd->fromsnap_txg = fromsnap_txg;
+
+ tosnap_txg = get_snap_txg(zhp->zfs_hdl, zhp->zfs_name, sd->tosnap);
+ if (tosnap_txg != 0)
+ sd->tosnap_txg = tosnap_txg;
+
+ /*
+ * on the send side, if the current dataset does not have tosnap,
+ * perform two additional checks:
+ *
+ * - skip sending the current dataset if it was created later than
+ * the parent tosnap
+ * - return error if the current dataset was created earlier than
+ * the parent tosnap
+ */
+ if (sd->tosnap != NULL && tosnap_txg == 0) {
+ if (sd->tosnap_txg != 0 && txg > sd->tosnap_txg) {
+ if (sd->verbose) {
+ (void) fprintf(stderr, dgettext(TEXT_DOMAIN,
+ "skipping dataset %s: snapshot %s does "
+ "not exist\n"), zhp->zfs_name, sd->tosnap);
+ }
+ } else {
+ (void) fprintf(stderr, dgettext(TEXT_DOMAIN,
+ "cannot send %s@%s%s: snapshot %s@%s does not "
+ "exist\n"), sd->fsname, sd->tosnap, sd->recursive ?
+ dgettext(TEXT_DOMAIN, " recursively") : "",
+ zhp->zfs_name, sd->tosnap);
+ rv = EZFS_NOENT;
+ }
+ goto out;
+ }
+
+ nvfs = fnvlist_alloc();
+ fnvlist_add_string(nvfs, "name", zhp->zfs_name);
+ fnvlist_add_uint64(nvfs, "parentfromsnap",
+ sd->parent_fromsnap_guid);
+
+ if (zhp->zfs_dmustats.dds_origin[0]) {
+ zfs_handle_t *origin = zfs_open(zhp->zfs_hdl,
+ zhp->zfs_dmustats.dds_origin, ZFS_TYPE_SNAPSHOT);
+ if (origin == NULL) {
+ rv = -1;
+ goto out;
+ }
+ fnvlist_add_uint64(nvfs, "origin",
+ origin->zfs_dmustats.dds_guid);
+
+ zfs_close(origin);
+ }
+
+ /* iterate over props */
+ if (sd->props || sd->backup || sd->recursive) {
+ nv = fnvlist_alloc();
+ send_iterate_prop(zhp, sd->backup, nv);
+ }
+ if (zfs_prop_get_int(zhp, ZFS_PROP_ENCRYPTION) != ZIO_CRYPT_OFF) {
+ boolean_t encroot;
+
+ /* determine if this dataset is an encryption root */
+ if (zfs_crypto_get_encryption_root(zhp, &encroot, NULL) != 0) {
+ rv = -1;
+ goto out;
+ }
+
+ if (encroot)
+ fnvlist_add_boolean(nvfs, "is_encroot");
+
+ /*
+ * Encrypted datasets can only be sent with properties if
+ * the raw flag is specified because the receive side doesn't
+ * currently have a mechanism for recursively asking the user
+ * for new encryption parameters.
+ */
+ if (!sd->raw) {
+ (void) fprintf(stderr, dgettext(TEXT_DOMAIN,
+ "cannot send %s@%s: encrypted dataset %s may not "
+ "be sent with properties without the raw flag\n"),
+ sd->fsname, sd->tosnap, zhp->zfs_name);
+ rv = -1;
+ goto out;
+ }
+
+ }
+
+ if (nv != NULL)
+ fnvlist_add_nvlist(nvfs, "props", nv);
+
+ /* iterate over snaps, and set sd->parent_fromsnap_guid */
+ sd->parent_fromsnap_guid = 0;
+ sd->parent_snaps = fnvlist_alloc();
+ sd->snapprops = fnvlist_alloc();
+ if (sd->holds)
+ VERIFY(0 == nvlist_alloc(&sd->snapholds, NV_UNIQUE_NAME, 0));
+
+
+ /*
+ * If this is a "doall" send, a replicate send or we're just trying
+ * to gather a list of previous snapshots, iterate through all the
+ * snaps in the txg range. Otherwise just look at the one we're
+ * interested in.
+ */
+ if (sd->doall || sd->replicate || sd->tosnap == NULL) {
+ if (!sd->replicate && fromsnap_txg != 0)
+ min_txg = fromsnap_txg;
+ if (!sd->replicate && tosnap_txg != 0)
+ max_txg = tosnap_txg;
+ (void) zfs_iter_snapshots_sorted(zhp, send_iterate_snap, sd,
+ min_txg, max_txg);
+ } else {
+ char snapname[MAXPATHLEN] = { 0 };
+ zfs_handle_t *snap;
+
+ (void) snprintf(snapname, sizeof (snapname), "%s@%s",
+ zhp->zfs_name, sd->tosnap);
+ if (sd->fromsnap != NULL)
+ sd->seenfrom = B_TRUE;
+ snap = zfs_open(zhp->zfs_hdl, snapname,
+ ZFS_TYPE_SNAPSHOT);
+ if (snap != NULL)
+ (void) send_iterate_snap(snap, sd);
+ }
+
+ fnvlist_add_nvlist(nvfs, "snaps", sd->parent_snaps);
+ fnvlist_add_nvlist(nvfs, "snapprops", sd->snapprops);
+ if (sd->holds)
+ fnvlist_add_nvlist(nvfs, "snapholds", sd->snapholds);
+ fnvlist_free(sd->parent_snaps);
+ fnvlist_free(sd->snapprops);
+ fnvlist_free(sd->snapholds);
+
+ /* add this fs to nvlist */
+ (void) snprintf(guidstring, sizeof (guidstring),
+ "0x%llx", (longlong_t)guid);
+ fnvlist_add_nvlist(sd->fss, guidstring, nvfs);
+
+ /* iterate over children */
+ if (sd->recursive)
+ rv = zfs_iter_filesystems(zhp, send_iterate_fs, sd);
+
+out:
+ sd->parent_fromsnap_guid = parent_fromsnap_guid_save;
+ sd->fromsnap_txg = fromsnap_txg_save;
+ sd->tosnap_txg = tosnap_txg_save;
+ fnvlist_free(nv);
+ fnvlist_free(nvfs);
+
+ zfs_close(zhp);
+ return (rv);
+}
+
+static int
+gather_nvlist(libzfs_handle_t *hdl, const char *fsname, const char *fromsnap,
+ const char *tosnap, boolean_t recursive, boolean_t raw, boolean_t doall,
+ boolean_t replicate, boolean_t verbose, boolean_t backup, boolean_t holds,
+ boolean_t props, nvlist_t **nvlp, avl_tree_t **avlp)
+{
+ zfs_handle_t *zhp;
+ send_data_t sd = { 0 };
+ int error;
+
+ zhp = zfs_open(hdl, fsname, ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME);
+ if (zhp == NULL)
+ return (EZFS_BADTYPE);
+
+ VERIFY(0 == nvlist_alloc(&sd.fss, NV_UNIQUE_NAME, 0));
+ sd.fsname = fsname;
+ sd.fromsnap = fromsnap;
+ sd.tosnap = tosnap;
+ sd.recursive = recursive;
+ sd.raw = raw;
+ sd.doall = doall;
+ sd.replicate = replicate;
+ sd.verbose = verbose;
+ sd.backup = backup;
+ sd.holds = holds;
+ sd.props = props;
+
+ if ((error = send_iterate_fs(zhp, &sd)) != 0) {
+ nvlist_free(sd.fss);
+ if (avlp != NULL)
+ *avlp = NULL;
+ *nvlp = NULL;
+ return (error);
+ }
+
+ if (avlp != NULL && (*avlp = fsavl_create(sd.fss)) == NULL) {
+ nvlist_free(sd.fss);
+ *nvlp = NULL;
+ return (EZFS_NOMEM);
+ }
+
+ *nvlp = sd.fss;
+ return (0);
+}
+
+/*
+ * Routines specific to "zfs send"
+ */
+typedef struct send_dump_data {
+ /* these are all just the short snapname (the part after the @) */
+ const char *fromsnap;
+ const char *tosnap;
+ char prevsnap[ZFS_MAX_DATASET_NAME_LEN];
+ uint64_t prevsnap_obj;
+ boolean_t seenfrom, seento, replicate, doall, fromorigin;
+ boolean_t dryrun, parsable, progress, embed_data, std_out;
+ boolean_t large_block, compress, raw, holds;
+ int outfd;
+ boolean_t err;
+ nvlist_t *fss;
+ nvlist_t *snapholds;
+ avl_tree_t *fsavl;
+ snapfilter_cb_t *filter_cb;
+ void *filter_cb_arg;
+ nvlist_t *debugnv;
+ char holdtag[ZFS_MAX_DATASET_NAME_LEN];
+ int cleanup_fd;
+ int verbosity;
+ uint64_t size;
+} send_dump_data_t;
+
+static int
+zfs_send_space(zfs_handle_t *zhp, const char *snapname, const char *from,
+ enum lzc_send_flags flags, uint64_t *spacep)
+{
+ libzfs_handle_t *hdl = zhp->zfs_hdl;
+ int error;
+
+ assert(snapname != NULL);
+ error = lzc_send_space(snapname, from, flags, spacep);
+
+ if (error != 0) {
+ char errbuf[1024];
+ (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
+ "warning: cannot estimate space for '%s'"), snapname);
+
+ switch (error) {
+ case EXDEV:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "not an earlier snapshot from the same fs"));
+ return (zfs_error(hdl, EZFS_CROSSTARGET, errbuf));
+
+ case ENOENT:
+ if (zfs_dataset_exists(hdl, snapname,
+ ZFS_TYPE_SNAPSHOT)) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "incremental source (%s) does not exist"),
+ snapname);
+ }
+ return (zfs_error(hdl, EZFS_NOENT, errbuf));
+
+ case EDQUOT:
+ case EFBIG:
+ case EIO:
+ case ENOLINK:
+ case ENOSPC:
+ case ENOSTR:
+ case ENXIO:
+ case EPIPE:
+ case ERANGE:
+ case EFAULT:
+ case EROFS:
+ case EINVAL:
+ zfs_error_aux(hdl, strerror(error));
+ return (zfs_error(hdl, EZFS_BADBACKUP, errbuf));
+
+ default:
+ return (zfs_standard_error(hdl, error, errbuf));
+ }
+ }
+
+ return (0);
+}
+
+/*
+ * Dumps a backup of the given snapshot (incremental from fromsnap if it's not
+ * NULL) to the file descriptor specified by outfd.
+ */
+static int
+dump_ioctl(zfs_handle_t *zhp, const char *fromsnap, uint64_t fromsnap_obj,
+ boolean_t fromorigin, int outfd, enum lzc_send_flags flags,
+ nvlist_t *debugnv)
+{
+ zfs_cmd_t zc = {"\0"};
+ libzfs_handle_t *hdl = zhp->zfs_hdl;
+ nvlist_t *thisdbg;
+
+ assert(zhp->zfs_type == ZFS_TYPE_SNAPSHOT);
+ assert(fromsnap_obj == 0 || !fromorigin);
+
+ (void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name));
+ zc.zc_cookie = outfd;
+ zc.zc_obj = fromorigin;
+ zc.zc_sendobj = zfs_prop_get_int(zhp, ZFS_PROP_OBJSETID);
+ zc.zc_fromobj = fromsnap_obj;
+ zc.zc_flags = flags;
+
+ VERIFY(0 == nvlist_alloc(&thisdbg, NV_UNIQUE_NAME, 0));
+ if (fromsnap && fromsnap[0] != '\0') {
+ VERIFY(0 == nvlist_add_string(thisdbg,
+ "fromsnap", fromsnap));
+ }
+
+ if (zfs_ioctl(zhp->zfs_hdl, ZFS_IOC_SEND, &zc) != 0) {
+ char errbuf[1024];
+ (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
+ "warning: cannot send '%s'"), zhp->zfs_name);
+
+ VERIFY(0 == nvlist_add_uint64(thisdbg, "error", errno));
+ if (debugnv) {
+ VERIFY(0 == nvlist_add_nvlist(debugnv,
+ zhp->zfs_name, thisdbg));
+ }
+ nvlist_free(thisdbg);
+
+ switch (errno) {
+ case EXDEV:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "not an earlier snapshot from the same fs"));
+ return (zfs_error(hdl, EZFS_CROSSTARGET, errbuf));
+
+ case EACCES:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "source key must be loaded"));
+ return (zfs_error(hdl, EZFS_CRYPTOFAILED, errbuf));
+
+ case ENOENT:
+ if (zfs_dataset_exists(hdl, zc.zc_name,
+ ZFS_TYPE_SNAPSHOT)) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "incremental source (@%s) does not exist"),
+ zc.zc_value);
+ }
+ return (zfs_error(hdl, EZFS_NOENT, errbuf));
+
+ case EDQUOT:
+ case EFBIG:
+ case EIO:
+ case ENOLINK:
+ case ENOSPC:
+ case ENOSTR:
+ case ENXIO:
+ case EPIPE:
+ case ERANGE:
+ case EFAULT:
+ case EROFS:
+ zfs_error_aux(hdl, strerror(errno));
+ return (zfs_error(hdl, EZFS_BADBACKUP, errbuf));
+
+ default:
+ return (zfs_standard_error(hdl, errno, errbuf));
+ }
+ }
+
+ if (debugnv)
+ VERIFY(0 == nvlist_add_nvlist(debugnv, zhp->zfs_name, thisdbg));
+ nvlist_free(thisdbg);
+
+ return (0);
+}
+
+static void
+gather_holds(zfs_handle_t *zhp, send_dump_data_t *sdd)
+{
+ assert(zhp->zfs_type == ZFS_TYPE_SNAPSHOT);
+
+ /*
+ * zfs_send() only sets snapholds for sends that need them,
+ * e.g. replication and doall.
+ */
+ if (sdd->snapholds == NULL)
+ return;
+
+ fnvlist_add_string(sdd->snapholds, zhp->zfs_name, sdd->holdtag);
+}
+
+int
+zfs_send_progress(zfs_handle_t *zhp, int fd, uint64_t *bytes_written,
+ uint64_t *blocks_visited)
+{
+ zfs_cmd_t zc = {"\0"};
+
+ (void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name));
+ zc.zc_cookie = fd;
+ if (zfs_ioctl(zhp->zfs_hdl, ZFS_IOC_SEND_PROGRESS, &zc) != 0)
+ return (errno);
+ if (bytes_written != NULL)
+ *bytes_written = zc.zc_cookie;
+ if (blocks_visited != NULL)
+ *blocks_visited = zc.zc_objset_type;
+ return (0);
+}
+
+static void *
+send_progress_thread(void *arg)
+{
+ progress_arg_t *pa = arg;
+ zfs_handle_t *zhp = pa->pa_zhp;
+ uint64_t bytes;
+ uint64_t blocks;
+ char buf[16];
+ time_t t;
+ struct tm *tm;
+ boolean_t firstloop = B_TRUE;
+
+ /*
+ * Print the progress from ZFS_IOC_SEND_PROGRESS every second.
+ */
+ for (;;) {
+ int err;
+ (void) sleep(1);
+ if ((err = zfs_send_progress(zhp, pa->pa_fd, &bytes,
+ &blocks)) != 0) {
+ if (err == EINTR || err == ENOENT)
+ return ((void *)0);
+ return ((void *)(uintptr_t)err);
+ }
+
+ if (firstloop && !pa->pa_parsable) {
+ (void) fprintf(stderr,
+ "TIME %s %sSNAPSHOT %s\n",
+ pa->pa_estimate ? "BYTES" : " SENT",
+ pa->pa_verbosity >= 2 ? " BLOCKS " : "",
+ zhp->zfs_name);
+ firstloop = B_FALSE;
+ }
+
+ (void) time(&t);
+ tm = localtime(&t);
+
+ if (pa->pa_verbosity >= 2 && pa->pa_parsable) {
+ (void) fprintf(stderr,
+ "%02d:%02d:%02d\t%llu\t%llu\t%s\n",
+ tm->tm_hour, tm->tm_min, tm->tm_sec,
+ (u_longlong_t)bytes, (u_longlong_t)blocks,
+ zhp->zfs_name);
+ } else if (pa->pa_verbosity >= 2) {
+ zfs_nicenum(bytes, buf, sizeof (buf));
+ (void) fprintf(stderr,
+ "%02d:%02d:%02d %5s %8llu %s\n",
+ tm->tm_hour, tm->tm_min, tm->tm_sec,
+ buf, (u_longlong_t)blocks, zhp->zfs_name);
+ } else if (pa->pa_parsable) {
+ (void) fprintf(stderr, "%02d:%02d:%02d\t%llu\t%s\n",
+ tm->tm_hour, tm->tm_min, tm->tm_sec,
+ (u_longlong_t)bytes, zhp->zfs_name);
+ } else {
+ zfs_nicebytes(bytes, buf, sizeof (buf));
+ (void) fprintf(stderr, "%02d:%02d:%02d %5s %s\n",
+ tm->tm_hour, tm->tm_min, tm->tm_sec,
+ buf, zhp->zfs_name);
+ }
+ }
+}
+
+static void
+send_print_verbose(FILE *fout, const char *tosnap, const char *fromsnap,
+ uint64_t size, boolean_t parsable)
+{
+ if (parsable) {
+ if (fromsnap != NULL) {
+ (void) fprintf(fout, "incremental\t%s\t%s",
+ fromsnap, tosnap);
+ } else {
+ (void) fprintf(fout, "full\t%s",
+ tosnap);
+ }
+ } else {
+ if (fromsnap != NULL) {
+ if (strchr(fromsnap, '@') == NULL &&
+ strchr(fromsnap, '#') == NULL) {
+ (void) fprintf(fout, dgettext(TEXT_DOMAIN,
+ "send from @%s to %s"),
+ fromsnap, tosnap);
+ } else {
+ (void) fprintf(fout, dgettext(TEXT_DOMAIN,
+ "send from %s to %s"),
+ fromsnap, tosnap);
+ }
+ } else {
+ (void) fprintf(fout, dgettext(TEXT_DOMAIN,
+ "full send of %s"),
+ tosnap);
+ }
+ }
+
+ if (parsable) {
+ (void) fprintf(fout, "\t%llu",
+ (longlong_t)size);
+ } else if (size != 0) {
+ char buf[16];
+ zfs_nicebytes(size, buf, sizeof (buf));
+ (void) fprintf(fout, dgettext(TEXT_DOMAIN,
+ " estimated size is %s"), buf);
+ }
+ (void) fprintf(fout, "\n");
+}
+
+static int
+dump_snapshot(zfs_handle_t *zhp, void *arg)
+{
+ send_dump_data_t *sdd = arg;
+ progress_arg_t pa = { 0 };
+ pthread_t tid;
+ char *thissnap;
+ enum lzc_send_flags flags = 0;
+ int err;
+ boolean_t isfromsnap, istosnap, fromorigin;
+ boolean_t exclude = B_FALSE;
+ FILE *fout = sdd->std_out ? stdout : stderr;
+
+ err = 0;
+ thissnap = strchr(zhp->zfs_name, '@') + 1;
+ isfromsnap = (sdd->fromsnap != NULL &&
+ strcmp(sdd->fromsnap, thissnap) == 0);
+
+ if (!sdd->seenfrom && isfromsnap) {
+ gather_holds(zhp, sdd);
+ sdd->seenfrom = B_TRUE;
+ (void) strlcpy(sdd->prevsnap, thissnap,
+ sizeof (sdd->prevsnap));
+ sdd->prevsnap_obj = zfs_prop_get_int(zhp, ZFS_PROP_OBJSETID);
+ zfs_close(zhp);
+ return (0);
+ }
+
+ if (sdd->seento || !sdd->seenfrom) {
+ zfs_close(zhp);
+ return (0);
+ }
+
+ istosnap = (strcmp(sdd->tosnap, thissnap) == 0);
+ if (istosnap)
+ sdd->seento = B_TRUE;
+
+ if (sdd->large_block)
+ flags |= LZC_SEND_FLAG_LARGE_BLOCK;
+ if (sdd->embed_data)
+ flags |= LZC_SEND_FLAG_EMBED_DATA;
+ if (sdd->compress)
+ flags |= LZC_SEND_FLAG_COMPRESS;
+ if (sdd->raw)
+ flags |= LZC_SEND_FLAG_RAW;
+
+ if (!sdd->doall && !isfromsnap && !istosnap) {
+ if (sdd->replicate) {
+ char *snapname;
+ nvlist_t *snapprops;
+ /*
+ * Filter out all intermediate snapshots except origin
+ * snapshots needed to replicate clones.
+ */
+ nvlist_t *nvfs = fsavl_find(sdd->fsavl,
+ zhp->zfs_dmustats.dds_guid, &snapname);
+
+ VERIFY(0 == nvlist_lookup_nvlist(nvfs,
+ "snapprops", &snapprops));
+ VERIFY(0 == nvlist_lookup_nvlist(snapprops,
+ thissnap, &snapprops));
+ exclude = !nvlist_exists(snapprops, "is_clone_origin");
+ } else {
+ exclude = B_TRUE;
+ }
+ }
+
+ /*
+ * If a filter function exists, call it to determine whether
+ * this snapshot will be sent.
+ */
+ if (exclude || (sdd->filter_cb != NULL &&
+ sdd->filter_cb(zhp, sdd->filter_cb_arg) == B_FALSE)) {
+ /*
+ * This snapshot is filtered out. Don't send it, and don't
+ * set prevsnap_obj, so it will be as if this snapshot didn't
+ * exist, and the next accepted snapshot will be sent as
+ * an incremental from the last accepted one, or as the
+ * first (and full) snapshot in the case of a replication,
+ * non-incremental send.
+ */
+ zfs_close(zhp);
+ return (0);
+ }
+
+ gather_holds(zhp, sdd);
+ fromorigin = sdd->prevsnap[0] == '\0' &&
+ (sdd->fromorigin || sdd->replicate);
+
+ if (sdd->verbosity != 0) {
+ uint64_t size = 0;
+ char fromds[ZFS_MAX_DATASET_NAME_LEN];
+
+ if (sdd->prevsnap[0] != '\0') {
+ (void) strlcpy(fromds, zhp->zfs_name, sizeof (fromds));
+ *(strchr(fromds, '@') + 1) = '\0';
+ (void) strlcat(fromds, sdd->prevsnap, sizeof (fromds));
+ }
+ if (zfs_send_space(zhp, zhp->zfs_name,
+ sdd->prevsnap[0] ? fromds : NULL, flags, &size) != 0) {
+ size = 0; /* cannot estimate send space */
+ } else {
+ send_print_verbose(fout, zhp->zfs_name,
+ sdd->prevsnap[0] ? sdd->prevsnap : NULL,
+ size, sdd->parsable);
+ }
+ sdd->size += size;
+ }
+
+ if (!sdd->dryrun) {
+ /*
+ * If progress reporting is requested, spawn a new thread to
+ * poll ZFS_IOC_SEND_PROGRESS at a regular interval.
+ */
+ if (sdd->progress) {
+ pa.pa_zhp = zhp;
+ pa.pa_fd = sdd->outfd;
+ pa.pa_parsable = sdd->parsable;
+ pa.pa_estimate = B_FALSE;
+ pa.pa_verbosity = sdd->verbosity;
+
+ if ((err = pthread_create(&tid, NULL,
+ send_progress_thread, &pa)) != 0) {
+ zfs_close(zhp);
+ return (err);
+ }
+ }
+
+ err = dump_ioctl(zhp, sdd->prevsnap, sdd->prevsnap_obj,
+ fromorigin, sdd->outfd, flags, sdd->debugnv);
+
+ if (sdd->progress) {
+ void *status = NULL;
+ (void) pthread_cancel(tid);
+ (void) pthread_join(tid, &status);
+ int error = (int)(uintptr_t)status;
+ if (error != 0 && status != PTHREAD_CANCELED) {
+ char errbuf[1024];
+ (void) snprintf(errbuf, sizeof (errbuf),
+ dgettext(TEXT_DOMAIN,
+ "progress thread exited nonzero"));
+ return (zfs_standard_error(zhp->zfs_hdl, error,
+ errbuf));
+ }
+ }
+ }
+
+ (void) strcpy(sdd->prevsnap, thissnap);
+ sdd->prevsnap_obj = zfs_prop_get_int(zhp, ZFS_PROP_OBJSETID);
+ zfs_close(zhp);
+ return (err);
+}
+
+static int
+dump_filesystem(zfs_handle_t *zhp, void *arg)
+{
+ int rv = 0;
+ send_dump_data_t *sdd = arg;
+ boolean_t missingfrom = B_FALSE;
+ zfs_cmd_t zc = {"\0"};
+ uint64_t min_txg = 0, max_txg = 0;
+
+ (void) snprintf(zc.zc_name, sizeof (zc.zc_name), "%s@%s",
+ zhp->zfs_name, sdd->tosnap);
+ if (zfs_ioctl(zhp->zfs_hdl, ZFS_IOC_OBJSET_STATS, &zc) != 0) {
+ (void) fprintf(stderr, dgettext(TEXT_DOMAIN,
+ "WARNING: could not send %s@%s: does not exist\n"),
+ zhp->zfs_name, sdd->tosnap);
+ sdd->err = B_TRUE;
+ return (0);
+ }
+
+ if (sdd->replicate && sdd->fromsnap) {
+ /*
+ * If this fs does not have fromsnap, and we're doing
+ * recursive, we need to send a full stream from the
+ * beginning (or an incremental from the origin if this
+ * is a clone). If we're doing non-recursive, then let
+ * them get the error.
+ */
+ (void) snprintf(zc.zc_name, sizeof (zc.zc_name), "%s@%s",
+ zhp->zfs_name, sdd->fromsnap);
+ if (zfs_ioctl(zhp->zfs_hdl,
+ ZFS_IOC_OBJSET_STATS, &zc) != 0) {
+ missingfrom = B_TRUE;
+ }
+ }
+
+ sdd->seenfrom = sdd->seento = sdd->prevsnap[0] = 0;
+ sdd->prevsnap_obj = 0;
+ if (sdd->fromsnap == NULL || missingfrom)
+ sdd->seenfrom = B_TRUE;
+
+
+
+ /*
+ * Iterate through all snapshots and process the ones we will be
+ * sending. If we only have a "from" and "to" snapshot to deal
+ * with, we can avoid iterating through all the other snapshots.
+ */
+ if (sdd->doall || sdd->replicate || sdd->tosnap == NULL) {
+ if (!sdd->replicate && sdd->fromsnap != NULL)
+ min_txg = get_snap_txg(zhp->zfs_hdl, zhp->zfs_name,
+ sdd->fromsnap);
+ if (!sdd->replicate && sdd->tosnap != NULL)
+ max_txg = get_snap_txg(zhp->zfs_hdl, zhp->zfs_name,
+ sdd->tosnap);
+ rv = zfs_iter_snapshots_sorted(zhp, dump_snapshot, arg,
+ min_txg, max_txg);
+ } else {
+ char snapname[MAXPATHLEN] = { 0 };
+ zfs_handle_t *snap;
+
+ if (!sdd->seenfrom) {
+ (void) snprintf(snapname, sizeof (snapname),
+ "%s@%s", zhp->zfs_name, sdd->fromsnap);
+ snap = zfs_open(zhp->zfs_hdl, snapname,
+ ZFS_TYPE_SNAPSHOT);
+ if (snap != NULL)
+ rv = dump_snapshot(snap, sdd);
+ else
+ rv = -1;
+ }
+
+ if (rv == 0) {
+ (void) snprintf(snapname, sizeof (snapname),
+ "%s@%s", zhp->zfs_name, sdd->tosnap);
+ snap = zfs_open(zhp->zfs_hdl, snapname,
+ ZFS_TYPE_SNAPSHOT);
+ if (snap != NULL)
+ rv = dump_snapshot(snap, sdd);
+ else
+ rv = -1;
+ }
+ }
+
+ if (!sdd->seenfrom) {
+ (void) fprintf(stderr, dgettext(TEXT_DOMAIN,
+ "WARNING: could not send %s@%s:\n"
+ "incremental source (%s@%s) does not exist\n"),
+ zhp->zfs_name, sdd->tosnap,
+ zhp->zfs_name, sdd->fromsnap);
+ sdd->err = B_TRUE;
+ } else if (!sdd->seento) {
+ if (sdd->fromsnap) {
+ (void) fprintf(stderr, dgettext(TEXT_DOMAIN,
+ "WARNING: could not send %s@%s:\n"
+ "incremental source (%s@%s) "
+ "is not earlier than it\n"),
+ zhp->zfs_name, sdd->tosnap,
+ zhp->zfs_name, sdd->fromsnap);
+ } else {
+ (void) fprintf(stderr, dgettext(TEXT_DOMAIN,
+ "WARNING: "
+ "could not send %s@%s: does not exist\n"),
+ zhp->zfs_name, sdd->tosnap);
+ }
+ sdd->err = B_TRUE;
+ }
+
+ return (rv);
+}
+
+static int
+dump_filesystems(zfs_handle_t *rzhp, void *arg)
+{
+ send_dump_data_t *sdd = arg;
+ nvpair_t *fspair;
+ boolean_t needagain, progress;
+
+ if (!sdd->replicate)
+ return (dump_filesystem(rzhp, sdd));
+
+ /* Mark the clone origin snapshots. */
+ for (fspair = nvlist_next_nvpair(sdd->fss, NULL); fspair;
+ fspair = nvlist_next_nvpair(sdd->fss, fspair)) {
+ nvlist_t *nvfs;
+ uint64_t origin_guid = 0;
+
+ VERIFY(0 == nvpair_value_nvlist(fspair, &nvfs));
+ (void) nvlist_lookup_uint64(nvfs, "origin", &origin_guid);
+ if (origin_guid != 0) {
+ char *snapname;
+ nvlist_t *origin_nv = fsavl_find(sdd->fsavl,
+ origin_guid, &snapname);
+ if (origin_nv != NULL) {
+ nvlist_t *snapprops;
+ VERIFY(0 == nvlist_lookup_nvlist(origin_nv,
+ "snapprops", &snapprops));
+ VERIFY(0 == nvlist_lookup_nvlist(snapprops,
+ snapname, &snapprops));
+ VERIFY(0 == nvlist_add_boolean(
+ snapprops, "is_clone_origin"));
+ }
+ }
+ }
+again:
+ needagain = progress = B_FALSE;
+ for (fspair = nvlist_next_nvpair(sdd->fss, NULL); fspair;
+ fspair = nvlist_next_nvpair(sdd->fss, fspair)) {
+ nvlist_t *fslist, *parent_nv;
+ char *fsname;
+ zfs_handle_t *zhp;
+ int err;
+ uint64_t origin_guid = 0;
+ uint64_t parent_guid = 0;
+
+ VERIFY(nvpair_value_nvlist(fspair, &fslist) == 0);
+ if (nvlist_lookup_boolean(fslist, "sent") == 0)
+ continue;
+
+ VERIFY(nvlist_lookup_string(fslist, "name", &fsname) == 0);
+ (void) nvlist_lookup_uint64(fslist, "origin", &origin_guid);
+ (void) nvlist_lookup_uint64(fslist, "parentfromsnap",
+ &parent_guid);
+
+ if (parent_guid != 0) {
+ parent_nv = fsavl_find(sdd->fsavl, parent_guid, NULL);
+ if (!nvlist_exists(parent_nv, "sent")) {
+ /* parent has not been sent; skip this one */
+ needagain = B_TRUE;
+ continue;
+ }
+ }
+
+ if (origin_guid != 0) {
+ nvlist_t *origin_nv = fsavl_find(sdd->fsavl,
+ origin_guid, NULL);
+ if (origin_nv != NULL &&
+ !nvlist_exists(origin_nv, "sent")) {
+ /*
+ * origin has not been sent yet;
+ * skip this clone.
+ */
+ needagain = B_TRUE;
+ continue;
+ }
+ }
+
+ zhp = zfs_open(rzhp->zfs_hdl, fsname, ZFS_TYPE_DATASET);
+ if (zhp == NULL)
+ return (-1);
+ err = dump_filesystem(zhp, sdd);
+ VERIFY(nvlist_add_boolean(fslist, "sent") == 0);
+ progress = B_TRUE;
+ zfs_close(zhp);
+ if (err)
+ return (err);
+ }
+ if (needagain) {
+ assert(progress);
+ goto again;
+ }
+
+ /* clean out the sent flags in case we reuse this fss */
+ for (fspair = nvlist_next_nvpair(sdd->fss, NULL); fspair;
+ fspair = nvlist_next_nvpair(sdd->fss, fspair)) {
+ nvlist_t *fslist;
+
+ VERIFY(nvpair_value_nvlist(fspair, &fslist) == 0);
+ (void) nvlist_remove_all(fslist, "sent");
+ }
+
+ return (0);
+}
+
+nvlist_t *
+zfs_send_resume_token_to_nvlist(libzfs_handle_t *hdl, const char *token)
+{
+ unsigned int version;
+ int nread, i;
+ unsigned long long checksum, packed_len;
+
+ /*
+ * Decode token header, which is:
+ * <token version>-<checksum of payload>-<uncompressed payload length>
+ * Note that the only supported token version is 1.
+ */
+ nread = sscanf(token, "%u-%llx-%llx-",
+ &version, &checksum, &packed_len);
+ if (nread != 3) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "resume token is corrupt (invalid format)"));
+ return (NULL);
+ }
+
+ if (version != ZFS_SEND_RESUME_TOKEN_VERSION) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "resume token is corrupt (invalid version %u)"),
+ version);
+ return (NULL);
+ }
+
+ /* convert hexadecimal representation to binary */
+ token = strrchr(token, '-') + 1;
+ int len = strlen(token) / 2;
+ unsigned char *compressed = zfs_alloc(hdl, len);
+ for (i = 0; i < len; i++) {
+ nread = sscanf(token + i * 2, "%2hhx", compressed + i);
+ if (nread != 1) {
+ free(compressed);
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "resume token is corrupt "
+ "(payload is not hex-encoded)"));
+ return (NULL);
+ }
+ }
+
+ /* verify checksum */
+ zio_cksum_t cksum;
+ fletcher_4_native_varsize(compressed, len, &cksum);
+ if (cksum.zc_word[0] != checksum) {
+ free(compressed);
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "resume token is corrupt (incorrect checksum)"));
+ return (NULL);
+ }
+
+ /* uncompress */
+ void *packed = zfs_alloc(hdl, packed_len);
+ uLongf packed_len_long = packed_len;
+ if (uncompress(packed, &packed_len_long, compressed, len) != Z_OK ||
+ packed_len_long != packed_len) {
+ free(packed);
+ free(compressed);
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "resume token is corrupt (decompression failed)"));
+ return (NULL);
+ }
+
+ /* unpack nvlist */
+ nvlist_t *nv;
+ int error = nvlist_unpack(packed, packed_len, &nv, KM_SLEEP);
+ free(packed);
+ free(compressed);
+ if (error != 0) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "resume token is corrupt (nvlist_unpack failed)"));
+ return (NULL);
+ }
+ return (nv);
+}
+static enum lzc_send_flags
+lzc_flags_from_sendflags(const sendflags_t *flags)
+{
+ enum lzc_send_flags lzc_flags = 0;
+ if (flags->largeblock)
+ lzc_flags |= LZC_SEND_FLAG_LARGE_BLOCK;
+ if (flags->embed_data)
+ lzc_flags |= LZC_SEND_FLAG_EMBED_DATA;
+ if (flags->compress)
+ lzc_flags |= LZC_SEND_FLAG_COMPRESS;
+ if (flags->raw)
+ lzc_flags |= LZC_SEND_FLAG_RAW;
+ if (flags->saved)
+ lzc_flags |= LZC_SEND_FLAG_SAVED;
+ return (lzc_flags);
+}
+
+static int
+estimate_size(zfs_handle_t *zhp, const char *from, int fd, sendflags_t *flags,
+ uint64_t resumeobj, uint64_t resumeoff, uint64_t bytes,
+ const char *redactbook, char *errbuf)
+{
+ uint64_t size;
+ FILE *fout = flags->dryrun ? stdout : stderr;
+ progress_arg_t pa = { 0 };
+ int err = 0;
+ pthread_t ptid;
+
+ if (flags->progress) {
+ pa.pa_zhp = zhp;
+ pa.pa_fd = fd;
+ pa.pa_parsable = flags->parsable;
+ pa.pa_estimate = B_TRUE;
+ pa.pa_verbosity = flags->verbosity;
+
+ err = pthread_create(&ptid, NULL,
+ send_progress_thread, &pa);
+ if (err != 0) {
+ zfs_error_aux(zhp->zfs_hdl, strerror(errno));
+ return (zfs_error(zhp->zfs_hdl,
+ EZFS_THREADCREATEFAILED, errbuf));
+ }
+ }
+
+ err = lzc_send_space_resume_redacted(zhp->zfs_name, from,
+ lzc_flags_from_sendflags(flags), resumeobj, resumeoff, bytes,
+ redactbook, fd, &size);
+
+ if (flags->progress) {
+ void *status = NULL;
+ (void) pthread_cancel(ptid);
+ (void) pthread_join(ptid, &status);
+ int error = (int)(uintptr_t)status;
+ if (error != 0 && status != PTHREAD_CANCELED) {
+ char errbuf[1024];
+ (void) snprintf(errbuf, sizeof (errbuf),
+ dgettext(TEXT_DOMAIN, "progress thread exited "
+ "nonzero"));
+ return (zfs_standard_error(zhp->zfs_hdl, error,
+ errbuf));
+ }
+ }
+
+ if (err != 0) {
+ zfs_error_aux(zhp->zfs_hdl, strerror(err));
+ return (zfs_error(zhp->zfs_hdl, EZFS_BADBACKUP,
+ errbuf));
+ }
+ send_print_verbose(fout, zhp->zfs_name, from, size,
+ flags->parsable);
+
+ if (flags->parsable) {
+ (void) fprintf(fout, "size\t%llu\n", (longlong_t)size);
+ } else {
+ char buf[16];
+ zfs_nicenum(size, buf, sizeof (buf));
+ (void) fprintf(fout, dgettext(TEXT_DOMAIN,
+ "total estimated size is %s\n"), buf);
+ }
+ return (0);
+}
+
+static boolean_t
+redact_snaps_contains(const uint64_t *snaps, uint64_t num_snaps, uint64_t guid)
+{
+ for (int i = 0; i < num_snaps; i++) {
+ if (snaps[i] == guid)
+ return (B_TRUE);
+ }
+ return (B_FALSE);
+}
+
+static boolean_t
+redact_snaps_equal(const uint64_t *snaps1, uint64_t num_snaps1,
+ const uint64_t *snaps2, uint64_t num_snaps2)
+{
+ if (num_snaps1 != num_snaps2)
+ return (B_FALSE);
+ for (int i = 0; i < num_snaps1; i++) {
+ if (!redact_snaps_contains(snaps2, num_snaps2, snaps1[i]))
+ return (B_FALSE);
+ }
+ return (B_TRUE);
+}
+
+/*
+ * Check that the list of redaction snapshots in the bookmark matches the send
+ * we're resuming, and return whether or not it's complete.
+ *
+ * Note that the caller needs to free the contents of *bookname with free() if
+ * this function returns successfully.
+ */
+static int
+find_redact_book(libzfs_handle_t *hdl, const char *path,
+ const uint64_t *redact_snap_guids, int num_redact_snaps,
+ char **bookname)
+{
+ char errbuf[1024];
+ int error = 0;
+ nvlist_t *props = fnvlist_alloc();
+ nvlist_t *bmarks;
+
+ (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
+ "cannot resume send"));
+
+ fnvlist_add_boolean(props, "redact_complete");
+ fnvlist_add_boolean(props, zfs_prop_to_name(ZFS_PROP_REDACT_SNAPS));
+ error = lzc_get_bookmarks(path, props, &bmarks);
+ nvlist_free(props);
+ if (error != 0) {
+ if (error == ESRCH) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "nonexistent redaction bookmark provided"));
+ } else if (error == ENOENT) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "dataset to be sent no longer exists"));
+ } else {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "unknown error: %s"), strerror(error));
+ }
+ return (zfs_error(hdl, EZFS_BADPROP, errbuf));
+ }
+ nvpair_t *pair;
+ for (pair = nvlist_next_nvpair(bmarks, NULL); pair;
+ pair = nvlist_next_nvpair(bmarks, pair)) {
+
+ nvlist_t *bmark = fnvpair_value_nvlist(pair);
+ nvlist_t *vallist = fnvlist_lookup_nvlist(bmark,
+ zfs_prop_to_name(ZFS_PROP_REDACT_SNAPS));
+ uint_t len = 0;
+ uint64_t *bmarksnaps = fnvlist_lookup_uint64_array(vallist,
+ ZPROP_VALUE, &len);
+ if (redact_snaps_equal(redact_snap_guids,
+ num_redact_snaps, bmarksnaps, len)) {
+ break;
+ }
+ }
+ if (pair == NULL) {
+ fnvlist_free(bmarks);
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "no appropriate redaction bookmark exists"));
+ return (zfs_error(hdl, EZFS_BADPROP, errbuf));
+ }
+ char *name = nvpair_name(pair);
+ nvlist_t *bmark = fnvpair_value_nvlist(pair);
+ nvlist_t *vallist = fnvlist_lookup_nvlist(bmark, "redact_complete");
+ boolean_t complete = fnvlist_lookup_boolean_value(vallist,
+ ZPROP_VALUE);
+ if (!complete) {
+ fnvlist_free(bmarks);
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "incomplete redaction bookmark provided"));
+ return (zfs_error(hdl, EZFS_BADPROP, errbuf));
+ }
+ *bookname = strndup(name, ZFS_MAX_DATASET_NAME_LEN);
+ ASSERT3P(*bookname, !=, NULL);
+ fnvlist_free(bmarks);
+ return (0);
+}
+
+static int
+zfs_send_resume_impl(libzfs_handle_t *hdl, sendflags_t *flags, int outfd,
+ nvlist_t *resume_nvl)
+{
+ char errbuf[1024];
+ char *toname;
+ char *fromname = NULL;
+ uint64_t resumeobj, resumeoff, toguid, fromguid, bytes;
+ zfs_handle_t *zhp;
+ int error = 0;
+ char name[ZFS_MAX_DATASET_NAME_LEN];
+ enum lzc_send_flags lzc_flags = 0;
+ FILE *fout = (flags->verbosity > 0 && flags->dryrun) ? stdout : stderr;
+ uint64_t *redact_snap_guids = NULL;
+ int num_redact_snaps = 0;
+ char *redact_book = NULL;
+
+ (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
+ "cannot resume send"));
+
+ if (flags->verbosity != 0) {
+ (void) fprintf(fout, dgettext(TEXT_DOMAIN,
+ "resume token contents:\n"));
+ nvlist_print(fout, resume_nvl);
+ }
+
+ if (nvlist_lookup_string(resume_nvl, "toname", &toname) != 0 ||
+ nvlist_lookup_uint64(resume_nvl, "object", &resumeobj) != 0 ||
+ nvlist_lookup_uint64(resume_nvl, "offset", &resumeoff) != 0 ||
+ nvlist_lookup_uint64(resume_nvl, "bytes", &bytes) != 0 ||
+ nvlist_lookup_uint64(resume_nvl, "toguid", &toguid) != 0) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "resume token is corrupt"));
+ return (zfs_error(hdl, EZFS_FAULT, errbuf));
+ }
+ fromguid = 0;
+ (void) nvlist_lookup_uint64(resume_nvl, "fromguid", &fromguid);
+
+ if (flags->largeblock || nvlist_exists(resume_nvl, "largeblockok"))
+ lzc_flags |= LZC_SEND_FLAG_LARGE_BLOCK;
+ if (flags->embed_data || nvlist_exists(resume_nvl, "embedok"))
+ lzc_flags |= LZC_SEND_FLAG_EMBED_DATA;
+ if (flags->compress || nvlist_exists(resume_nvl, "compressok"))
+ lzc_flags |= LZC_SEND_FLAG_COMPRESS;
+ if (flags->raw || nvlist_exists(resume_nvl, "rawok"))
+ lzc_flags |= LZC_SEND_FLAG_RAW;
+ if (flags->saved || nvlist_exists(resume_nvl, "savedok"))
+ lzc_flags |= LZC_SEND_FLAG_SAVED;
+
+ if (flags->saved) {
+ (void) strcpy(name, toname);
+ } else {
+ error = guid_to_name(hdl, toname, toguid, B_FALSE, name);
+ if (error != 0) {
+ if (zfs_dataset_exists(hdl, toname, ZFS_TYPE_DATASET)) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "'%s' is no longer the same snapshot "
+ "used in the initial send"), toname);
+ } else {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "'%s' used in the initial send no "
+ "longer exists"), toname);
+ }
+ return (zfs_error(hdl, EZFS_BADPATH, errbuf));
+ }
+ }
+
+ zhp = zfs_open(hdl, name, ZFS_TYPE_DATASET);
+ if (zhp == NULL) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "unable to access '%s'"), name);
+ return (zfs_error(hdl, EZFS_BADPATH, errbuf));
+ }
+
+ if (nvlist_lookup_uint64_array(resume_nvl, "book_redact_snaps",
+ &redact_snap_guids, (uint_t *)&num_redact_snaps) != 0) {
+ num_redact_snaps = -1;
+ }
+
+ if (fromguid != 0) {
+ if (guid_to_name_redact_snaps(hdl, toname, fromguid, B_TRUE,
+ redact_snap_guids, num_redact_snaps, name) != 0) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "incremental source %#llx no longer exists"),
+ (longlong_t)fromguid);
+ return (zfs_error(hdl, EZFS_BADPATH, errbuf));
+ }
+ fromname = name;
+ }
+
+ redact_snap_guids = NULL;
+
+ if (nvlist_lookup_uint64_array(resume_nvl,
+ zfs_prop_to_name(ZFS_PROP_REDACT_SNAPS), &redact_snap_guids,
+ (uint_t *)&num_redact_snaps) == 0) {
+ char path[ZFS_MAX_DATASET_NAME_LEN];
+
+ (void) strlcpy(path, toname, sizeof (path));
+ char *at = strchr(path, '@');
+ ASSERT3P(at, !=, NULL);
+
+ *at = '\0';
+
+ if ((error = find_redact_book(hdl, path, redact_snap_guids,
+ num_redact_snaps, &redact_book)) != 0) {
+ return (error);
+ }
+ }
+
+ if (flags->verbosity != 0) {
+ /*
+ * Some of these may have come from the resume token, set them
+ * here for size estimate purposes.
+ */
+ sendflags_t tmpflags = *flags;
+ if (lzc_flags & LZC_SEND_FLAG_LARGE_BLOCK)
+ tmpflags.largeblock = B_TRUE;
+ if (lzc_flags & LZC_SEND_FLAG_COMPRESS)
+ tmpflags.compress = B_TRUE;
+ if (lzc_flags & LZC_SEND_FLAG_EMBED_DATA)
+ tmpflags.embed_data = B_TRUE;
+ error = estimate_size(zhp, fromname, outfd, &tmpflags,
+ resumeobj, resumeoff, bytes, redact_book, errbuf);
+ }
+
+ if (!flags->dryrun) {
+ progress_arg_t pa = { 0 };
+ pthread_t tid;
+ /*
+ * If progress reporting is requested, spawn a new thread to
+ * poll ZFS_IOC_SEND_PROGRESS at a regular interval.
+ */
+ if (flags->progress) {
+ pa.pa_zhp = zhp;
+ pa.pa_fd = outfd;
+ pa.pa_parsable = flags->parsable;
+ pa.pa_estimate = B_FALSE;
+ pa.pa_verbosity = flags->verbosity;
+
+ error = pthread_create(&tid, NULL,
+ send_progress_thread, &pa);
+ if (error != 0) {
+ if (redact_book != NULL)
+ free(redact_book);
+ zfs_close(zhp);
+ return (error);
+ }
+ }
+
+ error = lzc_send_resume_redacted(zhp->zfs_name, fromname, outfd,
+ lzc_flags, resumeobj, resumeoff, redact_book);
+ if (redact_book != NULL)
+ free(redact_book);
+
+ if (flags->progress) {
+ void *status = NULL;
+ (void) pthread_cancel(tid);
+ (void) pthread_join(tid, &status);
+ int error = (int)(uintptr_t)status;
+ if (error != 0 && status != PTHREAD_CANCELED) {
+ char errbuf[1024];
+ (void) snprintf(errbuf, sizeof (errbuf),
+ dgettext(TEXT_DOMAIN,
+ "progress thread exited nonzero"));
+ return (zfs_standard_error(hdl, error, errbuf));
+ }
+ }
+
+ char errbuf[1024];
+ (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
+ "warning: cannot send '%s'"), zhp->zfs_name);
+
+ zfs_close(zhp);
+
+ switch (error) {
+ case 0:
+ return (0);
+ case EACCES:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "source key must be loaded"));
+ return (zfs_error(hdl, EZFS_CRYPTOFAILED, errbuf));
+ case ESRCH:
+ if (lzc_exists(zhp->zfs_name)) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "incremental source could not be found"));
+ }
+ return (zfs_error(hdl, EZFS_NOENT, errbuf));
+
+ case EXDEV:
+ case ENOENT:
+ case EDQUOT:
+ case EFBIG:
+ case EIO:
+ case ENOLINK:
+ case ENOSPC:
+ case ENOSTR:
+ case ENXIO:
+ case EPIPE:
+ case ERANGE:
+ case EFAULT:
+ case EROFS:
+ zfs_error_aux(hdl, strerror(errno));
+ return (zfs_error(hdl, EZFS_BADBACKUP, errbuf));
+
+ default:
+ return (zfs_standard_error(hdl, errno, errbuf));
+ }
+ } else {
+ if (redact_book != NULL)
+ free(redact_book);
+ }
+
+ zfs_close(zhp);
+
+ return (error);
+}
+
+int
+zfs_send_resume(libzfs_handle_t *hdl, sendflags_t *flags, int outfd,
+ const char *resume_token)
+{
+ int ret;
+ char errbuf[1024];
+ nvlist_t *resume_nvl;
+
+ (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
+ "cannot resume send"));
+
+ resume_nvl = zfs_send_resume_token_to_nvlist(hdl, resume_token);
+ if (resume_nvl == NULL) {
+ /*
+ * zfs_error_aux has already been set by
+ * zfs_send_resume_token_to_nvlist()
+ */
+ return (zfs_error(hdl, EZFS_FAULT, errbuf));
+ }
+
+ ret = zfs_send_resume_impl(hdl, flags, outfd, resume_nvl);
+ nvlist_free(resume_nvl);
+
+ return (ret);
+}
+
+int
+zfs_send_saved(zfs_handle_t *zhp, sendflags_t *flags, int outfd,
+ const char *resume_token)
+{
+ int ret;
+ libzfs_handle_t *hdl = zhp->zfs_hdl;
+ nvlist_t *saved_nvl = NULL, *resume_nvl = NULL;
+ uint64_t saved_guid = 0, resume_guid = 0;
+ uint64_t obj = 0, off = 0, bytes = 0;
+ char token_buf[ZFS_MAXPROPLEN];
+ char errbuf[1024];
+
+ (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
+ "saved send failed"));
+
+ ret = zfs_prop_get(zhp, ZFS_PROP_RECEIVE_RESUME_TOKEN,
+ token_buf, sizeof (token_buf), NULL, NULL, 0, B_TRUE);
+ if (ret != 0)
+ goto out;
+
+ saved_nvl = zfs_send_resume_token_to_nvlist(hdl, token_buf);
+ if (saved_nvl == NULL) {
+ /*
+ * zfs_error_aux has already been set by
+ * zfs_send_resume_token_to_nvlist()
+ */
+ ret = zfs_error(hdl, EZFS_FAULT, errbuf);
+ goto out;
+ }
+
+ /*
+ * If a resume token is provided we use the object and offset
+ * from that instead of the default, which starts from the
+ * beginning.
+ */
+ if (resume_token != NULL) {
+ resume_nvl = zfs_send_resume_token_to_nvlist(hdl,
+ resume_token);
+ if (resume_nvl == NULL) {
+ ret = zfs_error(hdl, EZFS_FAULT, errbuf);
+ goto out;
+ }
+
+ if (nvlist_lookup_uint64(resume_nvl, "object", &obj) != 0 ||
+ nvlist_lookup_uint64(resume_nvl, "offset", &off) != 0 ||
+ nvlist_lookup_uint64(resume_nvl, "bytes", &bytes) != 0 ||
+ nvlist_lookup_uint64(resume_nvl, "toguid",
+ &resume_guid) != 0) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "provided resume token is corrupt"));
+ ret = zfs_error(hdl, EZFS_FAULT, errbuf);
+ goto out;
+ }
+
+ if (nvlist_lookup_uint64(saved_nvl, "toguid",
+ &saved_guid)) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "dataset's resume token is corrupt"));
+ ret = zfs_error(hdl, EZFS_FAULT, errbuf);
+ goto out;
+ }
+
+ if (resume_guid != saved_guid) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "provided resume token does not match dataset"));
+ ret = zfs_error(hdl, EZFS_BADBACKUP, errbuf);
+ goto out;
+ }
+ }
+
+ (void) nvlist_remove_all(saved_nvl, "object");
+ fnvlist_add_uint64(saved_nvl, "object", obj);
+
+ (void) nvlist_remove_all(saved_nvl, "offset");
+ fnvlist_add_uint64(saved_nvl, "offset", off);
+
+ (void) nvlist_remove_all(saved_nvl, "bytes");
+ fnvlist_add_uint64(saved_nvl, "bytes", bytes);
+
+ (void) nvlist_remove_all(saved_nvl, "toname");
+ fnvlist_add_string(saved_nvl, "toname", zhp->zfs_name);
+
+ ret = zfs_send_resume_impl(hdl, flags, outfd, saved_nvl);
+
+out:
+ nvlist_free(saved_nvl);
+ nvlist_free(resume_nvl);
+ return (ret);
+}
+
+/*
+ * This function informs the target system that the recursive send is complete.
+ * The record is also expected in the case of a send -p.
+ */
+static int
+send_conclusion_record(int fd, zio_cksum_t *zc)
+{
+ dmu_replay_record_t drr = { 0 };
+ drr.drr_type = DRR_END;
+ if (zc != NULL)
+ drr.drr_u.drr_end.drr_checksum = *zc;
+ if (write(fd, &drr, sizeof (drr)) == -1) {
+ return (errno);
+ }
+ return (0);
+}
+
+/*
+ * This function is responsible for sending the records that contain the
+ * necessary information for the target system's libzfs to be able to set the
+ * properties of the filesystem being received, or to be able to prepare for
+ * a recursive receive.
+ *
+ * The "zhp" argument is the handle of the snapshot we are sending
+ * (the "tosnap"). The "from" argument is the short snapshot name (the part
+ * after the @) of the incremental source.
+ */
+static int
+send_prelim_records(zfs_handle_t *zhp, const char *from, int fd,
+ boolean_t gather_props, boolean_t recursive, boolean_t verbose,
+ boolean_t dryrun, boolean_t raw, boolean_t replicate, boolean_t backup,
+ boolean_t holds, boolean_t props, boolean_t doall,
+ nvlist_t **fssp, avl_tree_t **fsavlp)
+{
+ int err = 0;
+ char *packbuf = NULL;
+ size_t buflen = 0;
+ zio_cksum_t zc = { {0} };
+ int featureflags = 0;
+ /* name of filesystem/volume that contains snapshot we are sending */
+ char tofs[ZFS_MAX_DATASET_NAME_LEN];
+ /* short name of snap we are sending */
+ char *tosnap = "";
+
+ char errbuf[1024];
+ (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
+ "warning: cannot send '%s'"), zhp->zfs_name);
+ if (zhp->zfs_type == ZFS_TYPE_FILESYSTEM && zfs_prop_get_int(zhp,
+ ZFS_PROP_VERSION) >= ZPL_VERSION_SA) {
+ featureflags |= DMU_BACKUP_FEATURE_SA_SPILL;
+ }
+
+ if (holds)
+ featureflags |= DMU_BACKUP_FEATURE_HOLDS;
+
+ (void) strlcpy(tofs, zhp->zfs_name, ZFS_MAX_DATASET_NAME_LEN);
+ char *at = strchr(tofs, '@');
+ if (at != NULL) {
+ *at = '\0';
+ tosnap = at + 1;
+ }
+
+ if (gather_props) {
+ nvlist_t *hdrnv = fnvlist_alloc();
+ nvlist_t *fss = NULL;
+
+ if (from != NULL)
+ fnvlist_add_string(hdrnv, "fromsnap", from);
+ fnvlist_add_string(hdrnv, "tosnap", tosnap);
+ if (!recursive)
+ fnvlist_add_boolean(hdrnv, "not_recursive");
+
+ if (raw) {
+ VERIFY0(nvlist_add_boolean(hdrnv, "raw"));
+ }
+
+ if ((err = gather_nvlist(zhp->zfs_hdl, tofs,
+ from, tosnap, recursive, raw, doall, replicate, verbose,
+ backup, holds, props, &fss, fsavlp)) != 0) {
+ return (zfs_error(zhp->zfs_hdl, EZFS_BADBACKUP,
+ errbuf));
+ }
+ fnvlist_add_nvlist(hdrnv, "fss", fss);
+ VERIFY0(nvlist_pack(hdrnv, &packbuf, &buflen, NV_ENCODE_XDR,
+ 0));
+ if (fssp != NULL) {
+ *fssp = fss;
+ } else {
+ nvlist_free(fss);
+ }
+ nvlist_free(hdrnv);
+ }
+
+ if (!dryrun) {
+ dmu_replay_record_t drr = { 0 };
+ /* write first begin record */
+ drr.drr_type = DRR_BEGIN;
+ drr.drr_u.drr_begin.drr_magic = DMU_BACKUP_MAGIC;
+ DMU_SET_STREAM_HDRTYPE(drr.drr_u.drr_begin.
+ drr_versioninfo, DMU_COMPOUNDSTREAM);
+ DMU_SET_FEATUREFLAGS(drr.drr_u.drr_begin.
+ drr_versioninfo, featureflags);
+ if (snprintf(drr.drr_u.drr_begin.drr_toname,
+ sizeof (drr.drr_u.drr_begin.drr_toname), "%s@%s", tofs,
+ tosnap) >= sizeof (drr.drr_u.drr_begin.drr_toname)) {
+ return (zfs_error(zhp->zfs_hdl, EZFS_BADBACKUP,
+ errbuf));
+ }
+ drr.drr_payloadlen = buflen;
+
+ err = dump_record(&drr, packbuf, buflen, &zc, fd);
+ free(packbuf);
+ if (err != 0) {
+ zfs_error_aux(zhp->zfs_hdl, strerror(err));
+ return (zfs_error(zhp->zfs_hdl, EZFS_BADBACKUP,
+ errbuf));
+ }
+ err = send_conclusion_record(fd, &zc);
+ if (err != 0) {
+ zfs_error_aux(zhp->zfs_hdl, strerror(err));
+ return (zfs_error(zhp->zfs_hdl, EZFS_BADBACKUP,
+ errbuf));
+ }
+ }
+ return (0);
+}
+
+/*
+ * Generate a send stream. The "zhp" argument is the filesystem/volume
+ * that contains the snapshot to send. The "fromsnap" argument is the
+ * short name (the part after the '@') of the snapshot that is the
+ * incremental source to send from (if non-NULL). The "tosnap" argument
+ * is the short name of the snapshot to send.
+ *
+ * The content of the send stream is the snapshot identified by
+ * 'tosnap'. Incremental streams are requested in two ways:
+ * - from the snapshot identified by "fromsnap" (if non-null) or
+ * - from the origin of the dataset identified by zhp, which must
+ * be a clone. In this case, "fromsnap" is null and "fromorigin"
+ * is TRUE.
+ *
+ * The send stream is recursive (i.e. dumps a hierarchy of snapshots) and
+ * uses a special header (with a hdrtype field of DMU_COMPOUNDSTREAM)
+ * if "replicate" is set. If "doall" is set, dump all the intermediate
+ * snapshots. The DMU_COMPOUNDSTREAM header is used in the "doall"
+ * case too. If "props" is set, send properties.
+ */
+int
+zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap,
+ sendflags_t *flags, int outfd, snapfilter_cb_t filter_func,
+ void *cb_arg, nvlist_t **debugnvp)
+{
+ char errbuf[1024];
+ send_dump_data_t sdd = { 0 };
+ int err = 0;
+ nvlist_t *fss = NULL;
+ avl_tree_t *fsavl = NULL;
+ static uint64_t holdseq;
+ int spa_version;
+ pthread_t tid = 0;
+ int pipefd[2];
+ int featureflags = 0;
+ FILE *fout;
+
+ (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
+ "cannot send '%s'"), zhp->zfs_name);
+
+ if (fromsnap && fromsnap[0] == '\0') {
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "zero-length incremental source"));
+ return (zfs_error(zhp->zfs_hdl, EZFS_NOENT, errbuf));
+ }
+
+ if (zhp->zfs_type == ZFS_TYPE_FILESYSTEM) {
+ uint64_t version;
+ version = zfs_prop_get_int(zhp, ZFS_PROP_VERSION);
+ if (version >= ZPL_VERSION_SA) {
+ featureflags |= DMU_BACKUP_FEATURE_SA_SPILL;
+ }
+ }
+
+ if (flags->holds)
+ featureflags |= DMU_BACKUP_FEATURE_HOLDS;
+
+ if (flags->replicate || flags->doall || flags->props ||
+ flags->holds || flags->backup) {
+ char full_tosnap_name[ZFS_MAX_DATASET_NAME_LEN];
+ if (snprintf(full_tosnap_name, sizeof (full_tosnap_name),
+ "%s@%s", zhp->zfs_name, tosnap) >=
+ sizeof (full_tosnap_name)) {
+ err = EINVAL;
+ goto stderr_out;
+ }
+ zfs_handle_t *tosnap = zfs_open(zhp->zfs_hdl,
+ full_tosnap_name, ZFS_TYPE_SNAPSHOT);
+ if (tosnap == NULL) {
+ err = -1;
+ goto err_out;
+ }
+ err = send_prelim_records(tosnap, fromsnap, outfd,
+ flags->replicate || flags->props || flags->holds,
+ flags->replicate, flags->verbosity > 0, flags->dryrun,
+ flags->raw, flags->replicate, flags->backup, flags->holds,
+ flags->props, flags->doall, &fss, &fsavl);
+ zfs_close(tosnap);
+ if (err != 0)
+ goto err_out;
+ }
+
+ /* dump each stream */
+ sdd.fromsnap = fromsnap;
+ sdd.tosnap = tosnap;
+ if (tid != 0)
+ sdd.outfd = pipefd[0];
+ else
+ sdd.outfd = outfd;
+ sdd.replicate = flags->replicate;
+ sdd.doall = flags->doall;
+ sdd.fromorigin = flags->fromorigin;
+ sdd.fss = fss;
+ sdd.fsavl = fsavl;
+ sdd.verbosity = flags->verbosity;
+ sdd.parsable = flags->parsable;
+ sdd.progress = flags->progress;
+ sdd.dryrun = flags->dryrun;
+ sdd.large_block = flags->largeblock;
+ sdd.embed_data = flags->embed_data;
+ sdd.compress = flags->compress;
+ sdd.raw = flags->raw;
+ sdd.holds = flags->holds;
+ sdd.filter_cb = filter_func;
+ sdd.filter_cb_arg = cb_arg;
+ if (debugnvp)
+ sdd.debugnv = *debugnvp;
+ if (sdd.verbosity != 0 && sdd.dryrun)
+ sdd.std_out = B_TRUE;
+ fout = sdd.std_out ? stdout : stderr;
+
+ /*
+ * Some flags require that we place user holds on the datasets that are
+ * being sent so they don't get destroyed during the send. We can skip
+ * this step if the pool is imported read-only since the datasets cannot
+ * be destroyed.
+ */
+ if (!flags->dryrun && !zpool_get_prop_int(zfs_get_pool_handle(zhp),
+ ZPOOL_PROP_READONLY, NULL) &&
+ zfs_spa_version(zhp, &spa_version) == 0 &&
+ spa_version >= SPA_VERSION_USERREFS &&
+ (flags->doall || flags->replicate)) {
+ ++holdseq;
+ (void) snprintf(sdd.holdtag, sizeof (sdd.holdtag),
+ ".send-%d-%llu", getpid(), (u_longlong_t)holdseq);
+ sdd.cleanup_fd = open(ZFS_DEV, O_RDWR);
+ if (sdd.cleanup_fd < 0) {
+ err = errno;
+ goto stderr_out;
+ }
+ sdd.snapholds = fnvlist_alloc();
+ } else {
+ sdd.cleanup_fd = -1;
+ sdd.snapholds = NULL;
+ }
+
+ if (flags->verbosity != 0 || sdd.snapholds != NULL) {
+ /*
+ * Do a verbose no-op dry run to get all the verbose output
+ * or to gather snapshot hold's before generating any data,
+ * then do a non-verbose real run to generate the streams.
+ */
+ sdd.dryrun = B_TRUE;
+ err = dump_filesystems(zhp, &sdd);
+
+ if (err != 0)
+ goto stderr_out;
+
+ if (flags->verbosity != 0) {
+ if (flags->parsable) {
+ (void) fprintf(fout, "size\t%llu\n",
+ (longlong_t)sdd.size);
+ } else {
+ char buf[16];
+ zfs_nicebytes(sdd.size, buf, sizeof (buf));
+ (void) fprintf(fout, dgettext(TEXT_DOMAIN,
+ "total estimated size is %s\n"), buf);
+ }
+ }
+
+ /* Ensure no snaps found is treated as an error. */
+ if (!sdd.seento) {
+ err = ENOENT;
+ goto err_out;
+ }
+
+ /* Skip the second run if dryrun was requested. */
+ if (flags->dryrun)
+ goto err_out;
+
+ if (sdd.snapholds != NULL) {
+ err = zfs_hold_nvl(zhp, sdd.cleanup_fd, sdd.snapholds);
+ if (err != 0)
+ goto stderr_out;
+
+ fnvlist_free(sdd.snapholds);
+ sdd.snapholds = NULL;
+ }
+
+ sdd.dryrun = B_FALSE;
+ sdd.verbosity = 0;
+ }
+
+ err = dump_filesystems(zhp, &sdd);
+ fsavl_destroy(fsavl);
+ nvlist_free(fss);
+
+ /* Ensure no snaps found is treated as an error. */
+ if (err == 0 && !sdd.seento)
+ err = ENOENT;
+
+ if (tid != 0) {
+ if (err != 0)
+ (void) pthread_cancel(tid);
+ (void) close(pipefd[0]);
+ (void) pthread_join(tid, NULL);
+ }
+
+ if (sdd.cleanup_fd != -1) {
+ VERIFY(0 == close(sdd.cleanup_fd));
+ sdd.cleanup_fd = -1;
+ }
+
+ if (!flags->dryrun && (flags->replicate || flags->doall ||
+ flags->props || flags->backup || flags->holds)) {
+ /*
+ * write final end record. NB: want to do this even if
+ * there was some error, because it might not be totally
+ * failed.
+ */
+ err = send_conclusion_record(outfd, NULL);
+ if (err != 0)
+ return (zfs_standard_error(zhp->zfs_hdl, err, errbuf));
+ }
+
+ return (err || sdd.err);
+
+stderr_out:
+ err = zfs_standard_error(zhp->zfs_hdl, err, errbuf);
+err_out:
+ fsavl_destroy(fsavl);
+ nvlist_free(fss);
+ fnvlist_free(sdd.snapholds);
+
+ if (sdd.cleanup_fd != -1)
+ VERIFY(0 == close(sdd.cleanup_fd));
+ if (tid != 0) {
+ (void) pthread_cancel(tid);
+ (void) close(pipefd[0]);
+ (void) pthread_join(tid, NULL);
+ }
+ return (err);
+}
+
+static zfs_handle_t *
+name_to_dir_handle(libzfs_handle_t *hdl, const char *snapname)
+{
+ char dirname[ZFS_MAX_DATASET_NAME_LEN];
+ (void) strlcpy(dirname, snapname, ZFS_MAX_DATASET_NAME_LEN);
+ char *c = strchr(dirname, '@');
+ if (c != NULL)
+ *c = '\0';
+ return (zfs_open(hdl, dirname, ZFS_TYPE_DATASET));
+}
+
+/*
+ * Returns B_TRUE if earlier is an earlier snapshot in later's timeline; either
+ * an earlier snapshot in the same filesystem, or a snapshot before later's
+ * origin, or it's origin's origin, etc.
+ */
+static boolean_t
+snapshot_is_before(zfs_handle_t *earlier, zfs_handle_t *later)
+{
+ boolean_t ret;
+ uint64_t later_txg =
+ (later->zfs_type == ZFS_TYPE_FILESYSTEM ||
+ later->zfs_type == ZFS_TYPE_VOLUME ?
+ UINT64_MAX : zfs_prop_get_int(later, ZFS_PROP_CREATETXG));
+ uint64_t earlier_txg = zfs_prop_get_int(earlier, ZFS_PROP_CREATETXG);
+
+ if (earlier_txg >= later_txg)
+ return (B_FALSE);
+
+ zfs_handle_t *earlier_dir = name_to_dir_handle(earlier->zfs_hdl,
+ earlier->zfs_name);
+ zfs_handle_t *later_dir = name_to_dir_handle(later->zfs_hdl,
+ later->zfs_name);
+
+ if (strcmp(earlier_dir->zfs_name, later_dir->zfs_name) == 0) {
+ zfs_close(earlier_dir);
+ zfs_close(later_dir);
+ return (B_TRUE);
+ }
+
+ char clonename[ZFS_MAX_DATASET_NAME_LEN];
+ if (zfs_prop_get(later_dir, ZFS_PROP_ORIGIN, clonename,
+ ZFS_MAX_DATASET_NAME_LEN, NULL, NULL, 0, B_TRUE) != 0) {
+ zfs_close(earlier_dir);
+ zfs_close(later_dir);
+ return (B_FALSE);
+ }
+
+ zfs_handle_t *origin = zfs_open(earlier->zfs_hdl, clonename,
+ ZFS_TYPE_DATASET);
+ uint64_t origin_txg = zfs_prop_get_int(origin, ZFS_PROP_CREATETXG);
+
+ /*
+ * If "earlier" is exactly the origin, then
+ * snapshot_is_before(earlier, origin) will return false (because
+ * they're the same).
+ */
+ if (origin_txg == earlier_txg &&
+ strcmp(origin->zfs_name, earlier->zfs_name) == 0) {
+ zfs_close(earlier_dir);
+ zfs_close(later_dir);
+ zfs_close(origin);
+ return (B_TRUE);
+ }
+ zfs_close(earlier_dir);
+ zfs_close(later_dir);
+
+ ret = snapshot_is_before(earlier, origin);
+ zfs_close(origin);
+ return (ret);
+}
+
+/*
+ * The "zhp" argument is the handle of the dataset to send (typically a
+ * snapshot). The "from" argument is the full name of the snapshot or
+ * bookmark that is the incremental source.
+ */
+int
+zfs_send_one(zfs_handle_t *zhp, const char *from, int fd, sendflags_t *flags,
+ const char *redactbook)
+{
+ int err;
+ libzfs_handle_t *hdl = zhp->zfs_hdl;
+ char *name = zhp->zfs_name;
+ int orig_fd = fd;
+ pthread_t ptid;
+ progress_arg_t pa = { 0 };
+
+ char errbuf[1024];
+ (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
+ "warning: cannot send '%s'"), name);
+
+ if (from != NULL && strchr(from, '@')) {
+ zfs_handle_t *from_zhp = zfs_open(hdl, from,
+ ZFS_TYPE_DATASET);
+ if (from_zhp == NULL)
+ return (-1);
+ if (!snapshot_is_before(from_zhp, zhp)) {
+ zfs_close(from_zhp);
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "not an earlier snapshot from the same fs"));
+ return (zfs_error(hdl, EZFS_CROSSTARGET, errbuf));
+ }
+ zfs_close(from_zhp);
+ }
+
+ if (redactbook != NULL) {
+ char bookname[ZFS_MAX_DATASET_NAME_LEN];
+ nvlist_t *redact_snaps;
+ zfs_handle_t *book_zhp;
+ char *at, *pound;
+ int dsnamelen;
+
+ pound = strchr(redactbook, '#');
+ if (pound != NULL)
+ redactbook = pound + 1;
+ at = strchr(name, '@');
+ if (at == NULL) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "cannot do a redacted send to a filesystem"));
+ return (zfs_error(hdl, EZFS_BADTYPE, errbuf));
+ }
+ dsnamelen = at - name;
+ if (snprintf(bookname, sizeof (bookname), "%.*s#%s",
+ dsnamelen, name, redactbook)
+ >= sizeof (bookname)) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "invalid bookmark name"));
+ return (zfs_error(hdl, EZFS_INVALIDNAME, errbuf));
+ }
+ book_zhp = zfs_open(hdl, bookname, ZFS_TYPE_BOOKMARK);
+ if (book_zhp == NULL)
+ return (-1);
+ if (nvlist_lookup_nvlist(book_zhp->zfs_props,
+ zfs_prop_to_name(ZFS_PROP_REDACT_SNAPS),
+ &redact_snaps) != 0 || redact_snaps == NULL) {
+ zfs_close(book_zhp);
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "not a redaction bookmark"));
+ return (zfs_error(hdl, EZFS_BADTYPE, errbuf));
+ }
+ zfs_close(book_zhp);
+ }
+
+ /*
+ * Send fs properties
+ */
+ if (flags->props || flags->holds || flags->backup) {
+ /*
+ * Note: the header generated by send_prelim_records()
+ * assumes that the incremental source is in the same
+ * filesystem/volume as the target (which is a requirement
+ * when doing "zfs send -R"). But that isn't always the
+ * case here (e.g. send from snap in origin, or send from
+ * bookmark). We pass from=NULL, which will omit this
+ * information from the prelim records; it isn't used
+ * when receiving this type of stream.
+ */
+ err = send_prelim_records(zhp, NULL, fd, B_TRUE, B_FALSE,
+ flags->verbosity > 0, flags->dryrun, flags->raw,
+ flags->replicate, flags->backup, flags->holds,
+ flags->props, flags->doall, NULL, NULL);
+ if (err != 0)
+ return (err);
+ }
+
+ /*
+ * Perform size estimate if verbose was specified.
+ */
+ if (flags->verbosity != 0) {
+ err = estimate_size(zhp, from, fd, flags, 0, 0, 0, redactbook,
+ errbuf);
+ if (err != 0)
+ return (err);
+ }
+
+ if (flags->dryrun)
+ return (0);
+
+ /*
+ * If progress reporting is requested, spawn a new thread to poll
+ * ZFS_IOC_SEND_PROGRESS at a regular interval.
+ */
+ if (flags->progress) {
+ pa.pa_zhp = zhp;
+ pa.pa_fd = fd;
+ pa.pa_parsable = flags->parsable;
+ pa.pa_estimate = B_FALSE;
+ pa.pa_verbosity = flags->verbosity;
+
+ err = pthread_create(&ptid, NULL,
+ send_progress_thread, &pa);
+ if (err != 0) {
+ zfs_error_aux(zhp->zfs_hdl, strerror(errno));
+ return (zfs_error(zhp->zfs_hdl,
+ EZFS_THREADCREATEFAILED, errbuf));
+ }
+ }
+
+ err = lzc_send_redacted(name, from, fd,
+ lzc_flags_from_sendflags(flags), redactbook);
+
+ if (flags->progress) {
+ void *status = NULL;
+ if (err != 0)
+ (void) pthread_cancel(ptid);
+ (void) pthread_join(ptid, &status);
+ int error = (int)(uintptr_t)status;
+ if (error != 0 && status != PTHREAD_CANCELED) {
+ char errbuf[1024];
+ (void) snprintf(errbuf, sizeof (errbuf),
+ dgettext(TEXT_DOMAIN, "progress thread exited "
+ "nonzero"));
+ return (zfs_standard_error(hdl, error, errbuf));
+ }
+ }
+
+ if (flags->props || flags->holds || flags->backup) {
+ /* Write the final end record. */
+ err = send_conclusion_record(orig_fd, NULL);
+ if (err != 0)
+ return (zfs_standard_error(hdl, err, errbuf));
+ }
+ if (err != 0) {
+ switch (errno) {
+ case EXDEV:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "not an earlier snapshot from the same fs"));
+ return (zfs_error(hdl, EZFS_CROSSTARGET, errbuf));
+
+ case ENOENT:
+ case ESRCH:
+ if (lzc_exists(name)) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "incremental source (%s) does not exist"),
+ from);
+ }
+ return (zfs_error(hdl, EZFS_NOENT, errbuf));
+
+ case EACCES:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "dataset key must be loaded"));
+ return (zfs_error(hdl, EZFS_CRYPTOFAILED, errbuf));
+
+ case EBUSY:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "target is busy; if a filesystem, "
+ "it must not be mounted"));
+ return (zfs_error(hdl, EZFS_BUSY, errbuf));
+
+ case EDQUOT:
+ case EFAULT:
+ case EFBIG:
+ case EINVAL:
+ case EIO:
+ case ENOLINK:
+ case ENOSPC:
+ case ENOSTR:
+ case ENXIO:
+ case EPIPE:
+ case ERANGE:
+ case EROFS:
+ zfs_error_aux(hdl, strerror(errno));
+ return (zfs_error(hdl, EZFS_BADBACKUP, errbuf));
+
+ default:
+ return (zfs_standard_error(hdl, errno, errbuf));
+ }
+ }
+ return (err != 0);
+}
+
+/*
+ * Routines specific to "zfs recv"
+ */
+
+static int
+recv_read(libzfs_handle_t *hdl, int fd, void *buf, int ilen,
+ boolean_t byteswap, zio_cksum_t *zc)
+{
+ char *cp = buf;
+ int rv;
+ int len = ilen;
+
+ assert(ilen <= SPA_MAXBLOCKSIZE);
+
+ do {
+ rv = read(fd, cp, len);
+ cp += rv;
+ len -= rv;
+ } while (rv > 0);
+
+ if (rv < 0 || len != 0) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "failed to read from stream"));
+ return (zfs_error(hdl, EZFS_BADSTREAM, dgettext(TEXT_DOMAIN,
+ "cannot receive")));
+ }
+
+ if (zc) {
+ if (byteswap)
+ fletcher_4_incremental_byteswap(buf, ilen, zc);
+ else
+ fletcher_4_incremental_native(buf, ilen, zc);
+ }
+ return (0);
+}
+
+static int
+recv_read_nvlist(libzfs_handle_t *hdl, int fd, int len, nvlist_t **nvp,
+ boolean_t byteswap, zio_cksum_t *zc)
+{
+ char *buf;
+ int err;
+
+ buf = zfs_alloc(hdl, len);
+ if (buf == NULL)
+ return (ENOMEM);
+
+ err = recv_read(hdl, fd, buf, len, byteswap, zc);
+ if (err != 0) {
+ free(buf);
+ return (err);
+ }
+
+ err = nvlist_unpack(buf, len, nvp, 0);
+ free(buf);
+ if (err != 0) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "invalid "
+ "stream (malformed nvlist)"));
+ return (EINVAL);
+ }
+ return (0);
+}
+
+/*
+ * Returns the grand origin (origin of origin of origin...) of a given handle.
+ * If this dataset is not a clone, it simply returns a copy of the original
+ * handle.
+ */
+static zfs_handle_t *
+recv_open_grand_origin(zfs_handle_t *zhp)
+{
+ char origin[ZFS_MAX_DATASET_NAME_LEN];
+ zprop_source_t src;
+ zfs_handle_t *ozhp = zfs_handle_dup(zhp);
+
+ while (ozhp != NULL) {
+ if (zfs_prop_get(ozhp, ZFS_PROP_ORIGIN, origin,
+ sizeof (origin), &src, NULL, 0, B_FALSE) != 0)
+ break;
+
+ (void) zfs_close(ozhp);
+ ozhp = zfs_open(zhp->zfs_hdl, origin, ZFS_TYPE_FILESYSTEM);
+ }
+
+ return (ozhp);
+}
+
+static int
+recv_rename_impl(zfs_handle_t *zhp, const char *name, const char *newname)
+{
+ int err;
+ zfs_handle_t *ozhp = NULL;
+
+ /*
+ * Attempt to rename the dataset. If it fails with EACCES we have
+ * attempted to rename the dataset outside of its encryption root.
+ * Force the dataset to become an encryption root and try again.
+ */
+ err = lzc_rename(name, newname);
+ if (err == EACCES) {
+ ozhp = recv_open_grand_origin(zhp);
+ if (ozhp == NULL) {
+ err = ENOENT;
+ goto out;
+ }
+
+ err = lzc_change_key(ozhp->zfs_name, DCP_CMD_FORCE_NEW_KEY,
+ NULL, NULL, 0);
+ if (err != 0)
+ goto out;
+
+ err = lzc_rename(name, newname);
+ }
+
+out:
+ if (ozhp != NULL)
+ zfs_close(ozhp);
+ return (err);
+}
+
+static int
+recv_rename(libzfs_handle_t *hdl, const char *name, const char *tryname,
+ int baselen, char *newname, recvflags_t *flags)
+{
+ static int seq;
+ int err;
+ prop_changelist_t *clp = NULL;
+ zfs_handle_t *zhp = NULL;
+
+ zhp = zfs_open(hdl, name, ZFS_TYPE_DATASET);
+ if (zhp == NULL) {
+ err = -1;
+ goto out;
+ }
+ clp = changelist_gather(zhp, ZFS_PROP_NAME, 0,
+ flags->force ? MS_FORCE : 0);
+ if (clp == NULL) {
+ err = -1;
+ goto out;
+ }
+ err = changelist_prefix(clp);
+ if (err)
+ goto out;
+
+ if (tryname) {
+ (void) strcpy(newname, tryname);
+ if (flags->verbose) {
+ (void) printf("attempting rename %s to %s\n",
+ name, newname);
+ }
+ err = recv_rename_impl(zhp, name, newname);
+ if (err == 0)
+ changelist_rename(clp, name, tryname);
+ } else {
+ err = ENOENT;
+ }
+
+ if (err != 0 && strncmp(name + baselen, "recv-", 5) != 0) {
+ seq++;
+
+ (void) snprintf(newname, ZFS_MAX_DATASET_NAME_LEN,
+ "%.*srecv-%u-%u", baselen, name, getpid(), seq);
+
+ if (flags->verbose) {
+ (void) printf("failed - trying rename %s to %s\n",
+ name, newname);
+ }
+ err = recv_rename_impl(zhp, name, newname);
+ if (err == 0)
+ changelist_rename(clp, name, newname);
+ if (err && flags->verbose) {
+ (void) printf("failed (%u) - "
+ "will try again on next pass\n", errno);
+ }
+ err = EAGAIN;
+ } else if (flags->verbose) {
+ if (err == 0)
+ (void) printf("success\n");
+ else
+ (void) printf("failed (%u)\n", errno);
+ }
+
+ (void) changelist_postfix(clp);
+
+out:
+ if (clp != NULL)
+ changelist_free(clp);
+ if (zhp != NULL)
+ zfs_close(zhp);
+
+ return (err);
+}
+
+static int
+recv_promote(libzfs_handle_t *hdl, const char *fsname,
+ const char *origin_fsname, recvflags_t *flags)
+{
+ int err;
+ zfs_cmd_t zc = {"\0"};
+ zfs_handle_t *zhp = NULL, *ozhp = NULL;
+
+ if (flags->verbose)
+ (void) printf("promoting %s\n", fsname);
+
+ (void) strlcpy(zc.zc_value, origin_fsname, sizeof (zc.zc_value));
+ (void) strlcpy(zc.zc_name, fsname, sizeof (zc.zc_name));
+
+ /*
+ * Attempt to promote the dataset. If it fails with EACCES the
+ * promotion would cause this dataset to leave its encryption root.
+ * Force the origin to become an encryption root and try again.
+ */
+ err = zfs_ioctl(hdl, ZFS_IOC_PROMOTE, &zc);
+ if (err == EACCES) {
+ zhp = zfs_open(hdl, fsname, ZFS_TYPE_DATASET);
+ if (zhp == NULL) {
+ err = -1;
+ goto out;
+ }
+
+ ozhp = recv_open_grand_origin(zhp);
+ if (ozhp == NULL) {
+ err = -1;
+ goto out;
+ }
+
+ err = lzc_change_key(ozhp->zfs_name, DCP_CMD_FORCE_NEW_KEY,
+ NULL, NULL, 0);
+ if (err != 0)
+ goto out;
+
+ err = zfs_ioctl(hdl, ZFS_IOC_PROMOTE, &zc);
+ }
+
+out:
+ if (zhp != NULL)
+ zfs_close(zhp);
+ if (ozhp != NULL)
+ zfs_close(ozhp);
+
+ return (err);
+}
+
+static int
+recv_destroy(libzfs_handle_t *hdl, const char *name, int baselen,
+ char *newname, recvflags_t *flags)
+{
+ int err = 0;
+ prop_changelist_t *clp;
+ zfs_handle_t *zhp;
+ boolean_t defer = B_FALSE;
+ int spa_version;
+
+ zhp = zfs_open(hdl, name, ZFS_TYPE_DATASET);
+ if (zhp == NULL)
+ return (-1);
+ clp = changelist_gather(zhp, ZFS_PROP_NAME, 0,
+ flags->force ? MS_FORCE : 0);
+ if (zfs_get_type(zhp) == ZFS_TYPE_SNAPSHOT &&
+ zfs_spa_version(zhp, &spa_version) == 0 &&
+ spa_version >= SPA_VERSION_USERREFS)
+ defer = B_TRUE;
+ zfs_close(zhp);
+ if (clp == NULL)
+ return (-1);
+ err = changelist_prefix(clp);
+ if (err)
+ return (err);
+
+ if (flags->verbose)
+ (void) printf("attempting destroy %s\n", name);
+ if (zhp->zfs_type == ZFS_TYPE_SNAPSHOT) {
+ nvlist_t *nv = fnvlist_alloc();
+ fnvlist_add_boolean(nv, name);
+ err = lzc_destroy_snaps(nv, defer, NULL);
+ fnvlist_free(nv);
+ } else {
+ err = lzc_destroy(name);
+ }
+ if (err == 0) {
+ if (flags->verbose)
+ (void) printf("success\n");
+ changelist_remove(clp, name);
+ }
+
+ (void) changelist_postfix(clp);
+ changelist_free(clp);
+
+ /*
+ * Deferred destroy might destroy the snapshot or only mark it to be
+ * destroyed later, and it returns success in either case.
+ */
+ if (err != 0 || (defer && zfs_dataset_exists(hdl, name,
+ ZFS_TYPE_SNAPSHOT))) {
+ err = recv_rename(hdl, name, NULL, baselen, newname, flags);
+ }
+
+ return (err);
+}
+
+typedef struct guid_to_name_data {
+ uint64_t guid;
+ boolean_t bookmark_ok;
+ char *name;
+ char *skip;
+ uint64_t *redact_snap_guids;
+ uint64_t num_redact_snaps;
+} guid_to_name_data_t;
+
+static boolean_t
+redact_snaps_match(zfs_handle_t *zhp, guid_to_name_data_t *gtnd)
+{
+ uint64_t *bmark_snaps;
+ uint_t bmark_num_snaps;
+ nvlist_t *nvl;
+ if (zhp->zfs_type != ZFS_TYPE_BOOKMARK)
+ return (B_FALSE);
+
+ nvl = fnvlist_lookup_nvlist(zhp->zfs_props,
+ zfs_prop_to_name(ZFS_PROP_REDACT_SNAPS));
+ bmark_snaps = fnvlist_lookup_uint64_array(nvl, ZPROP_VALUE,
+ &bmark_num_snaps);
+ if (bmark_num_snaps != gtnd->num_redact_snaps)
+ return (B_FALSE);
+ int i = 0;
+ for (; i < bmark_num_snaps; i++) {
+ int j = 0;
+ for (; j < bmark_num_snaps; j++) {
+ if (bmark_snaps[i] == gtnd->redact_snap_guids[j])
+ break;
+ }
+ if (j == bmark_num_snaps)
+ break;
+ }
+ return (i == bmark_num_snaps);
+}
+
+static int
+guid_to_name_cb(zfs_handle_t *zhp, void *arg)
+{
+ guid_to_name_data_t *gtnd = arg;
+ const char *slash;
+ int err;
+
+ if (gtnd->skip != NULL &&
+ (slash = strrchr(zhp->zfs_name, '/')) != NULL &&
+ strcmp(slash + 1, gtnd->skip) == 0) {
+ zfs_close(zhp);
+ return (0);
+ }
+
+ if (zfs_prop_get_int(zhp, ZFS_PROP_GUID) == gtnd->guid &&
+ (gtnd->num_redact_snaps == -1 || redact_snaps_match(zhp, gtnd))) {
+ (void) strcpy(gtnd->name, zhp->zfs_name);
+ zfs_close(zhp);
+ return (EEXIST);
+ }
+
+ err = zfs_iter_children(zhp, guid_to_name_cb, gtnd);
+ if (err != EEXIST && gtnd->bookmark_ok)
+ err = zfs_iter_bookmarks(zhp, guid_to_name_cb, gtnd);
+ zfs_close(zhp);
+ return (err);
+}
+
+/*
+ * Attempt to find the local dataset associated with this guid. In the case of
+ * multiple matches, we attempt to find the "best" match by searching
+ * progressively larger portions of the hierarchy. This allows one to send a
+ * tree of datasets individually and guarantee that we will find the source
+ * guid within that hierarchy, even if there are multiple matches elsewhere.
+ *
+ * If num_redact_snaps is not -1, we attempt to find a redaction bookmark with
+ * the specified number of redaction snapshots. If num_redact_snaps isn't 0 or
+ * -1, then redact_snap_guids will be an array of the guids of the snapshots the
+ * redaction bookmark was created with. If num_redact_snaps is -1, then we will
+ * attempt to find a snapshot or bookmark (if bookmark_ok is passed) with the
+ * given guid. Note that a redaction bookmark can be returned if
+ * num_redact_snaps == -1.
+ */
+static int
+guid_to_name_redact_snaps(libzfs_handle_t *hdl, const char *parent,
+ uint64_t guid, boolean_t bookmark_ok, uint64_t *redact_snap_guids,
+ uint64_t num_redact_snaps, char *name)
+{
+ char pname[ZFS_MAX_DATASET_NAME_LEN];
+ guid_to_name_data_t gtnd;
+
+ gtnd.guid = guid;
+ gtnd.bookmark_ok = bookmark_ok;
+ gtnd.name = name;
+ gtnd.skip = NULL;
+ gtnd.redact_snap_guids = redact_snap_guids;
+ gtnd.num_redact_snaps = num_redact_snaps;
+
+ /*
+ * Search progressively larger portions of the hierarchy, starting
+ * with the filesystem specified by 'parent'. This will
+ * select the "most local" version of the origin snapshot in the case
+ * that there are multiple matching snapshots in the system.
+ */
+ (void) strlcpy(pname, parent, sizeof (pname));
+ char *cp = strrchr(pname, '@');
+ if (cp == NULL)
+ cp = strchr(pname, '\0');
+ for (; cp != NULL; cp = strrchr(pname, '/')) {
+ /* Chop off the last component and open the parent */
+ *cp = '\0';
+ zfs_handle_t *zhp = make_dataset_handle(hdl, pname);
+
+ if (zhp == NULL)
+ continue;
+ int err = guid_to_name_cb(zfs_handle_dup(zhp), &gtnd);
+ if (err != EEXIST)
+ err = zfs_iter_children(zhp, guid_to_name_cb, &gtnd);
+ if (err != EEXIST && bookmark_ok)
+ err = zfs_iter_bookmarks(zhp, guid_to_name_cb, &gtnd);
+ zfs_close(zhp);
+ if (err == EEXIST)
+ return (0);
+
+ /*
+ * Remember the last portion of the dataset so we skip it next
+ * time through (as we've already searched that portion of the
+ * hierarchy).
+ */
+ gtnd.skip = strrchr(pname, '/') + 1;
+ }
+
+ return (ENOENT);
+}
+
+static int
+guid_to_name(libzfs_handle_t *hdl, const char *parent, uint64_t guid,
+ boolean_t bookmark_ok, char *name)
+{
+ return (guid_to_name_redact_snaps(hdl, parent, guid, bookmark_ok, NULL,
+ -1, name));
+}
+
+/*
+ * Return +1 if guid1 is before guid2, 0 if they are the same, and -1 if
+ * guid1 is after guid2.
+ */
+static int
+created_before(libzfs_handle_t *hdl, avl_tree_t *avl,
+ uint64_t guid1, uint64_t guid2)
+{
+ nvlist_t *nvfs;
+ char *fsname = NULL, *snapname = NULL;
+ char buf[ZFS_MAX_DATASET_NAME_LEN];
+ int rv;
+ zfs_handle_t *guid1hdl, *guid2hdl;
+ uint64_t create1, create2;
+
+ if (guid2 == 0)
+ return (0);
+ if (guid1 == 0)
+ return (1);
+
+ nvfs = fsavl_find(avl, guid1, &snapname);
+ VERIFY(0 == nvlist_lookup_string(nvfs, "name", &fsname));
+ (void) snprintf(buf, sizeof (buf), "%s@%s", fsname, snapname);
+ guid1hdl = zfs_open(hdl, buf, ZFS_TYPE_SNAPSHOT);
+ if (guid1hdl == NULL)
+ return (-1);
+
+ nvfs = fsavl_find(avl, guid2, &snapname);
+ VERIFY(0 == nvlist_lookup_string(nvfs, "name", &fsname));
+ (void) snprintf(buf, sizeof (buf), "%s@%s", fsname, snapname);
+ guid2hdl = zfs_open(hdl, buf, ZFS_TYPE_SNAPSHOT);
+ if (guid2hdl == NULL) {
+ zfs_close(guid1hdl);
+ return (-1);
+ }
+
+ create1 = zfs_prop_get_int(guid1hdl, ZFS_PROP_CREATETXG);
+ create2 = zfs_prop_get_int(guid2hdl, ZFS_PROP_CREATETXG);
+
+ if (create1 < create2)
+ rv = -1;
+ else if (create1 > create2)
+ rv = +1;
+ else
+ rv = 0;
+
+ zfs_close(guid1hdl);
+ zfs_close(guid2hdl);
+
+ return (rv);
+}
+
+/*
+ * This function reestablishes the hierarchy of encryption roots after a
+ * recursive incremental receive has completed. This must be done after the
+ * second call to recv_incremental_replication() has renamed and promoted all
+ * sent datasets to their final locations in the dataset hierarchy.
+ */
+static int
+recv_fix_encryption_hierarchy(libzfs_handle_t *hdl, const char *top_zfs,
+ nvlist_t *stream_nv, avl_tree_t *stream_avl)
+{
+ int err;
+ nvpair_t *fselem = NULL;
+ nvlist_t *stream_fss;
+
+ VERIFY(0 == nvlist_lookup_nvlist(stream_nv, "fss", &stream_fss));
+
+ while ((fselem = nvlist_next_nvpair(stream_fss, fselem)) != NULL) {
+ zfs_handle_t *zhp = NULL;
+ uint64_t crypt;
+ nvlist_t *snaps, *props, *stream_nvfs = NULL;
+ nvpair_t *snapel = NULL;
+ boolean_t is_encroot, is_clone, stream_encroot;
+ char *cp;
+ char *stream_keylocation = NULL;
+ char keylocation[MAXNAMELEN];
+ char fsname[ZFS_MAX_DATASET_NAME_LEN];
+
+ keylocation[0] = '\0';
+ VERIFY(0 == nvpair_value_nvlist(fselem, &stream_nvfs));
+ VERIFY(0 == nvlist_lookup_nvlist(stream_nvfs, "snaps", &snaps));
+ VERIFY(0 == nvlist_lookup_nvlist(stream_nvfs, "props", &props));
+ stream_encroot = nvlist_exists(stream_nvfs, "is_encroot");
+
+ /* find a snapshot from the stream that exists locally */
+ err = ENOENT;
+ while ((snapel = nvlist_next_nvpair(snaps, snapel)) != NULL) {
+ uint64_t guid;
+
+ VERIFY(0 == nvpair_value_uint64(snapel, &guid));
+ err = guid_to_name(hdl, top_zfs, guid, B_FALSE,
+ fsname);
+ if (err == 0)
+ break;
+ }
+
+ if (err != 0)
+ continue;
+
+ cp = strchr(fsname, '@');
+ if (cp != NULL)
+ *cp = '\0';
+
+ zhp = zfs_open(hdl, fsname, ZFS_TYPE_DATASET);
+ if (zhp == NULL) {
+ err = ENOENT;
+ goto error;
+ }
+
+ crypt = zfs_prop_get_int(zhp, ZFS_PROP_ENCRYPTION);
+ is_clone = zhp->zfs_dmustats.dds_origin[0] != '\0';
+ (void) zfs_crypto_get_encryption_root(zhp, &is_encroot, NULL);
+
+ /* we don't need to do anything for unencrypted datasets */
+ if (crypt == ZIO_CRYPT_OFF) {
+ zfs_close(zhp);
+ continue;
+ }
+
+ /*
+ * If the dataset is flagged as an encryption root, was not
+ * received as a clone and is not currently an encryption root,
+ * force it to become one. Fixup the keylocation if necessary.
+ */
+ if (stream_encroot) {
+ if (!is_clone && !is_encroot) {
+ err = lzc_change_key(fsname,
+ DCP_CMD_FORCE_NEW_KEY, NULL, NULL, 0);
+ if (err != 0) {
+ zfs_close(zhp);
+ goto error;
+ }
+ }
+
+ VERIFY(0 == nvlist_lookup_string(props,
+ zfs_prop_to_name(ZFS_PROP_KEYLOCATION),
+ &stream_keylocation));
+
+ /*
+ * Refresh the properties in case the call to
+ * lzc_change_key() changed the value.
+ */
+ zfs_refresh_properties(zhp);
+ err = zfs_prop_get(zhp, ZFS_PROP_KEYLOCATION,
+ keylocation, sizeof (keylocation), NULL, NULL,
+ 0, B_TRUE);
+ if (err != 0) {
+ zfs_close(zhp);
+ goto error;
+ }
+
+ if (strcmp(keylocation, stream_keylocation) != 0) {
+ err = zfs_prop_set(zhp,
+ zfs_prop_to_name(ZFS_PROP_KEYLOCATION),
+ stream_keylocation);
+ if (err != 0) {
+ zfs_close(zhp);
+ goto error;
+ }
+ }
+ }
+
+ /*
+ * If the dataset is not flagged as an encryption root and is
+ * currently an encryption root, force it to inherit from its
+ * parent. The root of a raw send should never be
+ * force-inherited.
+ */
+ if (!stream_encroot && is_encroot &&
+ strcmp(top_zfs, fsname) != 0) {
+ err = lzc_change_key(fsname, DCP_CMD_FORCE_INHERIT,
+ NULL, NULL, 0);
+ if (err != 0) {
+ zfs_close(zhp);
+ goto error;
+ }
+ }
+
+ zfs_close(zhp);
+ }
+
+ return (0);
+
+error:
+ return (err);
+}
+
+static int
+recv_incremental_replication(libzfs_handle_t *hdl, const char *tofs,
+ recvflags_t *flags, nvlist_t *stream_nv, avl_tree_t *stream_avl,
+ nvlist_t *renamed)
+{
+ nvlist_t *local_nv, *deleted = NULL;
+ avl_tree_t *local_avl;
+ nvpair_t *fselem, *nextfselem;
+ char *fromsnap;
+ char newname[ZFS_MAX_DATASET_NAME_LEN];
+ char guidname[32];
+ int error;
+ boolean_t needagain, progress, recursive;
+ char *s1, *s2;
+
+ VERIFY(0 == nvlist_lookup_string(stream_nv, "fromsnap", &fromsnap));
+
+ recursive = (nvlist_lookup_boolean(stream_nv, "not_recursive") ==
+ ENOENT);
+
+ if (flags->dryrun)
+ return (0);
+
+again:
+ needagain = progress = B_FALSE;
+
+ VERIFY(0 == nvlist_alloc(&deleted, NV_UNIQUE_NAME, 0));
+
+ if ((error = gather_nvlist(hdl, tofs, fromsnap, NULL,
+ recursive, B_TRUE, B_FALSE, recursive, B_FALSE, B_FALSE,
+ B_FALSE, B_TRUE, &local_nv, &local_avl)) != 0)
+ return (error);
+
+ /*
+ * Process deletes and renames
+ */
+ for (fselem = nvlist_next_nvpair(local_nv, NULL);
+ fselem; fselem = nextfselem) {
+ nvlist_t *nvfs, *snaps;
+ nvlist_t *stream_nvfs = NULL;
+ nvpair_t *snapelem, *nextsnapelem;
+ uint64_t fromguid = 0;
+ uint64_t originguid = 0;
+ uint64_t stream_originguid = 0;
+ uint64_t parent_fromsnap_guid, stream_parent_fromsnap_guid;
+ char *fsname, *stream_fsname;
+
+ nextfselem = nvlist_next_nvpair(local_nv, fselem);
+
+ VERIFY(0 == nvpair_value_nvlist(fselem, &nvfs));
+ VERIFY(0 == nvlist_lookup_nvlist(nvfs, "snaps", &snaps));
+ VERIFY(0 == nvlist_lookup_string(nvfs, "name", &fsname));
+ VERIFY(0 == nvlist_lookup_uint64(nvfs, "parentfromsnap",
+ &parent_fromsnap_guid));
+ (void) nvlist_lookup_uint64(nvfs, "origin", &originguid);
+
+ /*
+ * First find the stream's fs, so we can check for
+ * a different origin (due to "zfs promote")
+ */
+ for (snapelem = nvlist_next_nvpair(snaps, NULL);
+ snapelem; snapelem = nvlist_next_nvpair(snaps, snapelem)) {
+ uint64_t thisguid;
+
+ VERIFY(0 == nvpair_value_uint64(snapelem, &thisguid));
+ stream_nvfs = fsavl_find(stream_avl, thisguid, NULL);
+
+ if (stream_nvfs != NULL)
+ break;
+ }
+
+ /* check for promote */
+ (void) nvlist_lookup_uint64(stream_nvfs, "origin",
+ &stream_originguid);
+ if (stream_nvfs && originguid != stream_originguid) {
+ switch (created_before(hdl, local_avl,
+ stream_originguid, originguid)) {
+ case 1: {
+ /* promote it! */
+ nvlist_t *origin_nvfs;
+ char *origin_fsname;
+
+ origin_nvfs = fsavl_find(local_avl, originguid,
+ NULL);
+ VERIFY(0 == nvlist_lookup_string(origin_nvfs,
+ "name", &origin_fsname));
+ error = recv_promote(hdl, fsname, origin_fsname,
+ flags);
+ if (error == 0)
+ progress = B_TRUE;
+ break;
+ }
+ default:
+ break;
+ case -1:
+ fsavl_destroy(local_avl);
+ nvlist_free(local_nv);
+ return (-1);
+ }
+ /*
+ * We had/have the wrong origin, therefore our
+ * list of snapshots is wrong. Need to handle
+ * them on the next pass.
+ */
+ needagain = B_TRUE;
+ continue;
+ }
+
+ for (snapelem = nvlist_next_nvpair(snaps, NULL);
+ snapelem; snapelem = nextsnapelem) {
+ uint64_t thisguid;
+ char *stream_snapname;
+ nvlist_t *found, *props;
+
+ nextsnapelem = nvlist_next_nvpair(snaps, snapelem);
+
+ VERIFY(0 == nvpair_value_uint64(snapelem, &thisguid));
+ found = fsavl_find(stream_avl, thisguid,
+ &stream_snapname);
+
+ /* check for delete */
+ if (found == NULL) {
+ char name[ZFS_MAX_DATASET_NAME_LEN];
+
+ if (!flags->force)
+ continue;
+
+ (void) snprintf(name, sizeof (name), "%s@%s",
+ fsname, nvpair_name(snapelem));
+
+ error = recv_destroy(hdl, name,
+ strlen(fsname)+1, newname, flags);
+ if (error)
+ needagain = B_TRUE;
+ else
+ progress = B_TRUE;
+ sprintf(guidname, "%llu",
+ (u_longlong_t)thisguid);
+ nvlist_add_boolean(deleted, guidname);
+ continue;
+ }
+
+ stream_nvfs = found;
+
+ if (0 == nvlist_lookup_nvlist(stream_nvfs, "snapprops",
+ &props) && 0 == nvlist_lookup_nvlist(props,
+ stream_snapname, &props)) {
+ zfs_cmd_t zc = {"\0"};
+
+ zc.zc_cookie = B_TRUE; /* received */
+ (void) snprintf(zc.zc_name, sizeof (zc.zc_name),
+ "%s@%s", fsname, nvpair_name(snapelem));
+ if (zcmd_write_src_nvlist(hdl, &zc,
+ props) == 0) {
+ (void) zfs_ioctl(hdl,
+ ZFS_IOC_SET_PROP, &zc);
+ zcmd_free_nvlists(&zc);
+ }
+ }
+
+ /* check for different snapname */
+ if (strcmp(nvpair_name(snapelem),
+ stream_snapname) != 0) {
+ char name[ZFS_MAX_DATASET_NAME_LEN];
+ char tryname[ZFS_MAX_DATASET_NAME_LEN];
+
+ (void) snprintf(name, sizeof (name), "%s@%s",
+ fsname, nvpair_name(snapelem));
+ (void) snprintf(tryname, sizeof (name), "%s@%s",
+ fsname, stream_snapname);
+
+ error = recv_rename(hdl, name, tryname,
+ strlen(fsname)+1, newname, flags);
+ if (error)
+ needagain = B_TRUE;
+ else
+ progress = B_TRUE;
+ }
+
+ if (strcmp(stream_snapname, fromsnap) == 0)
+ fromguid = thisguid;
+ }
+
+ /* check for delete */
+ if (stream_nvfs == NULL) {
+ if (!flags->force)
+ continue;
+
+ error = recv_destroy(hdl, fsname, strlen(tofs)+1,
+ newname, flags);
+ if (error)
+ needagain = B_TRUE;
+ else
+ progress = B_TRUE;
+ sprintf(guidname, "%llu",
+ (u_longlong_t)parent_fromsnap_guid);
+ nvlist_add_boolean(deleted, guidname);
+ continue;
+ }
+
+ if (fromguid == 0) {
+ if (flags->verbose) {
+ (void) printf("local fs %s does not have "
+ "fromsnap (%s in stream); must have "
+ "been deleted locally; ignoring\n",
+ fsname, fromsnap);
+ }
+ continue;
+ }
+
+ VERIFY(0 == nvlist_lookup_string(stream_nvfs,
+ "name", &stream_fsname));
+ VERIFY(0 == nvlist_lookup_uint64(stream_nvfs,
+ "parentfromsnap", &stream_parent_fromsnap_guid));
+
+ s1 = strrchr(fsname, '/');
+ s2 = strrchr(stream_fsname, '/');
+
+ /*
+ * Check if we're going to rename based on parent guid change
+ * and the current parent guid was also deleted. If it was then
+ * rename will fail and is likely unneeded, so avoid this and
+ * force an early retry to determine the new
+ * parent_fromsnap_guid.
+ */
+ if (stream_parent_fromsnap_guid != 0 &&
+ parent_fromsnap_guid != 0 &&
+ stream_parent_fromsnap_guid != parent_fromsnap_guid) {
+ sprintf(guidname, "%llu",
+ (u_longlong_t)parent_fromsnap_guid);
+ if (nvlist_exists(deleted, guidname)) {
+ progress = B_TRUE;
+ needagain = B_TRUE;
+ goto doagain;
+ }
+ }
+
+ /*
+ * Check for rename. If the exact receive path is specified, it
+ * does not count as a rename, but we still need to check the
+ * datasets beneath it.
+ */
+ if ((stream_parent_fromsnap_guid != 0 &&
+ parent_fromsnap_guid != 0 &&
+ stream_parent_fromsnap_guid != parent_fromsnap_guid) ||
+ ((flags->isprefix || strcmp(tofs, fsname) != 0) &&
+ (s1 != NULL) && (s2 != NULL) && strcmp(s1, s2) != 0)) {
+ nvlist_t *parent;
+ char tryname[ZFS_MAX_DATASET_NAME_LEN];
+
+ parent = fsavl_find(local_avl,
+ stream_parent_fromsnap_guid, NULL);
+ /*
+ * NB: parent might not be found if we used the
+ * tosnap for stream_parent_fromsnap_guid,
+ * because the parent is a newly-created fs;
+ * we'll be able to rename it after we recv the
+ * new fs.
+ */
+ if (parent != NULL) {
+ char *pname;
+
+ VERIFY(0 == nvlist_lookup_string(parent, "name",
+ &pname));
+ (void) snprintf(tryname, sizeof (tryname),
+ "%s%s", pname, strrchr(stream_fsname, '/'));
+ } else {
+ tryname[0] = '\0';
+ if (flags->verbose) {
+ (void) printf("local fs %s new parent "
+ "not found\n", fsname);
+ }
+ }
+
+ newname[0] = '\0';
+
+ error = recv_rename(hdl, fsname, tryname,
+ strlen(tofs)+1, newname, flags);
+
+ if (renamed != NULL && newname[0] != '\0') {
+ VERIFY(0 == nvlist_add_boolean(renamed,
+ newname));
+ }
+
+ if (error)
+ needagain = B_TRUE;
+ else
+ progress = B_TRUE;
+ }
+ }
+
+doagain:
+ fsavl_destroy(local_avl);
+ nvlist_free(local_nv);
+ nvlist_free(deleted);
+
+ if (needagain && progress) {
+ /* do another pass to fix up temporary names */
+ if (flags->verbose)
+ (void) printf("another pass:\n");
+ goto again;
+ }
+
+ return (needagain || error != 0);
+}
+
+static int
+zfs_receive_package(libzfs_handle_t *hdl, int fd, const char *destname,
+ recvflags_t *flags, dmu_replay_record_t *drr, zio_cksum_t *zc,
+ char **top_zfs, nvlist_t *cmdprops)
+{
+ nvlist_t *stream_nv = NULL;
+ avl_tree_t *stream_avl = NULL;
+ char *fromsnap = NULL;
+ char *sendsnap = NULL;
+ char *cp;
+ char tofs[ZFS_MAX_DATASET_NAME_LEN];
+ char sendfs[ZFS_MAX_DATASET_NAME_LEN];
+ char errbuf[1024];
+ dmu_replay_record_t drre;
+ int error;
+ boolean_t anyerr = B_FALSE;
+ boolean_t softerr = B_FALSE;
+ boolean_t recursive, raw;
+
+ (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
+ "cannot receive"));
+
+ assert(drr->drr_type == DRR_BEGIN);
+ assert(drr->drr_u.drr_begin.drr_magic == DMU_BACKUP_MAGIC);
+ assert(DMU_GET_STREAM_HDRTYPE(drr->drr_u.drr_begin.drr_versioninfo) ==
+ DMU_COMPOUNDSTREAM);
+
+ /*
+ * Read in the nvlist from the stream.
+ */
+ if (drr->drr_payloadlen != 0) {
+ error = recv_read_nvlist(hdl, fd, drr->drr_payloadlen,
+ &stream_nv, flags->byteswap, zc);
+ if (error) {
+ error = zfs_error(hdl, EZFS_BADSTREAM, errbuf);
+ goto out;
+ }
+ }
+
+ recursive = (nvlist_lookup_boolean(stream_nv, "not_recursive") ==
+ ENOENT);
+ raw = (nvlist_lookup_boolean(stream_nv, "raw") == 0);
+
+ if (recursive && strchr(destname, '@')) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "cannot specify snapshot name for multi-snapshot stream"));
+ error = zfs_error(hdl, EZFS_BADSTREAM, errbuf);
+ goto out;
+ }
+
+ /*
+ * Read in the end record and verify checksum.
+ */
+ if (0 != (error = recv_read(hdl, fd, &drre, sizeof (drre),
+ flags->byteswap, NULL)))
+ goto out;
+ if (flags->byteswap) {
+ drre.drr_type = BSWAP_32(drre.drr_type);
+ drre.drr_u.drr_end.drr_checksum.zc_word[0] =
+ BSWAP_64(drre.drr_u.drr_end.drr_checksum.zc_word[0]);
+ drre.drr_u.drr_end.drr_checksum.zc_word[1] =
+ BSWAP_64(drre.drr_u.drr_end.drr_checksum.zc_word[1]);
+ drre.drr_u.drr_end.drr_checksum.zc_word[2] =
+ BSWAP_64(drre.drr_u.drr_end.drr_checksum.zc_word[2]);
+ drre.drr_u.drr_end.drr_checksum.zc_word[3] =
+ BSWAP_64(drre.drr_u.drr_end.drr_checksum.zc_word[3]);
+ }
+ if (drre.drr_type != DRR_END) {
+ error = zfs_error(hdl, EZFS_BADSTREAM, errbuf);
+ goto out;
+ }
+ if (!ZIO_CHECKSUM_EQUAL(drre.drr_u.drr_end.drr_checksum, *zc)) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "incorrect header checksum"));
+ error = zfs_error(hdl, EZFS_BADSTREAM, errbuf);
+ goto out;
+ }
+
+ (void) nvlist_lookup_string(stream_nv, "fromsnap", &fromsnap);
+
+ if (drr->drr_payloadlen != 0) {
+ nvlist_t *stream_fss;
+
+ VERIFY(0 == nvlist_lookup_nvlist(stream_nv, "fss",
+ &stream_fss));
+ if ((stream_avl = fsavl_create(stream_fss)) == NULL) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "couldn't allocate avl tree"));
+ error = zfs_error(hdl, EZFS_NOMEM, errbuf);
+ goto out;
+ }
+
+ if (fromsnap != NULL && recursive) {
+ nvlist_t *renamed = NULL;
+ nvpair_t *pair = NULL;
+
+ (void) strlcpy(tofs, destname, sizeof (tofs));
+ if (flags->isprefix) {
+ struct drr_begin *drrb = &drr->drr_u.drr_begin;
+ int i;
+
+ if (flags->istail) {
+ cp = strrchr(drrb->drr_toname, '/');
+ if (cp == NULL) {
+ (void) strlcat(tofs, "/",
+ sizeof (tofs));
+ i = 0;
+ } else {
+ i = (cp - drrb->drr_toname);
+ }
+ } else {
+ i = strcspn(drrb->drr_toname, "/@");
+ }
+ /* zfs_receive_one() will create_parents() */
+ (void) strlcat(tofs, &drrb->drr_toname[i],
+ sizeof (tofs));
+ *strchr(tofs, '@') = '\0';
+ }
+
+ if (!flags->dryrun && !flags->nomount) {
+ VERIFY(0 == nvlist_alloc(&renamed,
+ NV_UNIQUE_NAME, 0));
+ }
+
+ softerr = recv_incremental_replication(hdl, tofs, flags,
+ stream_nv, stream_avl, renamed);
+
+ /* Unmount renamed filesystems before receiving. */
+ while ((pair = nvlist_next_nvpair(renamed,
+ pair)) != NULL) {
+ zfs_handle_t *zhp;
+ prop_changelist_t *clp = NULL;
+
+ zhp = zfs_open(hdl, nvpair_name(pair),
+ ZFS_TYPE_FILESYSTEM);
+ if (zhp != NULL) {
+ clp = changelist_gather(zhp,
+ ZFS_PROP_MOUNTPOINT, 0,
+ flags->forceunmount ? MS_FORCE : 0);
+ zfs_close(zhp);
+ if (clp != NULL) {
+ softerr |=
+ changelist_prefix(clp);
+ changelist_free(clp);
+ }
+ }
+ }
+
+ nvlist_free(renamed);
+ }
+ }
+
+ /*
+ * Get the fs specified by the first path in the stream (the top level
+ * specified by 'zfs send') and pass it to each invocation of
+ * zfs_receive_one().
+ */
+ (void) strlcpy(sendfs, drr->drr_u.drr_begin.drr_toname,
+ sizeof (sendfs));
+ if ((cp = strchr(sendfs, '@')) != NULL) {
+ *cp = '\0';
+ /*
+ * Find the "sendsnap", the final snapshot in a replication
+ * stream. zfs_receive_one() handles certain errors
+ * differently, depending on if the contained stream is the
+ * last one or not.
+ */
+ sendsnap = (cp + 1);
+ }
+
+ /* Finally, receive each contained stream */
+ do {
+ /*
+ * we should figure out if it has a recoverable
+ * error, in which case do a recv_skip() and drive on.
+ * Note, if we fail due to already having this guid,
+ * zfs_receive_one() will take care of it (ie,
+ * recv_skip() and return 0).
+ */
+ error = zfs_receive_impl(hdl, destname, NULL, flags, fd,
+ sendfs, stream_nv, stream_avl, top_zfs, sendsnap, cmdprops);
+ if (error == ENODATA) {
+ error = 0;
+ break;
+ }
+ anyerr |= error;
+ } while (error == 0);
+
+ if (drr->drr_payloadlen != 0 && recursive && fromsnap != NULL) {
+ /*
+ * Now that we have the fs's they sent us, try the
+ * renames again.
+ */
+ softerr = recv_incremental_replication(hdl, tofs, flags,
+ stream_nv, stream_avl, NULL);
+ }
+
+ if (raw && softerr == 0 && *top_zfs != NULL) {
+ softerr = recv_fix_encryption_hierarchy(hdl, *top_zfs,
+ stream_nv, stream_avl);
+ }
+
+out:
+ fsavl_destroy(stream_avl);
+ nvlist_free(stream_nv);
+ if (softerr)
+ error = -2;
+ if (anyerr)
+ error = -1;
+ return (error);
+}
+
+static void
+trunc_prop_errs(int truncated)
+{
+ ASSERT(truncated != 0);
+
+ if (truncated == 1)
+ (void) fprintf(stderr, dgettext(TEXT_DOMAIN,
+ "1 more property could not be set\n"));
+ else
+ (void) fprintf(stderr, dgettext(TEXT_DOMAIN,
+ "%d more properties could not be set\n"), truncated);
+}
+
+static int
+recv_skip(libzfs_handle_t *hdl, int fd, boolean_t byteswap)
+{
+ dmu_replay_record_t *drr;
+ void *buf = zfs_alloc(hdl, SPA_MAXBLOCKSIZE);
+ uint64_t payload_size;
+ char errbuf[1024];
+
+ (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
+ "cannot receive"));
+
+ /* XXX would be great to use lseek if possible... */
+ drr = buf;
+
+ while (recv_read(hdl, fd, drr, sizeof (dmu_replay_record_t),
+ byteswap, NULL) == 0) {
+ if (byteswap)
+ drr->drr_type = BSWAP_32(drr->drr_type);
+
+ switch (drr->drr_type) {
+ case DRR_BEGIN:
+ if (drr->drr_payloadlen != 0) {
+ (void) recv_read(hdl, fd, buf,
+ drr->drr_payloadlen, B_FALSE, NULL);
+ }
+ break;
+
+ case DRR_END:
+ free(buf);
+ return (0);
+
+ case DRR_OBJECT:
+ if (byteswap) {
+ drr->drr_u.drr_object.drr_bonuslen =
+ BSWAP_32(drr->drr_u.drr_object.
+ drr_bonuslen);
+ drr->drr_u.drr_object.drr_raw_bonuslen =
+ BSWAP_32(drr->drr_u.drr_object.
+ drr_raw_bonuslen);
+ }
+
+ payload_size =
+ DRR_OBJECT_PAYLOAD_SIZE(&drr->drr_u.drr_object);
+ (void) recv_read(hdl, fd, buf, payload_size,
+ B_FALSE, NULL);
+ break;
+
+ case DRR_WRITE:
+ if (byteswap) {
+ drr->drr_u.drr_write.drr_logical_size =
+ BSWAP_64(
+ drr->drr_u.drr_write.drr_logical_size);
+ drr->drr_u.drr_write.drr_compressed_size =
+ BSWAP_64(
+ drr->drr_u.drr_write.drr_compressed_size);
+ }
+ payload_size =
+ DRR_WRITE_PAYLOAD_SIZE(&drr->drr_u.drr_write);
+ (void) recv_read(hdl, fd, buf,
+ payload_size, B_FALSE, NULL);
+ break;
+ case DRR_SPILL:
+ if (byteswap) {
+ drr->drr_u.drr_spill.drr_length =
+ BSWAP_64(drr->drr_u.drr_spill.drr_length);
+ drr->drr_u.drr_spill.drr_compressed_size =
+ BSWAP_64(drr->drr_u.drr_spill.
+ drr_compressed_size);
+ }
+
+ payload_size =
+ DRR_SPILL_PAYLOAD_SIZE(&drr->drr_u.drr_spill);
+ (void) recv_read(hdl, fd, buf, payload_size,
+ B_FALSE, NULL);
+ break;
+ case DRR_WRITE_EMBEDDED:
+ if (byteswap) {
+ drr->drr_u.drr_write_embedded.drr_psize =
+ BSWAP_32(drr->drr_u.drr_write_embedded.
+ drr_psize);
+ }
+ (void) recv_read(hdl, fd, buf,
+ P2ROUNDUP(drr->drr_u.drr_write_embedded.drr_psize,
+ 8), B_FALSE, NULL);
+ break;
+ case DRR_OBJECT_RANGE:
+ case DRR_WRITE_BYREF:
+ case DRR_FREEOBJECTS:
+ case DRR_FREE:
+ break;
+
+ default:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "invalid record type"));
+ free(buf);
+ return (zfs_error(hdl, EZFS_BADSTREAM, errbuf));
+ }
+ }
+
+ free(buf);
+ return (-1);
+}
+
+static void
+recv_ecksum_set_aux(libzfs_handle_t *hdl, const char *target_snap,
+ boolean_t resumable, boolean_t checksum)
+{
+ char target_fs[ZFS_MAX_DATASET_NAME_LEN];
+
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, (checksum ?
+ "checksum mismatch" : "incomplete stream")));
+
+ if (!resumable)
+ return;
+ (void) strlcpy(target_fs, target_snap, sizeof (target_fs));
+ *strchr(target_fs, '@') = '\0';
+ zfs_handle_t *zhp = zfs_open(hdl, target_fs,
+ ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME);
+ if (zhp == NULL)
+ return;
+
+ char token_buf[ZFS_MAXPROPLEN];
+ int error = zfs_prop_get(zhp, ZFS_PROP_RECEIVE_RESUME_TOKEN,
+ token_buf, sizeof (token_buf),
+ NULL, NULL, 0, B_TRUE);
+ if (error == 0) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "checksum mismatch or incomplete stream.\n"
+ "Partially received snapshot is saved.\n"
+ "A resuming stream can be generated on the sending "
+ "system by running:\n"
+ " zfs send -t %s"),
+ token_buf);
+ }
+ zfs_close(zhp);
+}
+
+/*
+ * Prepare a new nvlist of properties that are to override (-o) or be excluded
+ * (-x) from the received dataset
+ * recvprops: received properties from the send stream
+ * cmdprops: raw input properties from command line
+ * origprops: properties, both locally-set and received, currently set on the
+ * target dataset if it exists, NULL otherwise.
+ * oxprops: valid output override (-o) and excluded (-x) properties
+ */
+static int
+zfs_setup_cmdline_props(libzfs_handle_t *hdl, zfs_type_t type,
+ char *fsname, boolean_t zoned, boolean_t recursive, boolean_t newfs,
+ boolean_t raw, boolean_t toplevel, nvlist_t *recvprops, nvlist_t *cmdprops,
+ nvlist_t *origprops, nvlist_t **oxprops, uint8_t **wkeydata_out,
+ uint_t *wkeylen_out, const char *errbuf)
+{
+ nvpair_t *nvp;
+ nvlist_t *oprops, *voprops;
+ zfs_handle_t *zhp = NULL;
+ zpool_handle_t *zpool_hdl = NULL;
+ char *cp;
+ int ret = 0;
+ char namebuf[ZFS_MAX_DATASET_NAME_LEN];
+
+ if (nvlist_empty(cmdprops))
+ return (0); /* No properties to override or exclude */
+
+ *oxprops = fnvlist_alloc();
+ oprops = fnvlist_alloc();
+
+ strlcpy(namebuf, fsname, ZFS_MAX_DATASET_NAME_LEN);
+
+ /*
+ * Get our dataset handle. The target dataset may not exist yet.
+ */
+ if (zfs_dataset_exists(hdl, namebuf, ZFS_TYPE_DATASET)) {
+ zhp = zfs_open(hdl, namebuf, ZFS_TYPE_DATASET);
+ if (zhp == NULL) {
+ ret = -1;
+ goto error;
+ }
+ }
+
+ /* open the zpool handle */
+ cp = strchr(namebuf, '/');
+ if (cp != NULL)
+ *cp = '\0';
+ zpool_hdl = zpool_open(hdl, namebuf);
+ if (zpool_hdl == NULL) {
+ ret = -1;
+ goto error;
+ }
+
+ /* restore namebuf to match fsname for later use */
+ if (cp != NULL)
+ *cp = '/';
+
+ /*
+ * first iteration: process excluded (-x) properties now and gather
+ * added (-o) properties to be later processed by zfs_valid_proplist()
+ */
+ nvp = NULL;
+ while ((nvp = nvlist_next_nvpair(cmdprops, nvp)) != NULL) {
+ const char *name = nvpair_name(nvp);
+ zfs_prop_t prop = zfs_name_to_prop(name);
+
+ /* "origin" is processed separately, don't handle it here */
+ if (prop == ZFS_PROP_ORIGIN)
+ continue;
+
+ /*
+ * we're trying to override or exclude a property that does not
+ * make sense for this type of dataset, but we don't want to
+ * fail if the receive is recursive: this comes in handy when
+ * the send stream contains, for instance, a child ZVOL and
+ * we're trying to receive it with "-o atime=on"
+ */
+ if (!zfs_prop_valid_for_type(prop, type, B_FALSE) &&
+ !zfs_prop_user(name)) {
+ if (recursive)
+ continue;
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "property '%s' does not apply to datasets of this "
+ "type"), name);
+ ret = zfs_error(hdl, EZFS_BADPROP, errbuf);
+ goto error;
+ }
+
+ /* raw streams can't override encryption properties */
+ if ((zfs_prop_encryption_key_param(prop) ||
+ prop == ZFS_PROP_ENCRYPTION) && raw) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "encryption property '%s' cannot "
+ "be set or excluded for raw streams."), name);
+ ret = zfs_error(hdl, EZFS_BADPROP, errbuf);
+ goto error;
+ }
+
+ /* incremental streams can only exclude encryption properties */
+ if ((zfs_prop_encryption_key_param(prop) ||
+ prop == ZFS_PROP_ENCRYPTION) && !newfs &&
+ nvpair_type(nvp) != DATA_TYPE_BOOLEAN) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "encryption property '%s' cannot "
+ "be set for incremental streams."), name);
+ ret = zfs_error(hdl, EZFS_BADPROP, errbuf);
+ goto error;
+ }
+
+ switch (nvpair_type(nvp)) {
+ case DATA_TYPE_BOOLEAN: /* -x property */
+ /*
+ * DATA_TYPE_BOOLEAN is the way we're asked to "exclude"
+ * a property: this is done by forcing an explicit
+ * inherit on the destination so the effective value is
+ * not the one we received from the send stream.
+ * We do this only if the property is not already
+ * locally-set, in which case its value will take
+ * priority over the received anyway.
+ */
+ if (nvlist_exists(origprops, name)) {
+ nvlist_t *attrs;
+ char *source = NULL;
+
+ attrs = fnvlist_lookup_nvlist(origprops, name);
+ if (nvlist_lookup_string(attrs,
+ ZPROP_SOURCE, &source) == 0 &&
+ strcmp(source, ZPROP_SOURCE_VAL_RECVD) != 0)
+ continue;
+ }
+ /*
+ * We can't force an explicit inherit on non-inheritable
+ * properties: if we're asked to exclude this kind of
+ * values we remove them from "recvprops" input nvlist.
+ */
+ if (!zfs_prop_inheritable(prop) &&
+ !zfs_prop_user(name) && /* can be inherited too */
+ nvlist_exists(recvprops, name))
+ fnvlist_remove(recvprops, name);
+ else
+ fnvlist_add_nvpair(*oxprops, nvp);
+ break;
+ case DATA_TYPE_STRING: /* -o property=value */
+ fnvlist_add_nvpair(oprops, nvp);
+ break;
+ default:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "property '%s' must be a string or boolean"), name);
+ ret = zfs_error(hdl, EZFS_BADPROP, errbuf);
+ goto error;
+ }
+ }
+
+ if (toplevel) {
+ /* convert override strings properties to native */
+ if ((voprops = zfs_valid_proplist(hdl, ZFS_TYPE_DATASET,
+ oprops, zoned, zhp, zpool_hdl, B_FALSE, errbuf)) == NULL) {
+ ret = zfs_error(hdl, EZFS_BADPROP, errbuf);
+ goto error;
+ }
+
+ /*
+ * zfs_crypto_create() requires the parent name. Get it
+ * by truncating the fsname copy stored in namebuf.
+ */
+ cp = strrchr(namebuf, '/');
+ if (cp != NULL)
+ *cp = '\0';
+
+ if (!raw && zfs_crypto_create(hdl, namebuf, voprops, NULL,
+ B_FALSE, wkeydata_out, wkeylen_out) != 0) {
+ fnvlist_free(voprops);
+ ret = zfs_error(hdl, EZFS_CRYPTOFAILED, errbuf);
+ goto error;
+ }
+
+ /* second pass: process "-o" properties */
+ fnvlist_merge(*oxprops, voprops);
+ fnvlist_free(voprops);
+ } else {
+ /* override props on child dataset are inherited */
+ nvp = NULL;
+ while ((nvp = nvlist_next_nvpair(oprops, nvp)) != NULL) {
+ const char *name = nvpair_name(nvp);
+ fnvlist_add_boolean(*oxprops, name);
+ }
+ }
+
+error:
+ if (zhp != NULL)
+ zfs_close(zhp);
+ if (zpool_hdl != NULL)
+ zpool_close(zpool_hdl);
+ fnvlist_free(oprops);
+ return (ret);
+}
+
+/*
+ * Restores a backup of tosnap from the file descriptor specified by infd.
+ */
+static int
+zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
+ const char *originsnap, recvflags_t *flags, dmu_replay_record_t *drr,
+ dmu_replay_record_t *drr_noswap, const char *sendfs, nvlist_t *stream_nv,
+ avl_tree_t *stream_avl, char **top_zfs,
+ const char *finalsnap, nvlist_t *cmdprops)
+{
+ time_t begin_time;
+ int ioctl_err, ioctl_errno, err;
+ char *cp;
+ struct drr_begin *drrb = &drr->drr_u.drr_begin;
+ char errbuf[1024];
+ const char *chopprefix;
+ boolean_t newfs = B_FALSE;
+ boolean_t stream_wantsnewfs;
+ boolean_t newprops = B_FALSE;
+ uint64_t read_bytes = 0;
+ uint64_t errflags = 0;
+ uint64_t parent_snapguid = 0;
+ prop_changelist_t *clp = NULL;
+ nvlist_t *snapprops_nvlist = NULL;
+ nvlist_t *snapholds_nvlist = NULL;
+ zprop_errflags_t prop_errflags;
+ nvlist_t *prop_errors = NULL;
+ boolean_t recursive;
+ char *snapname = NULL;
+ char destsnap[MAXPATHLEN * 2];
+ char origin[MAXNAMELEN];
+ char name[MAXPATHLEN];
+ char tmp_keylocation[MAXNAMELEN];
+ nvlist_t *rcvprops = NULL; /* props received from the send stream */
+ nvlist_t *oxprops = NULL; /* override (-o) and exclude (-x) props */
+ nvlist_t *origprops = NULL; /* original props (if destination exists) */
+ zfs_type_t type;
+ boolean_t toplevel = B_FALSE;
+ boolean_t zoned = B_FALSE;
+ boolean_t hastoken = B_FALSE;
+ boolean_t redacted;
+ uint8_t *wkeydata = NULL;
+ uint_t wkeylen = 0;
+
+ begin_time = time(NULL);
+ bzero(origin, MAXNAMELEN);
+ bzero(tmp_keylocation, MAXNAMELEN);
+
+ (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
+ "cannot receive"));
+
+ recursive = (nvlist_lookup_boolean(stream_nv, "not_recursive") ==
+ ENOENT);
+
+ /* Did the user request holds be skipped via zfs recv -k? */
+ boolean_t holds = flags->holds && !flags->skipholds;
+
+ if (stream_avl != NULL) {
+ char *keylocation = NULL;
+ nvlist_t *lookup = NULL;
+ nvlist_t *fs = fsavl_find(stream_avl, drrb->drr_toguid,
+ &snapname);
+
+ (void) nvlist_lookup_uint64(fs, "parentfromsnap",
+ &parent_snapguid);
+ err = nvlist_lookup_nvlist(fs, "props", &rcvprops);
+ if (err) {
+ VERIFY(0 == nvlist_alloc(&rcvprops, NV_UNIQUE_NAME, 0));
+ newprops = B_TRUE;
+ }
+
+ /*
+ * The keylocation property may only be set on encryption roots,
+ * but this dataset might not become an encryption root until
+ * recv_fix_encryption_hierarchy() is called. That function
+ * will fixup the keylocation anyway, so we temporarily unset
+ * the keylocation for now to avoid any errors from the receive
+ * ioctl.
+ */
+ err = nvlist_lookup_string(rcvprops,
+ zfs_prop_to_name(ZFS_PROP_KEYLOCATION), &keylocation);
+ if (err == 0) {
+ strcpy(tmp_keylocation, keylocation);
+ (void) nvlist_remove_all(rcvprops,
+ zfs_prop_to_name(ZFS_PROP_KEYLOCATION));
+ }
+
+ if (flags->canmountoff) {
+ VERIFY(0 == nvlist_add_uint64(rcvprops,
+ zfs_prop_to_name(ZFS_PROP_CANMOUNT), 0));
+ } else if (newprops) { /* nothing in rcvprops, eliminate it */
+ nvlist_free(rcvprops);
+ rcvprops = NULL;
+ newprops = B_FALSE;
+ }
+ if (0 == nvlist_lookup_nvlist(fs, "snapprops", &lookup)) {
+ VERIFY(0 == nvlist_lookup_nvlist(lookup,
+ snapname, &snapprops_nvlist));
+ }
+ if (holds) {
+ if (0 == nvlist_lookup_nvlist(fs, "snapholds",
+ &lookup)) {
+ VERIFY(0 == nvlist_lookup_nvlist(lookup,
+ snapname, &snapholds_nvlist));
+ }
+ }
+ }
+
+ cp = NULL;
+
+ /*
+ * Determine how much of the snapshot name stored in the stream
+ * we are going to tack on to the name they specified on the
+ * command line, and how much we are going to chop off.
+ *
+ * If they specified a snapshot, chop the entire name stored in
+ * the stream.
+ */
+ if (flags->istail) {
+ /*
+ * A filesystem was specified with -e. We want to tack on only
+ * the tail of the sent snapshot path.
+ */
+ if (strchr(tosnap, '@')) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "invalid "
+ "argument - snapshot not allowed with -e"));
+ err = zfs_error(hdl, EZFS_INVALIDNAME, errbuf);
+ goto out;
+ }
+
+ chopprefix = strrchr(sendfs, '/');
+
+ if (chopprefix == NULL) {
+ /*
+ * The tail is the poolname, so we need to
+ * prepend a path separator.
+ */
+ int len = strlen(drrb->drr_toname);
+ cp = malloc(len + 2);
+ cp[0] = '/';
+ (void) strcpy(&cp[1], drrb->drr_toname);
+ chopprefix = cp;
+ } else {
+ chopprefix = drrb->drr_toname + (chopprefix - sendfs);
+ }
+ } else if (flags->isprefix) {
+ /*
+ * A filesystem was specified with -d. We want to tack on
+ * everything but the first element of the sent snapshot path
+ * (all but the pool name).
+ */
+ if (strchr(tosnap, '@')) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "invalid "
+ "argument - snapshot not allowed with -d"));
+ err = zfs_error(hdl, EZFS_INVALIDNAME, errbuf);
+ goto out;
+ }
+
+ chopprefix = strchr(drrb->drr_toname, '/');
+ if (chopprefix == NULL)
+ chopprefix = strchr(drrb->drr_toname, '@');
+ } else if (strchr(tosnap, '@') == NULL) {
+ /*
+ * If a filesystem was specified without -d or -e, we want to
+ * tack on everything after the fs specified by 'zfs send'.
+ */
+ chopprefix = drrb->drr_toname + strlen(sendfs);
+ } else {
+ /* A snapshot was specified as an exact path (no -d or -e). */
+ if (recursive) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "cannot specify snapshot name for multi-snapshot "
+ "stream"));
+ err = zfs_error(hdl, EZFS_BADSTREAM, errbuf);
+ goto out;
+ }
+ chopprefix = drrb->drr_toname + strlen(drrb->drr_toname);
+ }
+
+ ASSERT(strstr(drrb->drr_toname, sendfs) == drrb->drr_toname);
+ ASSERT(chopprefix > drrb->drr_toname || strchr(sendfs, '/') == NULL);
+ ASSERT(chopprefix <= drrb->drr_toname + strlen(drrb->drr_toname) ||
+ strchr(sendfs, '/') == NULL);
+ ASSERT(chopprefix[0] == '/' || chopprefix[0] == '@' ||
+ chopprefix[0] == '\0');
+
+ /*
+ * Determine name of destination snapshot.
+ */
+ (void) strlcpy(destsnap, tosnap, sizeof (destsnap));
+ (void) strlcat(destsnap, chopprefix, sizeof (destsnap));
+ free(cp);
+ if (!zfs_name_valid(destsnap, ZFS_TYPE_SNAPSHOT)) {
+ err = zfs_error(hdl, EZFS_INVALIDNAME, errbuf);
+ goto out;
+ }
+
+ /*
+ * Determine the name of the origin snapshot.
+ */
+ if (originsnap) {
+ (void) strlcpy(origin, originsnap, sizeof (origin));
+ if (flags->verbose)
+ (void) printf("using provided clone origin %s\n",
+ origin);
+ } else if (drrb->drr_flags & DRR_FLAG_CLONE) {
+ if (guid_to_name(hdl, destsnap,
+ drrb->drr_fromguid, B_FALSE, origin) != 0) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "local origin for clone %s does not exist"),
+ destsnap);
+ err = zfs_error(hdl, EZFS_NOENT, errbuf);
+ goto out;
+ }
+ if (flags->verbose)
+ (void) printf("found clone origin %s\n", origin);
+ }
+
+ if ((DMU_GET_FEATUREFLAGS(drrb->drr_versioninfo) &
+ DMU_BACKUP_FEATURE_DEDUP)) {
+ (void) fprintf(stderr,
+ gettext("ERROR: \"zfs receive\" no longer supports "
+ "deduplicated send streams. Use\n"
+ "the \"zstream redup\" command to convert this stream "
+ "to a regular,\n"
+ "non-deduplicated stream.\n"));
+ err = zfs_error(hdl, EZFS_NOTSUP, errbuf);
+ goto out;
+ }
+
+ boolean_t resuming = DMU_GET_FEATUREFLAGS(drrb->drr_versioninfo) &
+ DMU_BACKUP_FEATURE_RESUMING;
+ boolean_t raw = DMU_GET_FEATUREFLAGS(drrb->drr_versioninfo) &
+ DMU_BACKUP_FEATURE_RAW;
+ boolean_t embedded = DMU_GET_FEATUREFLAGS(drrb->drr_versioninfo) &
+ DMU_BACKUP_FEATURE_EMBED_DATA;
+ stream_wantsnewfs = (drrb->drr_fromguid == 0 ||
+ (drrb->drr_flags & DRR_FLAG_CLONE) || originsnap) && !resuming;
+
+ if (stream_wantsnewfs) {
+ /*
+ * if the parent fs does not exist, look for it based on
+ * the parent snap GUID
+ */
+ (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
+ "cannot receive new filesystem stream"));
+
+ (void) strcpy(name, destsnap);
+ cp = strrchr(name, '/');
+ if (cp)
+ *cp = '\0';
+ if (cp &&
+ !zfs_dataset_exists(hdl, name, ZFS_TYPE_DATASET)) {
+ char suffix[ZFS_MAX_DATASET_NAME_LEN];
+ (void) strcpy(suffix, strrchr(destsnap, '/'));
+ if (guid_to_name(hdl, name, parent_snapguid,
+ B_FALSE, destsnap) == 0) {
+ *strchr(destsnap, '@') = '\0';
+ (void) strcat(destsnap, suffix);
+ }
+ }
+ } else {
+ /*
+ * If the fs does not exist, look for it based on the
+ * fromsnap GUID.
+ */
+ if (resuming) {
+ (void) snprintf(errbuf, sizeof (errbuf),
+ dgettext(TEXT_DOMAIN,
+ "cannot receive resume stream"));
+ } else {
+ (void) snprintf(errbuf, sizeof (errbuf),
+ dgettext(TEXT_DOMAIN,
+ "cannot receive incremental stream"));
+ }
+
+ (void) strcpy(name, destsnap);
+ *strchr(name, '@') = '\0';
+
+ /*
+ * If the exact receive path was specified and this is the
+ * topmost path in the stream, then if the fs does not exist we
+ * should look no further.
+ */
+ if ((flags->isprefix || (*(chopprefix = drrb->drr_toname +
+ strlen(sendfs)) != '\0' && *chopprefix != '@')) &&
+ !zfs_dataset_exists(hdl, name, ZFS_TYPE_DATASET)) {
+ char snap[ZFS_MAX_DATASET_NAME_LEN];
+ (void) strcpy(snap, strchr(destsnap, '@'));
+ if (guid_to_name(hdl, name, drrb->drr_fromguid,
+ B_FALSE, destsnap) == 0) {
+ *strchr(destsnap, '@') = '\0';
+ (void) strcat(destsnap, snap);
+ }
+ }
+ }
+
+ (void) strcpy(name, destsnap);
+ *strchr(name, '@') = '\0';
+
+ redacted = DMU_GET_FEATUREFLAGS(drrb->drr_versioninfo) &
+ DMU_BACKUP_FEATURE_REDACTED;
+
+ if (zfs_dataset_exists(hdl, name, ZFS_TYPE_DATASET)) {
+ zfs_cmd_t zc = {"\0"};
+ zfs_handle_t *zhp;
+ boolean_t encrypted;
+
+ (void) strcpy(zc.zc_name, name);
+
+ /*
+ * Destination fs exists. It must be one of these cases:
+ * - an incremental send stream
+ * - the stream specifies a new fs (full stream or clone)
+ * and they want us to blow away the existing fs (and
+ * have therefore specified -F and removed any snapshots)
+ * - we are resuming a failed receive.
+ */
+ if (stream_wantsnewfs) {
+ boolean_t is_volume = drrb->drr_type == DMU_OST_ZVOL;
+ if (!flags->force) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "destination '%s' exists\n"
+ "must specify -F to overwrite it"), name);
+ err = zfs_error(hdl, EZFS_EXISTS, errbuf);
+ goto out;
+ }
+ if (zfs_ioctl(hdl, ZFS_IOC_SNAPSHOT_LIST_NEXT,
+ &zc) == 0) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "destination has snapshots (eg. %s)\n"
+ "must destroy them to overwrite it"),
+ zc.zc_name);
+ err = zfs_error(hdl, EZFS_EXISTS, errbuf);
+ goto out;
+ }
+ if (is_volume && strrchr(name, '/') == NULL) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "destination %s is the root dataset\n"
+ "cannot overwrite with a ZVOL"),
+ name);
+ err = zfs_error(hdl, EZFS_EXISTS, errbuf);
+ goto out;
+ }
+ if (is_volume &&
+ zfs_ioctl(hdl, ZFS_IOC_DATASET_LIST_NEXT,
+ &zc) == 0) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "destination has children (eg. %s)\n"
+ "cannot overwrite with a ZVOL"),
+ zc.zc_name);
+ err = zfs_error(hdl, EZFS_WRONG_PARENT, errbuf);
+ goto out;
+ }
+ }
+
+ if ((zhp = zfs_open(hdl, name,
+ ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME)) == NULL) {
+ err = -1;
+ goto out;
+ }
+
+ if (stream_wantsnewfs &&
+ zhp->zfs_dmustats.dds_origin[0]) {
+ zfs_close(zhp);
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "destination '%s' is a clone\n"
+ "must destroy it to overwrite it"), name);
+ err = zfs_error(hdl, EZFS_EXISTS, errbuf);
+ goto out;
+ }
+
+ /*
+ * Raw sends can not be performed as an incremental on top
+ * of existing unencrypted datasets. zfs recv -F can't be
+ * used to blow away an existing encrypted filesystem. This
+ * is because it would require the dsl dir to point to the
+ * new key (or lack of a key) and the old key at the same
+ * time. The -F flag may still be used for deleting
+ * intermediate snapshots that would otherwise prevent the
+ * receive from working.
+ */
+ encrypted = zfs_prop_get_int(zhp, ZFS_PROP_ENCRYPTION) !=
+ ZIO_CRYPT_OFF;
+ if (!stream_wantsnewfs && !encrypted && raw) {
+ zfs_close(zhp);
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "cannot perform raw receive on top of "
+ "existing unencrypted dataset"));
+ err = zfs_error(hdl, EZFS_BADRESTORE, errbuf);
+ goto out;
+ }
+
+ if (stream_wantsnewfs && flags->force &&
+ ((raw && !encrypted) || encrypted)) {
+ zfs_close(zhp);
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "zfs receive -F cannot be used to destroy an "
+ "encrypted filesystem or overwrite an "
+ "unencrypted one with an encrypted one"));
+ err = zfs_error(hdl, EZFS_BADRESTORE, errbuf);
+ goto out;
+ }
+
+ if (!flags->dryrun && zhp->zfs_type == ZFS_TYPE_FILESYSTEM &&
+ stream_wantsnewfs) {
+ /* We can't do online recv in this case */
+ clp = changelist_gather(zhp, ZFS_PROP_NAME, 0,
+ flags->forceunmount ? MS_FORCE : 0);
+ if (clp == NULL) {
+ zfs_close(zhp);
+ err = -1;
+ goto out;
+ }
+ if (changelist_prefix(clp) != 0) {
+ changelist_free(clp);
+ zfs_close(zhp);
+ err = -1;
+ goto out;
+ }
+ }
+
+ /*
+ * If we are resuming a newfs, set newfs here so that we will
+ * mount it if the recv succeeds this time. We can tell
+ * that it was a newfs on the first recv because the fs
+ * itself will be inconsistent (if the fs existed when we
+ * did the first recv, we would have received it into
+ * .../%recv).
+ */
+ if (resuming && zfs_prop_get_int(zhp, ZFS_PROP_INCONSISTENT))
+ newfs = B_TRUE;
+
+ /* we want to know if we're zoned when validating -o|-x props */
+ zoned = zfs_prop_get_int(zhp, ZFS_PROP_ZONED);
+
+ /* may need this info later, get it now we have zhp around */
+ if (zfs_prop_get(zhp, ZFS_PROP_RECEIVE_RESUME_TOKEN, NULL, 0,
+ NULL, NULL, 0, B_TRUE) == 0)
+ hastoken = B_TRUE;
+
+ /* gather existing properties on destination */
+ origprops = fnvlist_alloc();
+ fnvlist_merge(origprops, zhp->zfs_props);
+ fnvlist_merge(origprops, zhp->zfs_user_props);
+
+ zfs_close(zhp);
+ } else {
+ zfs_handle_t *zhp;
+
+ /*
+ * Destination filesystem does not exist. Therefore we better
+ * be creating a new filesystem (either from a full backup, or
+ * a clone). It would therefore be invalid if the user
+ * specified only the pool name (i.e. if the destination name
+ * contained no slash character).
+ */
+ cp = strrchr(name, '/');
+
+ if (!stream_wantsnewfs || cp == NULL) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "destination '%s' does not exist"), name);
+ err = zfs_error(hdl, EZFS_NOENT, errbuf);
+ goto out;
+ }
+
+ /*
+ * Trim off the final dataset component so we perform the
+ * recvbackup ioctl to the filesystems's parent.
+ */
+ *cp = '\0';
+
+ if (flags->isprefix && !flags->istail && !flags->dryrun &&
+ create_parents(hdl, destsnap, strlen(tosnap)) != 0) {
+ err = zfs_error(hdl, EZFS_BADRESTORE, errbuf);
+ goto out;
+ }
+
+ /* validate parent */
+ zhp = zfs_open(hdl, name, ZFS_TYPE_DATASET);
+ if (zhp == NULL) {
+ err = zfs_error(hdl, EZFS_BADRESTORE, errbuf);
+ goto out;
+ }
+ if (zfs_get_type(zhp) != ZFS_TYPE_FILESYSTEM) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "parent '%s' is not a filesystem"), name);
+ err = zfs_error(hdl, EZFS_WRONG_PARENT, errbuf);
+ zfs_close(zhp);
+ goto out;
+ }
+
+ zfs_close(zhp);
+
+ newfs = B_TRUE;
+ *cp = '/';
+ }
+
+ if (flags->verbose) {
+ (void) printf("%s %s stream of %s into %s\n",
+ flags->dryrun ? "would receive" : "receiving",
+ drrb->drr_fromguid ? "incremental" : "full",
+ drrb->drr_toname, destsnap);
+ (void) fflush(stdout);
+ }
+
+ if (flags->dryrun) {
+ void *buf = zfs_alloc(hdl, SPA_MAXBLOCKSIZE);
+
+ /*
+ * We have read the DRR_BEGIN record, but we have
+ * not yet read the payload. For non-dryrun sends
+ * this will be done by the kernel, so we must
+ * emulate that here, before attempting to read
+ * more records.
+ */
+ err = recv_read(hdl, infd, buf, drr->drr_payloadlen,
+ flags->byteswap, NULL);
+ free(buf);
+ if (err != 0)
+ goto out;
+
+ err = recv_skip(hdl, infd, flags->byteswap);
+ goto out;
+ }
+
+ /*
+ * If this is the top-level dataset, record it so we can use it
+ * for recursive operations later.
+ */
+ if (top_zfs != NULL &&
+ (*top_zfs == NULL || strcmp(*top_zfs, name) == 0)) {
+ toplevel = B_TRUE;
+ if (*top_zfs == NULL)
+ *top_zfs = zfs_strdup(hdl, name);
+ }
+
+ if (drrb->drr_type == DMU_OST_ZVOL) {
+ type = ZFS_TYPE_VOLUME;
+ } else if (drrb->drr_type == DMU_OST_ZFS) {
+ type = ZFS_TYPE_FILESYSTEM;
+ } else {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "invalid record type: 0x%d"), drrb->drr_type);
+ err = zfs_error(hdl, EZFS_BADSTREAM, errbuf);
+ goto out;
+ }
+ if ((err = zfs_setup_cmdline_props(hdl, type, name, zoned, recursive,
+ stream_wantsnewfs, raw, toplevel, rcvprops, cmdprops, origprops,
+ &oxprops, &wkeydata, &wkeylen, errbuf)) != 0)
+ goto out;
+
+ /*
+ * When sending with properties (zfs send -p), the encryption property
+ * is not included because it is a SETONCE property and therefore
+ * treated as read only. However, we are always able to determine its
+ * value because raw sends will include it in the DRR_BDEGIN payload
+ * and non-raw sends with properties are not allowed for encrypted
+ * datasets. Therefore, if this is a non-raw properties stream, we can
+ * infer that the value should be ZIO_CRYPT_OFF and manually add that
+ * to the received properties.
+ */
+ if (stream_wantsnewfs && !raw && rcvprops != NULL &&
+ !nvlist_exists(cmdprops, zfs_prop_to_name(ZFS_PROP_ENCRYPTION))) {
+ if (oxprops == NULL)
+ oxprops = fnvlist_alloc();
+ fnvlist_add_uint64(oxprops,
+ zfs_prop_to_name(ZFS_PROP_ENCRYPTION), ZIO_CRYPT_OFF);
+ }
+
+ err = ioctl_err = lzc_receive_with_cmdprops(destsnap, rcvprops,
+ oxprops, wkeydata, wkeylen, origin, flags->force, flags->resumable,
+ raw, infd, drr_noswap, -1, &read_bytes, &errflags,
+ NULL, &prop_errors);
+ ioctl_errno = ioctl_err;
+ prop_errflags = errflags;
+
+ if (err == 0) {
+ nvpair_t *prop_err = NULL;
+
+ while ((prop_err = nvlist_next_nvpair(prop_errors,
+ prop_err)) != NULL) {
+ char tbuf[1024];
+ zfs_prop_t prop;
+ int intval;
+
+ prop = zfs_name_to_prop(nvpair_name(prop_err));
+ (void) nvpair_value_int32(prop_err, &intval);
+ if (strcmp(nvpair_name(prop_err),
+ ZPROP_N_MORE_ERRORS) == 0) {
+ trunc_prop_errs(intval);
+ break;
+ } else if (snapname == NULL || finalsnap == NULL ||
+ strcmp(finalsnap, snapname) == 0 ||
+ strcmp(nvpair_name(prop_err),
+ zfs_prop_to_name(ZFS_PROP_REFQUOTA)) != 0) {
+ /*
+ * Skip the special case of, for example,
+ * "refquota", errors on intermediate
+ * snapshots leading up to a final one.
+ * That's why we have all of the checks above.
+ *
+ * See zfs_ioctl.c's extract_delay_props() for
+ * a list of props which can fail on
+ * intermediate snapshots, but shouldn't
+ * affect the overall receive.
+ */
+ (void) snprintf(tbuf, sizeof (tbuf),
+ dgettext(TEXT_DOMAIN,
+ "cannot receive %s property on %s"),
+ nvpair_name(prop_err), name);
+ zfs_setprop_error(hdl, prop, intval, tbuf);
+ }
+ }
+ }
+
+ if (err == 0 && snapprops_nvlist) {
+ zfs_cmd_t zc = {"\0"};
+
+ (void) strcpy(zc.zc_name, destsnap);
+ zc.zc_cookie = B_TRUE; /* received */
+ if (zcmd_write_src_nvlist(hdl, &zc, snapprops_nvlist) == 0) {
+ (void) zfs_ioctl(hdl, ZFS_IOC_SET_PROP, &zc);
+ zcmd_free_nvlists(&zc);
+ }
+ }
+ if (err == 0 && snapholds_nvlist) {
+ nvpair_t *pair;
+ nvlist_t *holds, *errors = NULL;
+ int cleanup_fd = -1;
+
+ VERIFY(0 == nvlist_alloc(&holds, 0, KM_SLEEP));
+ for (pair = nvlist_next_nvpair(snapholds_nvlist, NULL);
+ pair != NULL;
+ pair = nvlist_next_nvpair(snapholds_nvlist, pair)) {
+ VERIFY(0 == nvlist_add_string(holds, destsnap,
+ nvpair_name(pair)));
+ }
+ (void) lzc_hold(holds, cleanup_fd, &errors);
+ nvlist_free(snapholds_nvlist);
+ nvlist_free(holds);
+ }
+
+ if (err && (ioctl_errno == ENOENT || ioctl_errno == EEXIST)) {
+ /*
+ * It may be that this snapshot already exists,
+ * in which case we want to consume & ignore it
+ * rather than failing.
+ */
+ avl_tree_t *local_avl;
+ nvlist_t *local_nv, *fs;
+ cp = strchr(destsnap, '@');
+
+ /*
+ * XXX Do this faster by just iterating over snaps in
+ * this fs. Also if zc_value does not exist, we will
+ * get a strange "does not exist" error message.
+ */
+ *cp = '\0';
+ if (gather_nvlist(hdl, destsnap, NULL, NULL, B_FALSE, B_TRUE,
+ B_FALSE, B_FALSE, B_FALSE, B_FALSE, B_FALSE, B_TRUE,
+ &local_nv, &local_avl) == 0) {
+ *cp = '@';
+ fs = fsavl_find(local_avl, drrb->drr_toguid, NULL);
+ fsavl_destroy(local_avl);
+ nvlist_free(local_nv);
+
+ if (fs != NULL) {
+ if (flags->verbose) {
+ (void) printf("snap %s already exists; "
+ "ignoring\n", destsnap);
+ }
+ err = ioctl_err = recv_skip(hdl, infd,
+ flags->byteswap);
+ }
+ }
+ *cp = '@';
+ }
+
+ if (ioctl_err != 0) {
+ switch (ioctl_errno) {
+ case ENODEV:
+ cp = strchr(destsnap, '@');
+ *cp = '\0';
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "most recent snapshot of %s does not\n"
+ "match incremental source"), destsnap);
+ (void) zfs_error(hdl, EZFS_BADRESTORE, errbuf);
+ *cp = '@';
+ break;
+ case ETXTBSY:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "destination %s has been modified\n"
+ "since most recent snapshot"), name);
+ (void) zfs_error(hdl, EZFS_BADRESTORE, errbuf);
+ break;
+ case EACCES:
+ if (raw && stream_wantsnewfs) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "failed to create encryption key"));
+ } else if (raw && !stream_wantsnewfs) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "encryption key does not match "
+ "existing key"));
+ } else {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "inherited key must be loaded"));
+ }
+ (void) zfs_error(hdl, EZFS_CRYPTOFAILED, errbuf);
+ break;
+ case EEXIST:
+ cp = strchr(destsnap, '@');
+ if (newfs) {
+ /* it's the containing fs that exists */
+ *cp = '\0';
+ }
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "destination already exists"));
+ (void) zfs_error_fmt(hdl, EZFS_EXISTS,
+ dgettext(TEXT_DOMAIN, "cannot restore to %s"),
+ destsnap);
+ *cp = '@';
+ break;
+ case EINVAL:
+ if (flags->resumable) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "kernel modules must be upgraded to "
+ "receive this stream."));
+ } else if (embedded && !raw) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "incompatible embedded data stream "
+ "feature with encrypted receive."));
+ }
+ (void) zfs_error(hdl, EZFS_BADSTREAM, errbuf);
+ break;
+ case ECKSUM:
+ case ZFS_ERR_STREAM_TRUNCATED:
+ recv_ecksum_set_aux(hdl, destsnap, flags->resumable,
+ ioctl_err == ECKSUM);
+ (void) zfs_error(hdl, EZFS_BADSTREAM, errbuf);
+ break;
+ case ZFS_ERR_STREAM_LARGE_BLOCK_MISMATCH:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "incremental send stream requires -L "
+ "(--large-block), to match previous receive."));
+ (void) zfs_error(hdl, EZFS_BADSTREAM, errbuf);
+ break;
+ case ENOTSUP:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "pool must be upgraded to receive this stream."));
+ (void) zfs_error(hdl, EZFS_BADVERSION, errbuf);
+ break;
+ case EDQUOT:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "destination %s space quota exceeded."), name);
+ (void) zfs_error(hdl, EZFS_NOSPC, errbuf);
+ break;
+ case ZFS_ERR_FROM_IVSET_GUID_MISSING:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "IV set guid missing. See errata %u at "
+ "https://zfsonlinux.org/msg/ZFS-8000-ER."),
+ ZPOOL_ERRATA_ZOL_8308_ENCRYPTION);
+ (void) zfs_error(hdl, EZFS_BADSTREAM, errbuf);
+ break;
+ case ZFS_ERR_FROM_IVSET_GUID_MISMATCH:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "IV set guid mismatch. See the 'zfs receive' "
+ "man page section\n discussing the limitations "
+ "of raw encrypted send streams."));
+ (void) zfs_error(hdl, EZFS_BADSTREAM, errbuf);
+ break;
+ case ZFS_ERR_SPILL_BLOCK_FLAG_MISSING:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Spill block flag missing for raw send.\n"
+ "The zfs software on the sending system must "
+ "be updated."));
+ (void) zfs_error(hdl, EZFS_BADSTREAM, errbuf);
+ break;
+ case EBUSY:
+ if (hastoken) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "destination %s contains "
+ "partially-complete state from "
+ "\"zfs receive -s\"."), name);
+ (void) zfs_error(hdl, EZFS_BUSY, errbuf);
+ break;
+ }
+ /* fallthru */
+ default:
+ (void) zfs_standard_error(hdl, ioctl_errno, errbuf);
+ }
+ }
+
+ /*
+ * Mount the target filesystem (if created). Also mount any
+ * children of the target filesystem if we did a replication
+ * receive (indicated by stream_avl being non-NULL).
+ */
+ if (clp) {
+ if (!flags->nomount)
+ err |= changelist_postfix(clp);
+ changelist_free(clp);
+ }
+
+ if ((newfs || stream_avl) && type == ZFS_TYPE_FILESYSTEM && !redacted)
+ flags->domount = B_TRUE;
+
+ if (prop_errflags & ZPROP_ERR_NOCLEAR) {
+ (void) fprintf(stderr, dgettext(TEXT_DOMAIN, "Warning: "
+ "failed to clear unreceived properties on %s"), name);
+ (void) fprintf(stderr, "\n");
+ }
+ if (prop_errflags & ZPROP_ERR_NORESTORE) {
+ (void) fprintf(stderr, dgettext(TEXT_DOMAIN, "Warning: "
+ "failed to restore original properties on %s"), name);
+ (void) fprintf(stderr, "\n");
+ }
+
+ if (err || ioctl_err) {
+ err = -1;
+ goto out;
+ }
+
+ if (flags->verbose) {
+ char buf1[64];
+ char buf2[64];
+ uint64_t bytes = read_bytes;
+ time_t delta = time(NULL) - begin_time;
+ if (delta == 0)
+ delta = 1;
+ zfs_nicebytes(bytes, buf1, sizeof (buf1));
+ zfs_nicebytes(bytes/delta, buf2, sizeof (buf1));
+
+ (void) printf("received %s stream in %lld seconds (%s/sec)\n",
+ buf1, (longlong_t)delta, buf2);
+ }
+
+ err = 0;
+out:
+ if (prop_errors != NULL)
+ nvlist_free(prop_errors);
+
+ if (tmp_keylocation[0] != '\0') {
+ VERIFY(0 == nvlist_add_string(rcvprops,
+ zfs_prop_to_name(ZFS_PROP_KEYLOCATION), tmp_keylocation));
+ }
+
+ if (newprops)
+ nvlist_free(rcvprops);
+
+ nvlist_free(oxprops);
+ nvlist_free(origprops);
+
+ return (err);
+}
+
+/*
+ * Check properties we were asked to override (both -o|-x)
+ */
+static boolean_t
+zfs_receive_checkprops(libzfs_handle_t *hdl, nvlist_t *props,
+ const char *errbuf)
+{
+ nvpair_t *nvp;
+ zfs_prop_t prop;
+ const char *name;
+
+ nvp = NULL;
+ while ((nvp = nvlist_next_nvpair(props, nvp)) != NULL) {
+ name = nvpair_name(nvp);
+ prop = zfs_name_to_prop(name);
+
+ if (prop == ZPROP_INVAL) {
+ if (!zfs_prop_user(name)) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "invalid property '%s'"), name);
+ return (B_FALSE);
+ }
+ continue;
+ }
+ /*
+ * "origin" is readonly but is used to receive datasets as
+ * clones so we don't raise an error here
+ */
+ if (prop == ZFS_PROP_ORIGIN)
+ continue;
+
+ /* encryption params have their own verification later */
+ if (prop == ZFS_PROP_ENCRYPTION ||
+ zfs_prop_encryption_key_param(prop))
+ continue;
+
+ /*
+ * cannot override readonly, set-once and other specific
+ * settable properties
+ */
+ if (zfs_prop_readonly(prop) || prop == ZFS_PROP_VERSION ||
+ prop == ZFS_PROP_VOLSIZE) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "invalid property '%s'"), name);
+ return (B_FALSE);
+ }
+ }
+
+ return (B_TRUE);
+}
+
+static int
+zfs_receive_impl(libzfs_handle_t *hdl, const char *tosnap,
+ const char *originsnap, recvflags_t *flags, int infd, const char *sendfs,
+ nvlist_t *stream_nv, avl_tree_t *stream_avl, char **top_zfs,
+ const char *finalsnap, nvlist_t *cmdprops)
+{
+ int err;
+ dmu_replay_record_t drr, drr_noswap;
+ struct drr_begin *drrb = &drr.drr_u.drr_begin;
+ char errbuf[1024];
+ zio_cksum_t zcksum = { { 0 } };
+ uint64_t featureflags;
+ int hdrtype;
+
+ (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
+ "cannot receive"));
+
+ /* check cmdline props, raise an error if they cannot be received */
+ if (!zfs_receive_checkprops(hdl, cmdprops, errbuf)) {
+ return (zfs_error(hdl, EZFS_BADPROP, errbuf));
+ }
+
+ if (flags->isprefix &&
+ !zfs_dataset_exists(hdl, tosnap, ZFS_TYPE_DATASET)) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "specified fs "
+ "(%s) does not exist"), tosnap);
+ return (zfs_error(hdl, EZFS_NOENT, errbuf));
+ }
+ if (originsnap &&
+ !zfs_dataset_exists(hdl, originsnap, ZFS_TYPE_DATASET)) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "specified origin fs "
+ "(%s) does not exist"), originsnap);
+ return (zfs_error(hdl, EZFS_NOENT, errbuf));
+ }
+
+ /* read in the BEGIN record */
+ if (0 != (err = recv_read(hdl, infd, &drr, sizeof (drr), B_FALSE,
+ &zcksum)))
+ return (err);
+
+ if (drr.drr_type == DRR_END || drr.drr_type == BSWAP_32(DRR_END)) {
+ /* It's the double end record at the end of a package */
+ return (ENODATA);
+ }
+
+ /* the kernel needs the non-byteswapped begin record */
+ drr_noswap = drr;
+
+ flags->byteswap = B_FALSE;
+ if (drrb->drr_magic == BSWAP_64(DMU_BACKUP_MAGIC)) {
+ /*
+ * We computed the checksum in the wrong byteorder in
+ * recv_read() above; do it again correctly.
+ */
+ bzero(&zcksum, sizeof (zio_cksum_t));
+ fletcher_4_incremental_byteswap(&drr, sizeof (drr), &zcksum);
+ flags->byteswap = B_TRUE;
+
+ drr.drr_type = BSWAP_32(drr.drr_type);
+ drr.drr_payloadlen = BSWAP_32(drr.drr_payloadlen);
+ drrb->drr_magic = BSWAP_64(drrb->drr_magic);
+ drrb->drr_versioninfo = BSWAP_64(drrb->drr_versioninfo);
+ drrb->drr_creation_time = BSWAP_64(drrb->drr_creation_time);
+ drrb->drr_type = BSWAP_32(drrb->drr_type);
+ drrb->drr_flags = BSWAP_32(drrb->drr_flags);
+ drrb->drr_toguid = BSWAP_64(drrb->drr_toguid);
+ drrb->drr_fromguid = BSWAP_64(drrb->drr_fromguid);
+ }
+
+ if (drrb->drr_magic != DMU_BACKUP_MAGIC || drr.drr_type != DRR_BEGIN) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "invalid "
+ "stream (bad magic number)"));
+ return (zfs_error(hdl, EZFS_BADSTREAM, errbuf));
+ }
+
+ featureflags = DMU_GET_FEATUREFLAGS(drrb->drr_versioninfo);
+ hdrtype = DMU_GET_STREAM_HDRTYPE(drrb->drr_versioninfo);
+
+ if (!DMU_STREAM_SUPPORTED(featureflags) ||
+ (hdrtype != DMU_SUBSTREAM && hdrtype != DMU_COMPOUNDSTREAM)) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "stream has unsupported feature, feature flags = %lx"),
+ featureflags);
+ return (zfs_error(hdl, EZFS_BADSTREAM, errbuf));
+ }
+
+ /* Holds feature is set once in the compound stream header. */
+ if (featureflags & DMU_BACKUP_FEATURE_HOLDS)
+ flags->holds = B_TRUE;
+
+ if (strchr(drrb->drr_toname, '@') == NULL) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "invalid "
+ "stream (bad snapshot name)"));
+ return (zfs_error(hdl, EZFS_BADSTREAM, errbuf));
+ }
+
+ if (DMU_GET_STREAM_HDRTYPE(drrb->drr_versioninfo) == DMU_SUBSTREAM) {
+ char nonpackage_sendfs[ZFS_MAX_DATASET_NAME_LEN];
+ if (sendfs == NULL) {
+ /*
+ * We were not called from zfs_receive_package(). Get
+ * the fs specified by 'zfs send'.
+ */
+ char *cp;
+ (void) strlcpy(nonpackage_sendfs,
+ drr.drr_u.drr_begin.drr_toname,
+ sizeof (nonpackage_sendfs));
+ if ((cp = strchr(nonpackage_sendfs, '@')) != NULL)
+ *cp = '\0';
+ sendfs = nonpackage_sendfs;
+ VERIFY(finalsnap == NULL);
+ }
+ return (zfs_receive_one(hdl, infd, tosnap, originsnap, flags,
+ &drr, &drr_noswap, sendfs, stream_nv, stream_avl, top_zfs,
+ finalsnap, cmdprops));
+ } else {
+ assert(DMU_GET_STREAM_HDRTYPE(drrb->drr_versioninfo) ==
+ DMU_COMPOUNDSTREAM);
+ return (zfs_receive_package(hdl, infd, tosnap, flags, &drr,
+ &zcksum, top_zfs, cmdprops));
+ }
+}
+
+/*
+ * Restores a backup of tosnap from the file descriptor specified by infd.
+ * Return 0 on total success, -2 if some things couldn't be
+ * destroyed/renamed/promoted, -1 if some things couldn't be received.
+ * (-1 will override -2, if -1 and the resumable flag was specified the
+ * transfer can be resumed if the sending side supports it).
+ */
+int
+zfs_receive(libzfs_handle_t *hdl, const char *tosnap, nvlist_t *props,
+ recvflags_t *flags, int infd, avl_tree_t *stream_avl)
+{
+ char *top_zfs = NULL;
+ int err;
+ struct stat sb;
+ char *originsnap = NULL;
+
+ /*
+ * The only way fstat can fail is if we do not have a valid file
+ * descriptor.
+ */
+ if (fstat(infd, &sb) == -1) {
+ perror("fstat");
+ return (-2);
+ }
+
+ /*
+ * It is not uncommon for gigabytes to be processed in zfs receive.
+ * Speculatively increase the buffer size if supported by the platform.
+ */
+ if (S_ISFIFO(sb.st_mode))
+ libzfs_set_pipe_max(infd);
+
+ if (props) {
+ err = nvlist_lookup_string(props, "origin", &originsnap);
+ if (err && err != ENOENT)
+ return (err);
+ }
+
+ err = zfs_receive_impl(hdl, tosnap, originsnap, flags, infd, NULL, NULL,
+ stream_avl, &top_zfs, NULL, props);
+
+ if (err == 0 && !flags->nomount && flags->domount && top_zfs) {
+ zfs_handle_t *zhp = NULL;
+ prop_changelist_t *clp = NULL;
+
+ zhp = zfs_open(hdl, top_zfs,
+ ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME);
+ if (zhp == NULL) {
+ err = -1;
+ goto out;
+ } else {
+ if (zhp->zfs_type == ZFS_TYPE_VOLUME) {
+ zfs_close(zhp);
+ goto out;
+ }
+
+ clp = changelist_gather(zhp, ZFS_PROP_MOUNTPOINT,
+ CL_GATHER_MOUNT_ALWAYS,
+ flags->forceunmount ? MS_FORCE : 0);
+ zfs_close(zhp);
+ if (clp == NULL) {
+ err = -1;
+ goto out;
+ }
+
+ /* mount and share received datasets */
+ err = changelist_postfix(clp);
+ changelist_free(clp);
+ if (err != 0)
+ err = -1;
+ }
+ }
+
+out:
+ if (top_zfs)
+ free(top_zfs);
+
+ return (err);
+}
diff --git a/lib/libzfs/libzfs_status.c b/lib/libzfs/libzfs_status.c
new file mode 100644
index 000000000000..435937041608
--- /dev/null
+++ b/lib/libzfs/libzfs_status.c
@@ -0,0 +1,508 @@
+/*
+ * 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) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012 by Delphix. All rights reserved.
+ * Copyright (c) 2013 Steven Hartland. All rights reserved.
+ */
+
+/*
+ * This file contains the functions which analyze the status of a pool. This
+ * include both the status of an active pool, as well as the status exported
+ * pools. Returns one of the ZPOOL_STATUS_* defines describing the status of
+ * the pool. This status is independent (to a certain degree) from the state of
+ * the pool. A pool's state describes only whether or not it is capable of
+ * providing the necessary fault tolerance for data. The status describes the
+ * overall status of devices. A pool that is online can still have a device
+ * that is experiencing errors.
+ *
+ * Only a subset of the possible faults can be detected using 'zpool status',
+ * and not all possible errors correspond to a FMA message ID. The explanation
+ * is left up to the caller, depending on whether it is a live pool or an
+ * import.
+ */
+
+#include <libzfs.h>
+#include <libzutil.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/systeminfo.h>
+#include "libzfs_impl.h"
+#include "zfeature_common.h"
+
+/*
+ * Message ID table. This must be kept in sync with the ZPOOL_STATUS_* defines
+ * in include/libzfs.h. Note that there are some status results which go past
+ * the end of this table, and hence have no associated message ID.
+ */
+static char *zfs_msgid_table[] = {
+ "ZFS-8000-14", /* ZPOOL_STATUS_CORRUPT_CACHE */
+ "ZFS-8000-2Q", /* ZPOOL_STATUS_MISSING_DEV_R */
+ "ZFS-8000-3C", /* ZPOOL_STATUS_MISSING_DEV_NR */
+ "ZFS-8000-4J", /* ZPOOL_STATUS_CORRUPT_LABEL_R */
+ "ZFS-8000-5E", /* ZPOOL_STATUS_CORRUPT_LABEL_NR */
+ "ZFS-8000-6X", /* ZPOOL_STATUS_BAD_GUID_SUM */
+ "ZFS-8000-72", /* ZPOOL_STATUS_CORRUPT_POOL */
+ "ZFS-8000-8A", /* ZPOOL_STATUS_CORRUPT_DATA */
+ "ZFS-8000-9P", /* ZPOOL_STATUS_FAILING_DEV */
+ "ZFS-8000-A5", /* ZPOOL_STATUS_VERSION_NEWER */
+ "ZFS-8000-EY", /* ZPOOL_STATUS_HOSTID_MISMATCH */
+ "ZFS-8000-EY", /* ZPOOL_STATUS_HOSTID_ACTIVE */
+ "ZFS-8000-EY", /* ZPOOL_STATUS_HOSTID_REQUIRED */
+ "ZFS-8000-HC", /* ZPOOL_STATUS_IO_FAILURE_WAIT */
+ "ZFS-8000-JQ", /* ZPOOL_STATUS_IO_FAILURE_CONTINUE */
+ "ZFS-8000-MM", /* ZPOOL_STATUS_IO_FAILURE_MMP */
+ "ZFS-8000-K4", /* ZPOOL_STATUS_BAD_LOG */
+ "ZFS-8000-ER", /* ZPOOL_STATUS_ERRATA */
+ /*
+ * The following results have no message ID.
+ * ZPOOL_STATUS_UNSUP_FEAT_READ
+ * ZPOOL_STATUS_UNSUP_FEAT_WRITE
+ * ZPOOL_STATUS_FAULTED_DEV_R
+ * ZPOOL_STATUS_FAULTED_DEV_NR
+ * ZPOOL_STATUS_VERSION_OLDER
+ * ZPOOL_STATUS_FEAT_DISABLED
+ * ZPOOL_STATUS_RESILVERING
+ * ZPOOL_STATUS_OFFLINE_DEV
+ * ZPOOL_STATUS_REMOVED_DEV
+ * ZPOOL_STATUS_REBUILDING
+ * ZPOOL_STATUS_REBUILD_SCRUB
+ * ZPOOL_STATUS_OK
+ */
+};
+
+#define NMSGID (sizeof (zfs_msgid_table) / sizeof (zfs_msgid_table[0]))
+
+/* ARGSUSED */
+static int
+vdev_missing(vdev_stat_t *vs, uint_t vsc)
+{
+ return (vs->vs_state == VDEV_STATE_CANT_OPEN &&
+ vs->vs_aux == VDEV_AUX_OPEN_FAILED);
+}
+
+/* ARGSUSED */
+static int
+vdev_faulted(vdev_stat_t *vs, uint_t vsc)
+{
+ return (vs->vs_state == VDEV_STATE_FAULTED);
+}
+
+/* ARGSUSED */
+static int
+vdev_errors(vdev_stat_t *vs, uint_t vsc)
+{
+ return (vs->vs_state == VDEV_STATE_DEGRADED ||
+ vs->vs_read_errors != 0 || vs->vs_write_errors != 0 ||
+ vs->vs_checksum_errors != 0);
+}
+
+/* ARGSUSED */
+static int
+vdev_broken(vdev_stat_t *vs, uint_t vsc)
+{
+ return (vs->vs_state == VDEV_STATE_CANT_OPEN);
+}
+
+/* ARGSUSED */
+static int
+vdev_offlined(vdev_stat_t *vs, uint_t vsc)
+{
+ return (vs->vs_state == VDEV_STATE_OFFLINE);
+}
+
+/* ARGSUSED */
+static int
+vdev_removed(vdev_stat_t *vs, uint_t vsc)
+{
+ return (vs->vs_state == VDEV_STATE_REMOVED);
+}
+
+static int
+vdev_non_native_ashift(vdev_stat_t *vs, uint_t vsc)
+{
+ if (getenv("ZPOOL_STATUS_NON_NATIVE_ASHIFT_IGNORE") != NULL)
+ return (0);
+
+ return (VDEV_STAT_VALID(vs_physical_ashift, vsc) &&
+ vs->vs_configured_ashift < vs->vs_physical_ashift);
+}
+
+/*
+ * Detect if any leaf devices that have seen errors or could not be opened.
+ */
+static boolean_t
+find_vdev_problem(nvlist_t *vdev, int (*func)(vdev_stat_t *, uint_t),
+ boolean_t ignore_replacing)
+{
+ nvlist_t **child;
+ vdev_stat_t *vs;
+ uint_t c, vsc, children;
+
+ /*
+ * Ignore problems within a 'replacing' vdev, since we're presumably in
+ * the process of repairing any such errors, and don't want to call them
+ * out again. We'll pick up the fact that a resilver is happening
+ * later.
+ */
+ if (ignore_replacing == B_TRUE) {
+ char *type;
+
+ verify(nvlist_lookup_string(vdev, ZPOOL_CONFIG_TYPE,
+ &type) == 0);
+ if (strcmp(type, VDEV_TYPE_REPLACING) == 0)
+ return (B_FALSE);
+ }
+
+ if (nvlist_lookup_nvlist_array(vdev, ZPOOL_CONFIG_CHILDREN, &child,
+ &children) == 0) {
+ for (c = 0; c < children; c++)
+ if (find_vdev_problem(child[c], func, ignore_replacing))
+ return (B_TRUE);
+ } else {
+ verify(nvlist_lookup_uint64_array(vdev, ZPOOL_CONFIG_VDEV_STATS,
+ (uint64_t **)&vs, &vsc) == 0);
+
+ if (func(vs, vsc) != 0)
+ return (B_TRUE);
+ }
+
+ /*
+ * Check any L2 cache devs
+ */
+ if (nvlist_lookup_nvlist_array(vdev, ZPOOL_CONFIG_L2CACHE, &child,
+ &children) == 0) {
+ for (c = 0; c < children; c++)
+ if (find_vdev_problem(child[c], func, ignore_replacing))
+ return (B_TRUE);
+ }
+
+ return (B_FALSE);
+}
+
+/*
+ * Active pool health status.
+ *
+ * To determine the status for a pool, we make several passes over the config,
+ * picking the most egregious error we find. In order of importance, we do the
+ * following:
+ *
+ * - Check for a complete and valid configuration
+ * - Look for any faulted or missing devices in a non-replicated config
+ * - Check for any data errors
+ * - Check for any faulted or missing devices in a replicated config
+ * - Look for any devices showing errors
+ * - Check for any resilvering or rebuilding devices
+ *
+ * There can obviously be multiple errors within a single pool, so this routine
+ * only picks the most damaging of all the current errors to report.
+ */
+static zpool_status_t
+check_status(nvlist_t *config, boolean_t isimport, zpool_errata_t *erratap)
+{
+ nvlist_t *nvroot;
+ vdev_stat_t *vs;
+ pool_scan_stat_t *ps = NULL;
+ uint_t vsc, psc;
+ uint64_t nerr;
+ uint64_t version;
+ uint64_t stateval;
+ uint64_t suspended;
+ uint64_t hostid = 0;
+ uint64_t errata = 0;
+ unsigned long system_hostid = get_system_hostid();
+
+ verify(nvlist_lookup_uint64(config, ZPOOL_CONFIG_VERSION,
+ &version) == 0);
+ verify(nvlist_lookup_nvlist(config, ZPOOL_CONFIG_VDEV_TREE,
+ &nvroot) == 0);
+ verify(nvlist_lookup_uint64_array(nvroot, ZPOOL_CONFIG_VDEV_STATS,
+ (uint64_t **)&vs, &vsc) == 0);
+ verify(nvlist_lookup_uint64(config, ZPOOL_CONFIG_POOL_STATE,
+ &stateval) == 0);
+
+ /*
+ * Currently resilvering a vdev
+ */
+ (void) nvlist_lookup_uint64_array(nvroot, ZPOOL_CONFIG_SCAN_STATS,
+ (uint64_t **)&ps, &psc);
+ if (ps != NULL && ps->pss_func == POOL_SCAN_RESILVER &&
+ ps->pss_state == DSS_SCANNING)
+ return (ZPOOL_STATUS_RESILVERING);
+
+ /*
+ * Currently rebuilding a vdev, check top-level vdevs.
+ */
+ vdev_rebuild_stat_t *vrs = NULL;
+ nvlist_t **child;
+ uint_t c, i, children;
+ uint64_t rebuild_end_time = 0;
+ if (nvlist_lookup_nvlist_array(nvroot, ZPOOL_CONFIG_CHILDREN,
+ &child, &children) == 0) {
+ for (c = 0; c < children; c++) {
+ if ((nvlist_lookup_uint64_array(child[c],
+ ZPOOL_CONFIG_REBUILD_STATS,
+ (uint64_t **)&vrs, &i) == 0) && (vrs != NULL)) {
+ uint64_t state = vrs->vrs_state;
+
+ if (state == VDEV_REBUILD_ACTIVE) {
+ return (ZPOOL_STATUS_REBUILDING);
+ } else if (state == VDEV_REBUILD_COMPLETE &&
+ vrs->vrs_end_time > rebuild_end_time) {
+ rebuild_end_time = vrs->vrs_end_time;
+ }
+ }
+ }
+
+ /*
+ * If we can determine when the last scrub was run, and it
+ * was before the last rebuild completed, then recommend
+ * that the pool be scrubbed to verify all checksums. When
+ * ps is NULL we can infer the pool has never been scrubbed.
+ */
+ if (rebuild_end_time > 0) {
+ if (ps != NULL) {
+ if ((ps->pss_state == DSS_FINISHED &&
+ ps->pss_func == POOL_SCAN_SCRUB &&
+ rebuild_end_time > ps->pss_end_time) ||
+ ps->pss_state == DSS_NONE)
+ return (ZPOOL_STATUS_REBUILD_SCRUB);
+ } else {
+ return (ZPOOL_STATUS_REBUILD_SCRUB);
+ }
+ }
+ }
+
+ /*
+ * The multihost property is set and the pool may be active.
+ */
+ if (vs->vs_state == VDEV_STATE_CANT_OPEN &&
+ vs->vs_aux == VDEV_AUX_ACTIVE) {
+ mmp_state_t mmp_state;
+ nvlist_t *nvinfo;
+
+ nvinfo = fnvlist_lookup_nvlist(config, ZPOOL_CONFIG_LOAD_INFO);
+ mmp_state = fnvlist_lookup_uint64(nvinfo,
+ ZPOOL_CONFIG_MMP_STATE);
+
+ if (mmp_state == MMP_STATE_ACTIVE)
+ return (ZPOOL_STATUS_HOSTID_ACTIVE);
+ else if (mmp_state == MMP_STATE_NO_HOSTID)
+ return (ZPOOL_STATUS_HOSTID_REQUIRED);
+ else
+ return (ZPOOL_STATUS_HOSTID_MISMATCH);
+ }
+
+ /*
+ * Pool last accessed by another system.
+ */
+ (void) nvlist_lookup_uint64(config, ZPOOL_CONFIG_HOSTID, &hostid);
+ if (hostid != 0 && (unsigned long)hostid != system_hostid &&
+ stateval == POOL_STATE_ACTIVE)
+ return (ZPOOL_STATUS_HOSTID_MISMATCH);
+
+ /*
+ * Newer on-disk version.
+ */
+ if (vs->vs_state == VDEV_STATE_CANT_OPEN &&
+ vs->vs_aux == VDEV_AUX_VERSION_NEWER)
+ return (ZPOOL_STATUS_VERSION_NEWER);
+
+ /*
+ * Unsupported feature(s).
+ */
+ if (vs->vs_state == VDEV_STATE_CANT_OPEN &&
+ vs->vs_aux == VDEV_AUX_UNSUP_FEAT) {
+ nvlist_t *nvinfo;
+
+ verify(nvlist_lookup_nvlist(config, ZPOOL_CONFIG_LOAD_INFO,
+ &nvinfo) == 0);
+ if (nvlist_exists(nvinfo, ZPOOL_CONFIG_CAN_RDONLY))
+ return (ZPOOL_STATUS_UNSUP_FEAT_WRITE);
+ return (ZPOOL_STATUS_UNSUP_FEAT_READ);
+ }
+
+ /*
+ * Check that the config is complete.
+ */
+ if (vs->vs_state == VDEV_STATE_CANT_OPEN &&
+ vs->vs_aux == VDEV_AUX_BAD_GUID_SUM)
+ return (ZPOOL_STATUS_BAD_GUID_SUM);
+
+ /*
+ * Check whether the pool has suspended.
+ */
+ if (nvlist_lookup_uint64(config, ZPOOL_CONFIG_SUSPENDED,
+ &suspended) == 0) {
+ uint64_t reason;
+
+ if (nvlist_lookup_uint64(config, ZPOOL_CONFIG_SUSPENDED_REASON,
+ &reason) == 0 && reason == ZIO_SUSPEND_MMP)
+ return (ZPOOL_STATUS_IO_FAILURE_MMP);
+
+ if (suspended == ZIO_FAILURE_MODE_CONTINUE)
+ return (ZPOOL_STATUS_IO_FAILURE_CONTINUE);
+ return (ZPOOL_STATUS_IO_FAILURE_WAIT);
+ }
+
+ /*
+ * Could not read a log.
+ */
+ if (vs->vs_state == VDEV_STATE_CANT_OPEN &&
+ vs->vs_aux == VDEV_AUX_BAD_LOG) {
+ return (ZPOOL_STATUS_BAD_LOG);
+ }
+
+ /*
+ * Bad devices in non-replicated config.
+ */
+ if (vs->vs_state == VDEV_STATE_CANT_OPEN &&
+ find_vdev_problem(nvroot, vdev_faulted, B_TRUE))
+ return (ZPOOL_STATUS_FAULTED_DEV_NR);
+
+ if (vs->vs_state == VDEV_STATE_CANT_OPEN &&
+ find_vdev_problem(nvroot, vdev_missing, B_TRUE))
+ return (ZPOOL_STATUS_MISSING_DEV_NR);
+
+ if (vs->vs_state == VDEV_STATE_CANT_OPEN &&
+ find_vdev_problem(nvroot, vdev_broken, B_TRUE))
+ return (ZPOOL_STATUS_CORRUPT_LABEL_NR);
+
+ /*
+ * Corrupted pool metadata
+ */
+ if (vs->vs_state == VDEV_STATE_CANT_OPEN &&
+ vs->vs_aux == VDEV_AUX_CORRUPT_DATA)
+ return (ZPOOL_STATUS_CORRUPT_POOL);
+
+ /*
+ * Persistent data errors.
+ */
+ if (!isimport) {
+ if (nvlist_lookup_uint64(config, ZPOOL_CONFIG_ERRCOUNT,
+ &nerr) == 0 && nerr != 0)
+ return (ZPOOL_STATUS_CORRUPT_DATA);
+ }
+
+ /*
+ * Missing devices in a replicated config.
+ */
+ if (find_vdev_problem(nvroot, vdev_faulted, B_TRUE))
+ return (ZPOOL_STATUS_FAULTED_DEV_R);
+ if (find_vdev_problem(nvroot, vdev_missing, B_TRUE))
+ return (ZPOOL_STATUS_MISSING_DEV_R);
+ if (find_vdev_problem(nvroot, vdev_broken, B_TRUE))
+ return (ZPOOL_STATUS_CORRUPT_LABEL_R);
+
+ /*
+ * Devices with errors
+ */
+ if (!isimport && find_vdev_problem(nvroot, vdev_errors, B_TRUE))
+ return (ZPOOL_STATUS_FAILING_DEV);
+
+ /*
+ * Offlined devices
+ */
+ if (find_vdev_problem(nvroot, vdev_offlined, B_TRUE))
+ return (ZPOOL_STATUS_OFFLINE_DEV);
+
+ /*
+ * Removed device
+ */
+ if (find_vdev_problem(nvroot, vdev_removed, B_TRUE))
+ return (ZPOOL_STATUS_REMOVED_DEV);
+
+ /*
+ * Suboptimal, but usable, ashift configuration.
+ */
+ if (find_vdev_problem(nvroot, vdev_non_native_ashift, B_FALSE))
+ return (ZPOOL_STATUS_NON_NATIVE_ASHIFT);
+
+ /*
+ * Informational errata available.
+ */
+ (void) nvlist_lookup_uint64(config, ZPOOL_CONFIG_ERRATA, &errata);
+ if (errata) {
+ *erratap = errata;
+ return (ZPOOL_STATUS_ERRATA);
+ }
+
+ /*
+ * Outdated, but usable, version
+ */
+ if (SPA_VERSION_IS_SUPPORTED(version) && version != SPA_VERSION)
+ return (ZPOOL_STATUS_VERSION_OLDER);
+
+ /*
+ * Usable pool with disabled features
+ */
+ if (version >= SPA_VERSION_FEATURES) {
+ int i;
+ nvlist_t *feat;
+
+ if (isimport) {
+ feat = fnvlist_lookup_nvlist(config,
+ ZPOOL_CONFIG_LOAD_INFO);
+ if (nvlist_exists(feat, ZPOOL_CONFIG_ENABLED_FEAT))
+ feat = fnvlist_lookup_nvlist(feat,
+ ZPOOL_CONFIG_ENABLED_FEAT);
+ } else {
+ feat = fnvlist_lookup_nvlist(config,
+ ZPOOL_CONFIG_FEATURE_STATS);
+ }
+
+ for (i = 0; i < SPA_FEATURES; i++) {
+ zfeature_info_t *fi = &spa_feature_table[i];
+ if (!nvlist_exists(feat, fi->fi_guid))
+ return (ZPOOL_STATUS_FEAT_DISABLED);
+ }
+ }
+
+ return (ZPOOL_STATUS_OK);
+}
+
+zpool_status_t
+zpool_get_status(zpool_handle_t *zhp, char **msgid, zpool_errata_t *errata)
+{
+ zpool_status_t ret = check_status(zhp->zpool_config, B_FALSE, errata);
+ if (msgid != NULL) {
+ if (ret >= NMSGID)
+ *msgid = NULL;
+ else
+ *msgid = zfs_msgid_table[ret];
+ }
+ return (ret);
+}
+
+zpool_status_t
+zpool_import_status(nvlist_t *config, char **msgid, zpool_errata_t *errata)
+{
+ zpool_status_t ret = check_status(config, B_TRUE, errata);
+
+ if (ret >= NMSGID)
+ *msgid = NULL;
+ else
+ *msgid = zfs_msgid_table[ret];
+
+ return (ret);
+}
diff --git a/lib/libzfs/libzfs_util.c b/lib/libzfs/libzfs_util.c
new file mode 100644
index 000000000000..c43c6bb1fe9b
--- /dev/null
+++ b/lib/libzfs/libzfs_util.c
@@ -0,0 +1,2092 @@
+/*
+ * 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) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright 2020 Joyent, Inc. All rights reserved.
+ * Copyright (c) 2011, 2020 by Delphix. All rights reserved.
+ * Copyright 2016 Igor Kozhukhov <ikozhukhov@gmail.com>
+ * Copyright (c) 2017 Datto Inc.
+ * Copyright (c) 2020 The FreeBSD Foundation
+ *
+ * Portions of this software were developed by Allan Jude
+ * under sponsorship from the FreeBSD Foundation.
+ */
+
+/*
+ * Internal utility routines for the ZFS library.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <libintl.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <unistd.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>
+
+/*
+ * We only care about the scheme in order to match the scheme
+ * with the handler. Each handler should validate the full URI
+ * as necessary.
+ */
+#define URI_REGEX "^\\([A-Za-z][A-Za-z0-9+.\\-]*\\):"
+
+int
+libzfs_errno(libzfs_handle_t *hdl)
+{
+ return (hdl->libzfs_error);
+}
+
+const char *
+libzfs_error_action(libzfs_handle_t *hdl)
+{
+ return (hdl->libzfs_action);
+}
+
+const char *
+libzfs_error_description(libzfs_handle_t *hdl)
+{
+ if (hdl->libzfs_desc[0] != '\0')
+ return (hdl->libzfs_desc);
+
+ switch (hdl->libzfs_error) {
+ case EZFS_NOMEM:
+ return (dgettext(TEXT_DOMAIN, "out of memory"));
+ case EZFS_BADPROP:
+ return (dgettext(TEXT_DOMAIN, "invalid property value"));
+ case EZFS_PROPREADONLY:
+ return (dgettext(TEXT_DOMAIN, "read-only property"));
+ case EZFS_PROPTYPE:
+ return (dgettext(TEXT_DOMAIN, "property doesn't apply to "
+ "datasets of this type"));
+ case EZFS_PROPNONINHERIT:
+ return (dgettext(TEXT_DOMAIN, "property cannot be inherited"));
+ case EZFS_PROPSPACE:
+ return (dgettext(TEXT_DOMAIN, "invalid quota or reservation"));
+ case EZFS_BADTYPE:
+ return (dgettext(TEXT_DOMAIN, "operation not applicable to "
+ "datasets of this type"));
+ case EZFS_BUSY:
+ return (dgettext(TEXT_DOMAIN, "pool or dataset is busy"));
+ case EZFS_EXISTS:
+ return (dgettext(TEXT_DOMAIN, "pool or dataset exists"));
+ case EZFS_NOENT:
+ return (dgettext(TEXT_DOMAIN, "no such pool or dataset"));
+ case EZFS_BADSTREAM:
+ return (dgettext(TEXT_DOMAIN, "invalid backup stream"));
+ case EZFS_DSREADONLY:
+ return (dgettext(TEXT_DOMAIN, "dataset is read-only"));
+ case EZFS_VOLTOOBIG:
+ return (dgettext(TEXT_DOMAIN, "volume size exceeds limit for "
+ "this system"));
+ case EZFS_INVALIDNAME:
+ return (dgettext(TEXT_DOMAIN, "invalid name"));
+ case EZFS_BADRESTORE:
+ return (dgettext(TEXT_DOMAIN, "unable to restore to "
+ "destination"));
+ case EZFS_BADBACKUP:
+ return (dgettext(TEXT_DOMAIN, "backup failed"));
+ case EZFS_BADTARGET:
+ return (dgettext(TEXT_DOMAIN, "invalid target vdev"));
+ case EZFS_NODEVICE:
+ return (dgettext(TEXT_DOMAIN, "no such device in pool"));
+ case EZFS_BADDEV:
+ return (dgettext(TEXT_DOMAIN, "invalid device"));
+ case EZFS_NOREPLICAS:
+ return (dgettext(TEXT_DOMAIN, "no valid replicas"));
+ case EZFS_RESILVERING:
+ return (dgettext(TEXT_DOMAIN, "currently resilvering"));
+ case EZFS_BADVERSION:
+ return (dgettext(TEXT_DOMAIN, "unsupported version or "
+ "feature"));
+ case EZFS_POOLUNAVAIL:
+ return (dgettext(TEXT_DOMAIN, "pool is unavailable"));
+ case EZFS_DEVOVERFLOW:
+ return (dgettext(TEXT_DOMAIN, "too many devices in one vdev"));
+ case EZFS_BADPATH:
+ return (dgettext(TEXT_DOMAIN, "must be an absolute path"));
+ case EZFS_CROSSTARGET:
+ return (dgettext(TEXT_DOMAIN, "operation crosses datasets or "
+ "pools"));
+ case EZFS_ZONED:
+ return (dgettext(TEXT_DOMAIN, "dataset in use by local zone"));
+ case EZFS_MOUNTFAILED:
+ return (dgettext(TEXT_DOMAIN, "mount failed"));
+ case EZFS_UMOUNTFAILED:
+ return (dgettext(TEXT_DOMAIN, "umount failed"));
+ case EZFS_UNSHARENFSFAILED:
+ return (dgettext(TEXT_DOMAIN, "unshare(1M) failed"));
+ case EZFS_SHARENFSFAILED:
+ return (dgettext(TEXT_DOMAIN, "share(1M) failed"));
+ case EZFS_UNSHARESMBFAILED:
+ return (dgettext(TEXT_DOMAIN, "smb remove share failed"));
+ case EZFS_SHARESMBFAILED:
+ return (dgettext(TEXT_DOMAIN, "smb add share failed"));
+ 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:
+ return (dgettext(TEXT_DOMAIN, "signal received"));
+ case EZFS_ISSPARE:
+ return (dgettext(TEXT_DOMAIN, "device is reserved as a hot "
+ "spare"));
+ case EZFS_INVALCONFIG:
+ return (dgettext(TEXT_DOMAIN, "invalid vdev configuration"));
+ case EZFS_RECURSIVE:
+ return (dgettext(TEXT_DOMAIN, "recursive dataset dependency"));
+ case EZFS_NOHISTORY:
+ return (dgettext(TEXT_DOMAIN, "no history available"));
+ case EZFS_POOLPROPS:
+ return (dgettext(TEXT_DOMAIN, "failed to retrieve "
+ "pool properties"));
+ case EZFS_POOL_NOTSUP:
+ return (dgettext(TEXT_DOMAIN, "operation not supported "
+ "on this type of pool"));
+ case EZFS_POOL_INVALARG:
+ return (dgettext(TEXT_DOMAIN, "invalid argument for "
+ "this pool operation"));
+ case EZFS_NAMETOOLONG:
+ return (dgettext(TEXT_DOMAIN, "dataset name is too long"));
+ case EZFS_OPENFAILED:
+ return (dgettext(TEXT_DOMAIN, "open failed"));
+ case EZFS_NOCAP:
+ return (dgettext(TEXT_DOMAIN,
+ "disk capacity information could not be retrieved"));
+ case EZFS_LABELFAILED:
+ return (dgettext(TEXT_DOMAIN, "write of label failed"));
+ case EZFS_BADWHO:
+ return (dgettext(TEXT_DOMAIN, "invalid user/group"));
+ case EZFS_BADPERM:
+ return (dgettext(TEXT_DOMAIN, "invalid permission"));
+ case EZFS_BADPERMSET:
+ return (dgettext(TEXT_DOMAIN, "invalid permission set name"));
+ case EZFS_NODELEGATION:
+ return (dgettext(TEXT_DOMAIN, "delegated administration is "
+ "disabled on pool"));
+ case EZFS_BADCACHE:
+ return (dgettext(TEXT_DOMAIN, "invalid or missing cache file"));
+ case EZFS_ISL2CACHE:
+ return (dgettext(TEXT_DOMAIN, "device is in use as a cache"));
+ case EZFS_VDEVNOTSUP:
+ return (dgettext(TEXT_DOMAIN, "vdev specification is not "
+ "supported"));
+ 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"));
+ case EZFS_UNPLAYED_LOGS:
+ return (dgettext(TEXT_DOMAIN, "log device has unplayed intent "
+ "logs"));
+ case EZFS_REFTAG_RELE:
+ return (dgettext(TEXT_DOMAIN, "no such tag on this dataset"));
+ 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_REBUILDING:
+ return (dgettext(TEXT_DOMAIN, "currently sequentially "
+ "resilvering"));
+ case EZFS_UNKNOWN:
+ return (dgettext(TEXT_DOMAIN, "unknown error"));
+ default:
+ assert(hdl->libzfs_error == 0);
+ return (dgettext(TEXT_DOMAIN, "no error"));
+ }
+}
+
+/*PRINTFLIKE2*/
+void
+zfs_error_aux(libzfs_handle_t *hdl, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+
+ (void) vsnprintf(hdl->libzfs_desc, sizeof (hdl->libzfs_desc),
+ fmt, ap);
+ hdl->libzfs_desc_active = 1;
+
+ va_end(ap);
+}
+
+static void
+zfs_verror(libzfs_handle_t *hdl, int error, const char *fmt, va_list ap)
+{
+ (void) vsnprintf(hdl->libzfs_action, sizeof (hdl->libzfs_action),
+ fmt, ap);
+ hdl->libzfs_error = error;
+
+ if (hdl->libzfs_desc_active)
+ hdl->libzfs_desc_active = 0;
+ else
+ hdl->libzfs_desc[0] = '\0';
+
+ if (hdl->libzfs_printerr) {
+ if (error == EZFS_UNKNOWN) {
+ (void) fprintf(stderr, dgettext(TEXT_DOMAIN, "internal "
+ "error: %s: %s\n"), hdl->libzfs_action,
+ libzfs_error_description(hdl));
+ abort();
+ }
+
+ (void) fprintf(stderr, "%s: %s\n", hdl->libzfs_action,
+ libzfs_error_description(hdl));
+ if (error == EZFS_NOMEM)
+ exit(1);
+ }
+}
+
+int
+zfs_error(libzfs_handle_t *hdl, int error, const char *msg)
+{
+ return (zfs_error_fmt(hdl, error, "%s", msg));
+}
+
+/*PRINTFLIKE3*/
+int
+zfs_error_fmt(libzfs_handle_t *hdl, int error, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+
+ zfs_verror(hdl, error, fmt, ap);
+
+ va_end(ap);
+
+ return (-1);
+}
+
+static int
+zfs_common_error(libzfs_handle_t *hdl, int error, const char *fmt,
+ va_list ap)
+{
+ switch (error) {
+ case EPERM:
+ case EACCES:
+ zfs_verror(hdl, EZFS_PERM, fmt, ap);
+ return (-1);
+
+ case ECANCELED:
+ zfs_verror(hdl, EZFS_NODELEGATION, fmt, ap);
+ return (-1);
+
+ case EIO:
+ 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);
+ }
+
+ return (0);
+}
+
+int
+zfs_standard_error(libzfs_handle_t *hdl, int error, const char *msg)
+{
+ return (zfs_standard_error_fmt(hdl, error, "%s", msg));
+}
+
+/*PRINTFLIKE3*/
+int
+zfs_standard_error_fmt(libzfs_handle_t *hdl, int error, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+
+ if (zfs_common_error(hdl, error, fmt, ap) != 0) {
+ va_end(ap);
+ return (-1);
+ }
+
+ switch (error) {
+ case ENXIO:
+ case ENODEV:
+ case EPIPE:
+ zfs_verror(hdl, EZFS_IO, fmt, ap);
+ break;
+
+ case ENOENT:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "dataset does not exist"));
+ zfs_verror(hdl, EZFS_NOENT, fmt, ap);
+ break;
+
+ case ENOSPC:
+ case EDQUOT:
+ zfs_verror(hdl, EZFS_NOSPC, fmt, ap);
+ break;
+
+ case EEXIST:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "dataset already exists"));
+ zfs_verror(hdl, EZFS_EXISTS, fmt, ap);
+ break;
+
+ case EBUSY:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "dataset is busy"));
+ zfs_verror(hdl, EZFS_BUSY, fmt, ap);
+ break;
+ case EROFS:
+ zfs_verror(hdl, EZFS_POOLREADONLY, fmt, ap);
+ break;
+ case ENAMETOOLONG:
+ zfs_verror(hdl, EZFS_NAMETOOLONG, fmt, ap);
+ break;
+ case ENOTSUP:
+ zfs_verror(hdl, EZFS_BADVERSION, fmt, ap);
+ break;
+ case EAGAIN:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "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;
+ case ZFS_ERR_BADPROP:
+ zfs_verror(hdl, EZFS_BADPROP, fmt, ap);
+ break;
+ default:
+ zfs_error_aux(hdl, strerror(error));
+ zfs_verror(hdl, EZFS_UNKNOWN, fmt, ap);
+ break;
+ }
+
+ va_end(ap);
+ return (-1);
+}
+
+void
+zfs_setprop_error(libzfs_handle_t *hdl, zfs_prop_t prop, int err,
+ char *errbuf)
+{
+ switch (err) {
+
+ case ENOSPC:
+ /*
+ * For quotas and reservations, ENOSPC indicates
+ * something different; setting a quota or reservation
+ * doesn't use any disk space.
+ */
+ switch (prop) {
+ case ZFS_PROP_QUOTA:
+ case ZFS_PROP_REFQUOTA:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "size is less than current used or "
+ "reserved space"));
+ (void) zfs_error(hdl, EZFS_PROPSPACE, errbuf);
+ break;
+
+ case ZFS_PROP_RESERVATION:
+ case ZFS_PROP_REFRESERVATION:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "size is greater than available space"));
+ (void) zfs_error(hdl, EZFS_PROPSPACE, errbuf);
+ break;
+
+ default:
+ (void) zfs_standard_error(hdl, err, errbuf);
+ break;
+ }
+ break;
+
+ case EBUSY:
+ (void) zfs_standard_error(hdl, EBUSY, errbuf);
+ break;
+
+ case EROFS:
+ (void) zfs_error(hdl, EZFS_DSREADONLY, errbuf);
+ break;
+
+ case E2BIG:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "property value too long"));
+ (void) zfs_error(hdl, EZFS_BADPROP, errbuf);
+ break;
+
+ case ENOTSUP:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "pool and or dataset must be upgraded to set this "
+ "property or value"));
+ (void) zfs_error(hdl, EZFS_BADVERSION, errbuf);
+ break;
+
+ case ERANGE:
+ if (prop == ZFS_PROP_COMPRESSION ||
+ prop == ZFS_PROP_DNODESIZE ||
+ prop == ZFS_PROP_RECORDSIZE) {
+ (void) zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "property setting is not allowed on "
+ "bootable datasets"));
+ (void) zfs_error(hdl, EZFS_NOTSUP, errbuf);
+ } else if (prop == ZFS_PROP_CHECKSUM ||
+ prop == ZFS_PROP_DEDUP) {
+ (void) zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "property setting is not allowed on "
+ "root pools"));
+ (void) zfs_error(hdl, EZFS_NOTSUP, errbuf);
+ } else {
+ (void) zfs_standard_error(hdl, err, errbuf);
+ }
+ break;
+
+ case EINVAL:
+ if (prop == ZPROP_INVAL) {
+ (void) zfs_error(hdl, EZFS_BADPROP, errbuf);
+ } else {
+ (void) zfs_standard_error(hdl, err, errbuf);
+ }
+ break;
+
+ case ZFS_ERR_BADPROP:
+ (void) zfs_error(hdl, EZFS_BADPROP, errbuf);
+ break;
+
+ case EACCES:
+ if (prop == ZFS_PROP_KEYLOCATION) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "keylocation may only be set on encryption roots"));
+ (void) zfs_error(hdl, EZFS_BADPROP, errbuf);
+ } else {
+ (void) zfs_standard_error(hdl, err, errbuf);
+ }
+ break;
+
+ case EOVERFLOW:
+ /*
+ * This platform can't address a volume this big.
+ */
+#ifdef _ILP32
+ if (prop == ZFS_PROP_VOLSIZE) {
+ (void) zfs_error(hdl, EZFS_VOLTOOBIG, errbuf);
+ break;
+ }
+#endif
+ /* FALLTHROUGH */
+ default:
+ (void) zfs_standard_error(hdl, err, errbuf);
+ }
+}
+
+int
+zpool_standard_error(libzfs_handle_t *hdl, int error, const char *msg)
+{
+ return (zpool_standard_error_fmt(hdl, error, "%s", msg));
+}
+
+/*PRINTFLIKE3*/
+int
+zpool_standard_error_fmt(libzfs_handle_t *hdl, int error, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+
+ if (zfs_common_error(hdl, error, fmt, ap) != 0) {
+ va_end(ap);
+ return (-1);
+ }
+
+ switch (error) {
+ case ENODEV:
+ zfs_verror(hdl, EZFS_NODEVICE, fmt, ap);
+ break;
+
+ case ENOENT:
+ zfs_error_aux(hdl,
+ dgettext(TEXT_DOMAIN, "no such pool or dataset"));
+ zfs_verror(hdl, EZFS_NOENT, fmt, ap);
+ break;
+
+ case EEXIST:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "pool already exists"));
+ zfs_verror(hdl, EZFS_EXISTS, fmt, ap);
+ break;
+
+ case EBUSY:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "pool is busy"));
+ 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"));
+ zfs_verror(hdl, EZFS_BADDEV, fmt, ap);
+ break;
+
+ case ENAMETOOLONG:
+ zfs_verror(hdl, EZFS_DEVOVERFLOW, fmt, ap);
+ break;
+
+ case ENOTSUP:
+ zfs_verror(hdl, EZFS_POOL_NOTSUP, fmt, ap);
+ break;
+
+ case EINVAL:
+ zfs_verror(hdl, EZFS_POOL_INVALARG, fmt, ap);
+ break;
+
+ case ENOSPC:
+ 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_RESILVER_IN_PROGRESS:
+ zfs_verror(hdl, EZFS_RESILVERING, fmt, ap);
+ break;
+ case ZFS_ERR_REBUILD_IN_PROGRESS:
+ zfs_verror(hdl, EZFS_REBUILDING, fmt, ap);
+ break;
+ case ZFS_ERR_BADPROP:
+ zfs_verror(hdl, EZFS_BADPROP, 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);
+ }
+
+ va_end(ap);
+ return (-1);
+}
+
+/*
+ * Display an out of memory error message and abort the current program.
+ */
+int
+no_memory(libzfs_handle_t *hdl)
+{
+ return (zfs_error(hdl, EZFS_NOMEM, "internal error"));
+}
+
+/*
+ * A safe form of malloc() which will die if the allocation fails.
+ */
+void *
+zfs_alloc(libzfs_handle_t *hdl, size_t size)
+{
+ void *data;
+
+ if ((data = calloc(1, size)) == NULL)
+ (void) no_memory(hdl);
+
+ 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.
+ */
+void *
+zfs_realloc(libzfs_handle_t *hdl, void *ptr, size_t oldsize, size_t newsize)
+{
+ void *ret;
+
+ if ((ret = realloc(ptr, newsize)) == NULL) {
+ (void) no_memory(hdl);
+ return (NULL);
+ }
+
+ bzero((char *)ret + oldsize, (newsize - oldsize));
+ return (ret);
+}
+
+/*
+ * A safe form of strdup() which will die if the allocation fails.
+ */
+char *
+zfs_strdup(libzfs_handle_t *hdl, const char *str)
+{
+ char *ret;
+
+ if ((ret = strdup(str)) == NULL)
+ (void) no_memory(hdl);
+
+ return (ret);
+}
+
+void
+libzfs_print_on_error(libzfs_handle_t *hdl, boolean_t printerr)
+{
+ hdl->libzfs_printerr = printerr;
+}
+
+/*
+ * 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 (-EPIPE);
+
+ 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_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);
+}
+
+libzfs_handle_t *
+libzfs_init(void)
+{
+ libzfs_handle_t *hdl;
+ int error;
+
+ error = libzfs_load_module();
+ if (error) {
+ errno = error;
+ return (NULL);
+ }
+
+ if ((hdl = calloc(1, sizeof (libzfs_handle_t))) == NULL) {
+ return (NULL);
+ }
+
+ if (regcomp(&hdl->libzfs_urire, URI_REGEX, 0) != 0) {
+ free(hdl);
+ return (NULL);
+ }
+
+ if ((hdl->libzfs_fd = open(ZFS_DEV, O_RDWR|O_EXCL)) < 0) {
+ free(hdl);
+ 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);
+ }
+
+ if (libzfs_core_init() != 0) {
+ (void) close(hdl->libzfs_fd);
+ (void) fclose(hdl->libzfs_mnttab);
+ 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);
+}
+
+void
+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
+ zpool_free_handles(hdl);
+ namespace_clear(hdl);
+ libzfs_mnttab_fini(hdl);
+ libzfs_core_fini();
+ regfree(&hdl->libzfs_urire);
+ fletcher_4_fini();
+ free(hdl);
+}
+
+libzfs_handle_t *
+zpool_get_handle(zpool_handle_t *zhp)
+{
+ return (zhp->zpool_hdl);
+}
+
+libzfs_handle_t *
+zfs_get_handle(zfs_handle_t *zhp)
+{
+ return (zhp->zfs_hdl);
+}
+
+zpool_handle_t *
+zfs_get_pool_handle(const zfs_handle_t *zhp)
+{
+ return (zhp->zpool_hdl);
+}
+
+/*
+ * 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/bkmark name.
+ */
+zfs_handle_t *
+zfs_path_to_zhandle(libzfs_handle_t *hdl, const char *path, zfs_type_t argtype)
+{
+ struct stat64 statbuf;
+ struct extmnttab entry;
+
+ if (path[0] != '/' && strncmp(path, "./", strlen("./")) != 0) {
+ /*
+ * It's not a valid path, assume it's a name of type 'argtype'.
+ */
+ return (zfs_open(hdl, path, argtype));
+ }
+
+ /* Reopen MNTTAB to prevent reading stale data from open file */
+ if (freopen(MNTTAB, "r", hdl->libzfs_mnttab) == NULL)
+ return (NULL);
+
+ if (getextmntent(path, &entry, &statbuf) != 0)
+ return (NULL);
+
+ if (strcmp(entry.mnt_fstype, MNTTYPE_ZFS) != 0) {
+ (void) fprintf(stderr, gettext("'%s': not a ZFS filesystem\n"),
+ path);
+ return (NULL);
+ }
+
+ return (zfs_open(hdl, entry.mnt_special, ZFS_TYPE_FILESYSTEM));
+}
+
+/*
+ * Initialize the zc_nvlist_dst member to prepare for receiving an nvlist from
+ * an ioctl().
+ */
+int
+zcmd_alloc_dst_nvlist(libzfs_handle_t *hdl, zfs_cmd_t *zc, size_t len)
+{
+ if (len == 0)
+ len = 256 * 1024;
+ zc->zc_nvlist_dst_size = len;
+ 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);
+}
+
+/*
+ * Called when an ioctl() which returns an nvlist fails with ENOMEM. This will
+ * expand the nvlist to the size specified in 'zc_nvlist_dst_size', which was
+ * filled in by the kernel to indicate the actual required size.
+ */
+int
+zcmd_expand_dst_nvlist(libzfs_handle_t *hdl, zfs_cmd_t *zc)
+{
+ free((void *)(uintptr_t)zc->zc_nvlist_dst);
+ 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);
+}
+
+/*
+ * Called to free the src and dst nvlists stored in the command structure.
+ */
+void
+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
+zcmd_write_nvlist_com(libzfs_handle_t *hdl, uint64_t *outnv, uint64_t *outlen,
+ nvlist_t *nvl)
+{
+ char *packed;
+ size_t len;
+
+ verify(nvlist_size(nvl, &len, NV_ENCODE_NATIVE) == 0);
+
+ if ((packed = zfs_alloc(hdl, len)) == NULL)
+ return (-1);
+
+ verify(nvlist_pack(nvl, &packed, &len, NV_ENCODE_NATIVE, 0) == 0);
+
+ *outnv = (uint64_t)(uintptr_t)packed;
+ *outlen = len;
+
+ return (0);
+}
+
+int
+zcmd_write_conf_nvlist(libzfs_handle_t *hdl, zfs_cmd_t *zc, nvlist_t *nvl)
+{
+ return (zcmd_write_nvlist_com(hdl, &zc->zc_nvlist_conf,
+ &zc->zc_nvlist_conf_size, nvl));
+}
+
+int
+zcmd_write_src_nvlist(libzfs_handle_t *hdl, zfs_cmd_t *zc, nvlist_t *nvl)
+{
+ return (zcmd_write_nvlist_com(hdl, &zc->zc_nvlist_src,
+ &zc->zc_nvlist_src_size, nvl));
+}
+
+/*
+ * Unpacks an nvlist from the ZFS ioctl command structure.
+ */
+int
+zcmd_read_dst_nvlist(libzfs_handle_t *hdl, zfs_cmd_t *zc, nvlist_t **nvlp)
+{
+ if (nvlist_unpack((void *)(uintptr_t)zc->zc_nvlist_dst,
+ zc->zc_nvlist_dst_size, nvlp, 0) != 0)
+ return (no_memory(hdl));
+
+ return (0);
+}
+
+/*
+ * ================================================================
+ * API shared by zfs and zpool property management
+ * ================================================================
+ */
+
+static void
+zprop_print_headers(zprop_get_cbdata_t *cbp, zfs_type_t type)
+{
+ zprop_list_t *pl = cbp->cb_proplist;
+ int i;
+ char *title;
+ size_t len;
+
+ cbp->cb_first = B_FALSE;
+ if (cbp->cb_scripted)
+ return;
+
+ /*
+ * Start with the length of the column headers.
+ */
+ cbp->cb_colwidths[GET_COL_NAME] = strlen(dgettext(TEXT_DOMAIN, "NAME"));
+ cbp->cb_colwidths[GET_COL_PROPERTY] = strlen(dgettext(TEXT_DOMAIN,
+ "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"));
+
+ /* first property is always NAME */
+ assert(cbp->cb_proplist->pl_prop ==
+ ((type == ZFS_TYPE_POOL) ? ZPOOL_PROP_NAME : ZFS_PROP_NAME));
+
+ /*
+ * Go through and calculate the widths for each column. For the
+ * 'source' column, we kludge it up by taking the worst-case scenario of
+ * 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 than any values we find.
+ */
+ for (pl = cbp->cb_proplist; pl != NULL; pl = pl->pl_next) {
+ /*
+ * 'PROPERTY' column
+ */
+ if (pl->pl_prop != ZPROP_INVAL) {
+ const char *propname = (type == ZFS_TYPE_POOL) ?
+ zpool_prop_to_name(pl->pl_prop) :
+ zfs_prop_to_name(pl->pl_prop);
+
+ len = strlen(propname);
+ if (len > cbp->cb_colwidths[GET_COL_PROPERTY])
+ cbp->cb_colwidths[GET_COL_PROPERTY] = len;
+ } else {
+ len = strlen(pl->pl_user_prop);
+ if (len > cbp->cb_colwidths[GET_COL_PROPERTY])
+ cbp->cb_colwidths[GET_COL_PROPERTY] = len;
+ }
+
+ /*
+ * 'VALUE' column. The first property is always the 'name'
+ * property that was tacked on either by /sbin/zfs's
+ * zfs_do_get() or when calling zprop_expand_list(), so we
+ * ignore its width. If the user specified the name property
+ * to display, then it will be later in the list in any case.
+ */
+ if (pl != cbp->cb_proplist &&
+ 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
+ */
+ if (pl->pl_prop == (type == ZFS_TYPE_POOL ? ZPOOL_PROP_NAME :
+ ZFS_PROP_NAME) &&
+ pl->pl_width > cbp->cb_colwidths[GET_COL_NAME]) {
+ cbp->cb_colwidths[GET_COL_NAME] = pl->pl_width;
+ cbp->cb_colwidths[GET_COL_SOURCE] = pl->pl_width +
+ strlen(dgettext(TEXT_DOMAIN, "inherited from"));
+ }
+ }
+
+ /*
+ * Now go through and print the headers.
+ */
+ for (i = 0; i < ZFS_GET_NCOLS; i++) {
+ switch (cbp->cb_columns[i]) {
+ case GET_COL_NAME:
+ title = dgettext(TEXT_DOMAIN, "NAME");
+ break;
+ case GET_COL_PROPERTY:
+ title = dgettext(TEXT_DOMAIN, "PROPERTY");
+ break;
+ 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;
+ default:
+ title = NULL;
+ }
+
+ if (title != NULL) {
+ if (i == (ZFS_GET_NCOLS - 1) ||
+ cbp->cb_columns[i + 1] == GET_COL_NONE)
+ (void) printf("%s", title);
+ else
+ (void) printf("%-*s ",
+ cbp->cb_colwidths[cbp->cb_columns[i]],
+ title);
+ }
+ }
+ (void) printf("\n");
+}
+
+/*
+ * Display a single line of output, according to the settings in the callback
+ * structure.
+ */
+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 *recvd_value)
+{
+ int i;
+ const char *str = NULL;
+ char buf[128];
+
+ /*
+ * Ignore those source types that the user has chosen to ignore.
+ */
+ if ((sourcetype & cbp->cb_sources) == 0)
+ return;
+
+ if (cbp->cb_first)
+ zprop_print_headers(cbp, cbp->cb_type);
+
+ for (i = 0; i < ZFS_GET_NCOLS; i++) {
+ switch (cbp->cb_columns[i]) {
+ case GET_COL_NAME:
+ str = name;
+ break;
+
+ case GET_COL_PROPERTY:
+ str = propname;
+ break;
+
+ case GET_COL_VALUE:
+ str = value;
+ break;
+
+ case GET_COL_SOURCE:
+ switch (sourcetype) {
+ case ZPROP_SRC_NONE:
+ str = "-";
+ break;
+
+ case ZPROP_SRC_DEFAULT:
+ str = "default";
+ break;
+
+ case ZPROP_SRC_LOCAL:
+ str = "local";
+ break;
+
+ case ZPROP_SRC_TEMPORARY:
+ str = "temporary";
+ break;
+
+ case ZPROP_SRC_INHERITED:
+ (void) snprintf(buf, sizeof (buf),
+ "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 (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);
+ else
+ (void) printf("%-*s ",
+ cbp->cb_colwidths[cbp->cb_columns[i]],
+ str);
+ }
+
+ (void) printf("\n");
+}
+
+/*
+ * Given a numeric suffix, convert the value into a number of bits that the
+ * resulting value must be shifted.
+ */
+static int
+str2shift(libzfs_handle_t *hdl, const char *buf)
+{
+ const char *ends = "BKMGTPEZ";
+ int i;
+
+ if (buf[0] == '\0')
+ return (0);
+ for (i = 0; i < strlen(ends); i++) {
+ if (toupper(buf[0]) == ends[i])
+ break;
+ }
+ if (i == strlen(ends)) {
+ if (hdl)
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "invalid numeric suffix '%s'"), buf);
+ return (-1);
+ }
+
+ /*
+ * Allow 'G' = 'GB' = 'GiB', case-insensitively.
+ * However, 'BB' and 'BiB' are disallowed.
+ */
+ 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);
+}
+
+/*
+ * Convert a string of the form '100G' into a real number. Used when setting
+ * properties or creating a volume. 'buf' is used to place an extended error
+ * message for the caller to use.
+ */
+int
+zfs_nicestrtonum(libzfs_handle_t *hdl, const char *value, uint64_t *num)
+{
+ char *end;
+ int shift;
+
+ *num = 0;
+
+ /* Check to see if this looks like a number. */
+ if ((value[0] < '0' || value[0] > '9') && value[0] != '.') {
+ if (hdl)
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "bad numeric value '%s'"), value);
+ return (-1);
+ }
+
+ /* Rely on strtoull() to process the numeric portion. */
+ errno = 0;
+ *num = strtoull(value, &end, 10);
+
+ /*
+ * Check for ERANGE, which indicates that the value is too large to fit
+ * in a 64-bit value.
+ */
+ if (errno == ERANGE) {
+ if (hdl)
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "numeric value is too large"));
+ return (-1);
+ }
+
+ /*
+ * If we have a decimal value, then do the computation with floating
+ * point arithmetic. Otherwise, use standard arithmetic.
+ */
+ if (*end == '.') {
+ double fval = strtod(value, &end);
+
+ if ((shift = str2shift(hdl, end)) == -1)
+ return (-1);
+
+ fval *= pow(2, shift);
+
+ /*
+ * UINT64_MAX is not exactly representable as a double.
+ * The closest representation is UINT64_MAX + 1, so we
+ * use a >= comparison instead of > for the bounds check.
+ */
+ if (fval >= (double)UINT64_MAX) {
+ if (hdl)
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "numeric value is too large"));
+ return (-1);
+ }
+
+ *num = (uint64_t)fval;
+ } else {
+ if ((shift = str2shift(hdl, end)) == -1)
+ return (-1);
+
+ /* Check for overflow */
+ if (shift >= 64 || (*num << shift) >> shift != *num) {
+ if (hdl)
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "numeric value is too large"));
+ return (-1);
+ }
+
+ *num <<= shift;
+ }
+
+ return (0);
+}
+
+/*
+ * Given a propname=value nvpair to set, parse any numeric properties
+ * (index, boolean, etc) if they are specified as strings and add the
+ * resulting nvpair to the returned nvlist.
+ *
+ * At the DSL layer, all properties are either 64-bit numbers or strings.
+ * We want the user to be able to ignore this fact and specify properties
+ * as native values (numbers, for example) or as strings (to simplify
+ * command line utilities). This also handles converting index types
+ * (compression, checksum, etc) from strings to their on-disk index.
+ */
+int
+zprop_parse_value(libzfs_handle_t *hdl, nvpair_t *elem, int prop,
+ zfs_type_t type, nvlist_t *ret, char **svalp, uint64_t *ivalp,
+ const char *errbuf)
+{
+ data_type_t datatype = nvpair_type(elem);
+ zprop_type_t proptype;
+ 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);
+ propname = zpool_prop_to_name(prop);
+ } else {
+ proptype = zfs_prop_get_type(prop);
+ propname = zfs_prop_to_name(prop);
+ }
+
+ /*
+ * Convert any properties to the internal DSL value types.
+ */
+ *svalp = NULL;
+ *ivalp = 0;
+
+ switch (proptype) {
+ case PROP_TYPE_STRING:
+ if (datatype != DATA_TYPE_STRING) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "'%s' must be a string"), nvpair_name(elem));
+ goto error;
+ }
+ 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));
+ goto error;
+ }
+ break;
+
+ case PROP_TYPE_NUMBER:
+ if (datatype == DATA_TYPE_STRING) {
+ (void) nvpair_value_string(elem, &value);
+ if (strcmp(value, "none") == 0) {
+ isnone = B_TRUE;
+ } 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) {
+ (void) nvpair_value_uint64(elem, ivalp);
+ } else {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "'%s' must be a number"), nvpair_name(elem));
+ goto error;
+ }
+
+ /*
+ * Quota special: force 'none' and don't allow 0.
+ */
+ if ((type & ZFS_TYPE_DATASET) && *ivalp == 0 && !isnone &&
+ (prop == ZFS_PROP_QUOTA || prop == ZFS_PROP_REFQUOTA)) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "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:
+ if (datatype != DATA_TYPE_STRING) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "'%s' must be a string"), nvpair_name(elem));
+ goto error;
+ }
+
+ (void) nvpair_value_string(elem, &value);
+
+ if (zprop_string_to_index(prop, value, ivalp, type) != 0) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "'%s' must be one of '%s'"), propname,
+ zprop_values(prop, type));
+ goto error;
+ }
+ break;
+
+ default:
+ abort();
+ }
+
+ /*
+ * Add the result to our return set of properties.
+ */
+ if (*svalp != NULL) {
+ if (nvlist_add_string(ret, propname, *svalp) != 0) {
+ (void) no_memory(hdl);
+ return (-1);
+ }
+ } else {
+ if (nvlist_add_uint64(ret, propname, *ivalp) != 0) {
+ (void) no_memory(hdl);
+ return (-1);
+ }
+ }
+
+ return (0);
+error:
+ (void) zfs_error(hdl, EZFS_BADPROP, errbuf);
+ return (-1);
+}
+
+static int
+addlist(libzfs_handle_t *hdl, char *propname, zprop_list_t **listp,
+ zfs_type_t type)
+{
+ int prop;
+ zprop_list_t *entry;
+
+ prop = zprop_name_to_prop(propname, type);
+
+ if (prop != ZPROP_INVAL && !zprop_valid_for_type(prop, type, B_FALSE))
+ prop = ZPROP_INVAL;
+
+ /*
+ * When no property table entry can be found, return failure if
+ * this is a pool property or if this isn't a user-defined
+ * dataset property,
+ */
+ 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,
+ dgettext(TEXT_DOMAIN, "bad property list")));
+ }
+
+ if ((entry = zfs_alloc(hdl, sizeof (zprop_list_t))) == NULL)
+ return (-1);
+
+ entry->pl_prop = prop;
+ if (prop == ZPROP_INVAL) {
+ if ((entry->pl_user_prop = zfs_strdup(hdl, propname)) ==
+ NULL) {
+ free(entry);
+ return (-1);
+ }
+ entry->pl_width = strlen(propname);
+ } else {
+ entry->pl_width = zprop_width(prop, &entry->pl_fixed,
+ type);
+ }
+
+ *listp = entry;
+
+ return (0);
+}
+
+/*
+ * Given a comma-separated list of properties, construct a property list
+ * containing both user-defined and native properties. This function will
+ * return a NULL list if 'all' is specified, which can later be expanded
+ * by zprop_expand_list().
+ */
+int
+zprop_get_list(libzfs_handle_t *hdl, char *props, zprop_list_t **listp,
+ zfs_type_t type)
+{
+ *listp = NULL;
+
+ /*
+ * If 'all' is specified, return a NULL list.
+ */
+ if (strcmp(props, "all") == 0)
+ return (0);
+
+ /*
+ * If no props were specified, return an error.
+ */
+ if (props[0] == '\0') {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "no properties specified"));
+ return (zfs_error(hdl, EZFS_BADPROP, dgettext(TEXT_DOMAIN,
+ "bad property list")));
+ }
+
+ /*
+ * It would be nice to use getsubopt() here, but the inclusion of column
+ * aliases makes this more effort than it's worth.
+ */
+ while (*props != '\0') {
+ size_t len;
+ char *p;
+ char c;
+
+ if ((p = strchr(props, ',')) == NULL) {
+ len = strlen(props);
+ p = props + len;
+ } else {
+ len = p - props;
+ }
+
+ /*
+ * Check for empty options.
+ */
+ if (len == 0) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "empty property name"));
+ return (zfs_error(hdl, EZFS_BADPROP,
+ dgettext(TEXT_DOMAIN, "bad property list")));
+ }
+
+ /*
+ * Check all regular property names.
+ */
+ c = props[len];
+ props[len] = '\0';
+
+ if (strcmp(props, "space") == 0) {
+ static char *spaceprops[] = {
+ "name", "avail", "used", "usedbysnapshots",
+ "usedbydataset", "usedbyrefreservation",
+ "usedbychildren", NULL
+ };
+ int i;
+
+ for (i = 0; spaceprops[i]; i++) {
+ if (addlist(hdl, spaceprops[i], listp, type))
+ return (-1);
+ listp = &(*listp)->pl_next;
+ }
+ } else {
+ if (addlist(hdl, props, listp, type))
+ return (-1);
+ listp = &(*listp)->pl_next;
+ }
+
+ props = p;
+ if (c == ',')
+ props++;
+ }
+
+ return (0);
+}
+
+void
+zprop_free_list(zprop_list_t *pl)
+{
+ zprop_list_t *next;
+
+ while (pl != NULL) {
+ next = pl->pl_next;
+ free(pl->pl_user_prop);
+ free(pl);
+ pl = next;
+ }
+}
+
+typedef struct expand_data {
+ zprop_list_t **last;
+ libzfs_handle_t *hdl;
+ zfs_type_t type;
+} expand_data_t;
+
+static int
+zprop_expand_list_cb(int prop, void *cb)
+{
+ zprop_list_t *entry;
+ expand_data_t *edp = cb;
+
+ if ((entry = zfs_alloc(edp->hdl, sizeof (zprop_list_t))) == NULL)
+ return (ZPROP_INVAL);
+
+ entry->pl_prop = prop;
+ entry->pl_width = zprop_width(prop, &entry->pl_fixed, edp->type);
+ entry->pl_all = B_TRUE;
+
+ *(edp->last) = entry;
+ edp->last = &entry->pl_next;
+
+ return (ZPROP_CONT);
+}
+
+int
+zprop_expand_list(libzfs_handle_t *hdl, zprop_list_t **plp, zfs_type_t type)
+{
+ zprop_list_t *entry;
+ zprop_list_t **last;
+ expand_data_t exp;
+
+ if (*plp == NULL) {
+ /*
+ * If this is the very first time we've been called for an 'all'
+ * specification, expand the list to include all native
+ * properties.
+ */
+ last = plp;
+
+ exp.last = last;
+ exp.hdl = hdl;
+ exp.type = type;
+
+ if (zprop_iter_common(zprop_expand_list_cb, &exp, B_FALSE,
+ B_FALSE, type) == ZPROP_INVAL)
+ return (-1);
+
+ /*
+ * Add 'name' to the beginning of the list, which is handled
+ * specially.
+ */
+ if ((entry = zfs_alloc(hdl, sizeof (zprop_list_t))) == NULL)
+ return (-1);
+
+ entry->pl_prop = (type == ZFS_TYPE_POOL) ? ZPOOL_PROP_NAME :
+ ZFS_PROP_NAME;
+ entry->pl_width = zprop_width(entry->pl_prop,
+ &entry->pl_fixed, type);
+ entry->pl_all = B_TRUE;
+ entry->pl_next = *plp;
+ *plp = entry;
+ }
+ return (0);
+}
+
+int
+zprop_iter(zprop_func func, void *cb, boolean_t show_all, boolean_t ordered,
+ zfs_type_t type)
+{
+ 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);
+}
+
+/*
+ * 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];
+
+ zfs_version_userland(zver_userland, sizeof (zver_userland));
+
+ (void) printf("%s\n", zver_userland);
+
+ if (zfs_version_kernel(zver_kernel, sizeof (zver_kernel)) == -1) {
+ fprintf(stderr, "zfs_version_kernel() failed: %s\n",
+ strerror(errno));
+ return (-1);
+ }
+
+ (void) printf("zfs-kmod-%s\n", zver_kernel);
+
+ return (0);
+}
+
+/*
+ * Return 1 if the user requested ANSI color output, and our terminal supports
+ * it. Return 0 for no color.
+ */
+static int
+use_color(void)
+{
+ static int use_color = -1;
+ char *term;
+
+ /*
+ * Optimization:
+ *
+ * For each zpool invocation, we do a single check to see if we should
+ * be using color or not, and cache that value for the lifetime of the
+ * the zpool command. That makes it cheap to call use_color() when
+ * we're printing with color. We assume that the settings are not going
+ * to change during the invocation of a zpool command (the user isn't
+ * going to change the ZFS_COLOR value while zpool is running, for
+ * example).
+ */
+ if (use_color != -1) {
+ /*
+ * We've already figured out if we should be using color or
+ * not. Return the cached value.
+ */
+ return (use_color);
+ }
+
+ term = getenv("TERM");
+ /*
+ * The user sets the ZFS_COLOR env var set to enable zpool ANSI color
+ * output. However if NO_COLOR is set (https://no-color.org/) then
+ * don't use it. Also, don't use color if terminal doesn't support
+ * it.
+ */
+ if (libzfs_envvar_is_set("ZFS_COLOR") &&
+ !libzfs_envvar_is_set("NO_COLOR") &&
+ isatty(STDOUT_FILENO) && term && strcmp("dumb", term) != 0 &&
+ strcmp("unknown", term) != 0) {
+ /* Color supported */
+ use_color = 1;
+ } else {
+ use_color = 0;
+ }
+
+ return (use_color);
+}
+
+/*
+ * color_start() and color_end() are used for when you want to colorize a block
+ * of text. For example:
+ *
+ * color_start(ANSI_RED_FG)
+ * printf("hello");
+ * printf("world");
+ * color_end();
+ */
+void
+color_start(char *color)
+{
+ if (use_color())
+ printf("%s", color);
+}
+
+void
+color_end(void)
+{
+ if (use_color())
+ printf(ANSI_RESET);
+}
+
+/* printf() with a color. If color is NULL, then do a normal printf. */
+int
+printf_color(char *color, char *format, ...)
+{
+ va_list aptr;
+ int rc;
+
+ if (color)
+ color_start(color);
+
+ va_start(aptr, format);
+ rc = vprintf(format, aptr);
+ va_end(aptr);
+
+ if (color)
+ color_end();
+
+ return (rc);
+}
diff --git a/lib/libzfs/os/freebsd/libzfs_compat.c b/lib/libzfs/os/freebsd/libzfs_compat.c
new file mode 100644
index 000000000000..037ba56efe1c
--- /dev/null
+++ b/lib/libzfs/os/freebsd/libzfs_compat.c
@@ -0,0 +1,321 @@
+/*
+ * 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) 2013 Martin Matuska <mm@FreeBSD.org>. All rights reserved.
+ */
+#include <os/freebsd/zfs/sys/zfs_ioctl_compat.h>
+#include <libzfs_impl.h>
+#include <libzfs.h>
+#include <libzutil.h>
+#include <sys/sysctl.h>
+#include <libintl.h>
+#include <sys/linker.h>
+#include <sys/module.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+
+#ifdef IN_BASE
+#define ZFS_KMOD "zfs"
+#else
+#define ZFS_KMOD "openzfs"
+#endif
+
+void
+libzfs_set_pipe_max(int infd)
+{
+ /* FreeBSD automatically resizes */
+}
+
+static int
+execvPe(const char *name, const char *path, char * const *argv,
+ char * const *envp)
+{
+ const char **memp;
+ size_t cnt, lp, ln;
+ int eacces, save_errno;
+ char *cur, buf[MAXPATHLEN];
+ const char *p, *bp;
+ struct stat sb;
+
+ eacces = 0;
+
+ /* If it's an absolute or relative path name, it's easy. */
+ if (strchr(name, '/')) {
+ bp = name;
+ cur = NULL;
+ goto retry;
+ }
+ bp = buf;
+
+ /* If it's an empty path name, fail in the usual POSIX way. */
+ if (*name == '\0') {
+ errno = ENOENT;
+ return (-1);
+ }
+
+ cur = alloca(strlen(path) + 1);
+ if (cur == NULL) {
+ errno = ENOMEM;
+ return (-1);
+ }
+ strcpy(cur, path);
+ while ((p = strsep(&cur, ":")) != NULL) {
+ /*
+ * It's a SHELL path -- double, leading and trailing colons
+ * mean the current directory.
+ */
+ if (*p == '\0') {
+ p = ".";
+ lp = 1;
+ } else
+ lp = strlen(p);
+ ln = strlen(name);
+
+ /*
+ * If the path is too long complain. This is a possible
+ * security issue; given a way to make the path too long
+ * the user may execute the wrong program.
+ */
+ if (lp + ln + 2 > sizeof (buf)) {
+ (void) write(STDERR_FILENO, "execvP: ", 8);
+ (void) write(STDERR_FILENO, p, lp);
+ (void) write(STDERR_FILENO, ": path too long\n",
+ 16);
+ continue;
+ }
+ bcopy(p, buf, lp);
+ buf[lp] = '/';
+ bcopy(name, buf + lp + 1, ln);
+ buf[lp + ln + 1] = '\0';
+
+retry: (void) execve(bp, argv, envp);
+ switch (errno) {
+ case E2BIG:
+ goto done;
+ case ELOOP:
+ case ENAMETOOLONG:
+ case ENOENT:
+ break;
+ case ENOEXEC:
+ for (cnt = 0; argv[cnt]; ++cnt)
+ ;
+ memp = alloca((cnt + 2) * sizeof (char *));
+ if (memp == NULL) {
+ /* errno = ENOMEM; XXX override ENOEXEC? */
+ goto done;
+ }
+ memp[0] = "sh";
+ memp[1] = bp;
+ bcopy(argv + 1, memp + 2, cnt * sizeof (char *));
+ execve(_PATH_BSHELL, __DECONST(char **, memp), envp);
+ goto done;
+ case ENOMEM:
+ goto done;
+ case ENOTDIR:
+ break;
+ case ETXTBSY:
+ /*
+ * We used to retry here, but sh(1) doesn't.
+ */
+ goto done;
+ default:
+ /*
+ * EACCES may be for an inaccessible directory or
+ * a non-executable file. Call stat() to decide
+ * which. This also handles ambiguities for EFAULT
+ * and EIO, and undocumented errors like ESTALE.
+ * We hope that the race for a stat() is unimportant.
+ */
+ save_errno = errno;
+ if (stat(bp, &sb) != 0)
+ break;
+ if (save_errno == EACCES) {
+ eacces = 1;
+ continue;
+ }
+ errno = save_errno;
+ goto done;
+ }
+ }
+ if (eacces)
+ errno = EACCES;
+ else
+ errno = ENOENT;
+done:
+ return (-1);
+}
+
+int
+execvpe(const char *name, char * const argv[], char * const envp[])
+{
+ const char *path;
+
+ /* Get the path we're searching. */
+ if ((path = getenv("PATH")) == NULL)
+ path = _PATH_DEFPATH;
+
+ return (execvPe(name, path, argv, envp));
+}
+
+const char *
+libzfs_error_init(int error)
+{
+
+ return (strerror(error));
+}
+
+int
+zfs_ioctl(libzfs_handle_t *hdl, int request, zfs_cmd_t *zc)
+{
+ return (zfs_ioctl_fd(hdl->libzfs_fd, request, zc));
+}
+
+/*
+ * 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
+ */
+int
+libzfs_load_module(void)
+{
+ /*
+ * XXX: kldfind(ZFS_KMOD) would be nice here, but we retain
+ * modfind("zfs") so out-of-base openzfs userland works with the
+ * in-base module.
+ */
+ if (modfind("zfs") < 0) {
+ /* Not present in kernel, try loading it. */
+ if (kldload(ZFS_KMOD) < 0 && errno != EEXIST) {
+ return (errno);
+ }
+ }
+ return (0);
+}
+
+int
+zpool_relabel_disk(libzfs_handle_t *hdl, const char *path, const char *msg)
+{
+ return (0);
+}
+
+int
+zpool_label_disk(libzfs_handle_t *hdl, zpool_handle_t *zhp, const char *name)
+{
+ return (0);
+}
+
+int
+find_shares_object(differ_info_t *di)
+{
+ return (0);
+}
+
+/*
+ * Attach/detach the given filesystem to/from the given jail.
+ */
+int
+zfs_jail(zfs_handle_t *zhp, int jailid, int attach)
+{
+ libzfs_handle_t *hdl = zhp->zfs_hdl;
+ zfs_cmd_t zc = {"\0"};
+ char errbuf[1024];
+ unsigned long cmd;
+ int ret;
+
+ if (attach) {
+ (void) snprintf(errbuf, sizeof (errbuf),
+ dgettext(TEXT_DOMAIN, "cannot jail '%s'"), zhp->zfs_name);
+ } else {
+ (void) snprintf(errbuf, sizeof (errbuf),
+ dgettext(TEXT_DOMAIN, "cannot unjail '%s'"), zhp->zfs_name);
+ }
+
+ switch (zhp->zfs_type) {
+ case ZFS_TYPE_VOLUME:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "volumes can not be jailed"));
+ return (zfs_error(hdl, EZFS_BADTYPE, errbuf));
+ case ZFS_TYPE_SNAPSHOT:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "snapshots can not be jailed"));
+ return (zfs_error(hdl, EZFS_BADTYPE, errbuf));
+ case ZFS_TYPE_BOOKMARK:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "bookmarks can not be jailed"));
+ return (zfs_error(hdl, EZFS_BADTYPE, errbuf));
+ case ZFS_TYPE_POOL:
+ case ZFS_TYPE_FILESYSTEM:
+ /* OK */
+ ;
+ }
+ assert(zhp->zfs_type == ZFS_TYPE_FILESYSTEM);
+
+ (void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name));
+ zc.zc_objset_type = DMU_OST_ZFS;
+ zc.zc_zoneid = jailid;
+
+ cmd = attach ? ZFS_IOC_JAIL : ZFS_IOC_UNJAIL;
+ if ((ret = zfs_ioctl(hdl, cmd, &zc)) != 0)
+ zfs_standard_error(hdl, errno, errbuf);
+
+ return (ret);
+}
+
+/*
+ * Set loader options for next boot.
+ */
+int
+zpool_nextboot(libzfs_handle_t *hdl, uint64_t pool_guid, uint64_t dev_guid,
+ const char *command)
+{
+ zfs_cmd_t zc = {"\0"};
+ nvlist_t *args;
+ int error;
+
+ args = fnvlist_alloc();
+ fnvlist_add_uint64(args, ZPOOL_CONFIG_POOL_GUID, pool_guid);
+ fnvlist_add_uint64(args, ZPOOL_CONFIG_GUID, dev_guid);
+ fnvlist_add_string(args, "command", command);
+ error = zcmd_write_src_nvlist(hdl, &zc, args);
+ if (error == 0)
+ error = zfs_ioctl(hdl, ZFS_IOC_NEXTBOOT, &zc);
+ zcmd_free_nvlists(&zc);
+ nvlist_free(args);
+ return (error);
+}
+
+/*
+ * Fill given version buffer with zfs kernel version.
+ * Returns 0 on success, and -1 on error (with errno set)
+ */
+int
+zfs_version_kernel(char *version, int len)
+{
+ size_t l = len;
+
+ return (sysctlbyname("vfs.zfs.version.module",
+ version, &l, NULL, 0));
+}
diff --git a/lib/libzfs/os/freebsd/libzfs_ioctl_compat.c b/lib/libzfs/os/freebsd/libzfs_ioctl_compat.c
new file mode 100644
index 000000000000..18b93fe27969
--- /dev/null
+++ b/lib/libzfs/os/freebsd/libzfs_ioctl_compat.c
@@ -0,0 +1,432 @@
+/*
+ * 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 2013 Xin Li <delphij@FreeBSD.org>. All rights reserved.
+ * Copyright 2013 Martin Matuska <mm@FreeBSD.org>. All rights reserved.
+ * Portions Copyright 2005, 2010, Oracle and/or its affiliates.
+ * All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/cred.h>
+#include <sys/dmu.h>
+#include <sys/zio.h>
+#include <sys/nvpair.h>
+#include <sys/dsl_deleg.h>
+#include <sys/zfs_ioctl.h>
+#include "zfs_namecheck.h"
+#include <os/freebsd/zfs/sys/zfs_ioctl_compat.h>
+
+/*
+ * FreeBSD zfs_cmd compatibility with older binaries
+ * appropriately remap/extend the zfs_cmd_t structure
+ */
+void
+zfs_cmd_compat_get(zfs_cmd_t *zc, caddr_t addr, const int cflag)
+{
+
+}
+#if 0
+static int
+zfs_ioctl_compat_get_nvlist(uint64_t nvl, size_t size, int iflag,
+ nvlist_t **nvp)
+{
+ char *packed;
+ int error;
+ nvlist_t *list = NULL;
+
+ /*
+ * Read in and unpack the user-supplied nvlist.
+ */
+ if (size == 0)
+ return (EINVAL);
+
+#ifdef _KERNEL
+ packed = kmem_alloc(size, KM_SLEEP);
+ if ((error = ddi_copyin((void *)(uintptr_t)nvl, packed, size,
+ iflag)) != 0) {
+ kmem_free(packed, size);
+ return (error);
+ }
+#else
+ packed = (void *)(uintptr_t)nvl;
+#endif
+
+ error = nvlist_unpack(packed, size, &list, 0);
+
+#ifdef _KERNEL
+ kmem_free(packed, size);
+#endif
+
+ if (error != 0)
+ return (error);
+
+ *nvp = list;
+ return (0);
+}
+
+static int
+zfs_ioctl_compat_put_nvlist(zfs_cmd_t *zc, nvlist_t *nvl)
+{
+ char *packed = NULL;
+ int error = 0;
+ size_t size;
+
+ VERIFY(nvlist_size(nvl, &size, NV_ENCODE_NATIVE) == 0);
+
+#ifdef _KERNEL
+ packed = kmem_alloc(size, KM_SLEEP);
+ VERIFY(nvlist_pack(nvl, &packed, &size, NV_ENCODE_NATIVE,
+ KM_SLEEP) == 0);
+
+ if (ddi_copyout(packed,
+ (void *)(uintptr_t)zc->zc_nvlist_dst, size, zc->zc_iflags) != 0)
+ error = EFAULT;
+ kmem_free(packed, size);
+#else
+ packed = (void *)(uintptr_t)zc->zc_nvlist_dst;
+ VERIFY(nvlist_pack(nvl, &packed, &size, NV_ENCODE_NATIVE,
+ 0) == 0);
+#endif
+
+ zc->zc_nvlist_dst_size = size;
+ return (error);
+}
+
+static void
+zfs_ioctl_compat_fix_stats_nvlist(nvlist_t *nvl)
+{
+ nvlist_t **child;
+ nvlist_t *nvroot = NULL;
+ vdev_stat_t *vs;
+ uint_t c, children, nelem;
+
+ if (nvlist_lookup_nvlist_array(nvl, ZPOOL_CONFIG_CHILDREN,
+ &child, &children) == 0) {
+ for (c = 0; c < children; c++) {
+ zfs_ioctl_compat_fix_stats_nvlist(child[c]);
+ }
+ }
+
+ if (nvlist_lookup_nvlist(nvl, ZPOOL_CONFIG_VDEV_TREE,
+ &nvroot) == 0)
+ zfs_ioctl_compat_fix_stats_nvlist(nvroot);
+ if ((nvlist_lookup_uint64_array(nvl, "stats",
+ (uint64_t **)&vs, &nelem) == 0)) {
+ nvlist_add_uint64_array(nvl,
+ ZPOOL_CONFIG_VDEV_STATS,
+ (uint64_t *)vs, nelem);
+ nvlist_remove(nvl, "stats",
+ DATA_TYPE_UINT64_ARRAY);
+ }
+}
+
+
+static int
+zfs_ioctl_compat_fix_stats(zfs_cmd_t *zc, const int nc)
+{
+ nvlist_t *nv, *nvp = NULL;
+ nvpair_t *elem;
+ int error;
+
+ if ((error = zfs_ioctl_compat_get_nvlist(zc->zc_nvlist_dst,
+ zc->zc_nvlist_dst_size, zc->zc_iflags, &nv)) != 0)
+ return (error);
+
+ if (nc == 5) { /* ZFS_IOC_POOL_STATS */
+ elem = NULL;
+ while ((elem = nvlist_next_nvpair(nv, elem)) != NULL) {
+ if (nvpair_value_nvlist(elem, &nvp) == 0)
+ zfs_ioctl_compat_fix_stats_nvlist(nvp);
+ }
+ elem = NULL;
+ } else
+ zfs_ioctl_compat_fix_stats_nvlist(nv);
+
+ error = zfs_ioctl_compat_put_nvlist(zc, nv);
+
+ nvlist_free(nv);
+
+ return (error);
+}
+
+static int
+zfs_ioctl_compat_pool_get_props(zfs_cmd_t *zc)
+{
+ nvlist_t *nv, *nva = NULL;
+ int error;
+
+ if ((error = zfs_ioctl_compat_get_nvlist(zc->zc_nvlist_dst,
+ zc->zc_nvlist_dst_size, zc->zc_iflags, &nv)) != 0)
+ return (error);
+
+ if (nvlist_lookup_nvlist(nv, "used", &nva) == 0) {
+ nvlist_add_nvlist(nv, "allocated", nva);
+ nvlist_remove(nv, "used", DATA_TYPE_NVLIST);
+ }
+
+ if (nvlist_lookup_nvlist(nv, "available", &nva) == 0) {
+ nvlist_add_nvlist(nv, "free", nva);
+ nvlist_remove(nv, "available", DATA_TYPE_NVLIST);
+ }
+
+ error = zfs_ioctl_compat_put_nvlist(zc, nv);
+
+ nvlist_free(nv);
+
+ return (error);
+}
+#endif
+
+#ifdef _KERNEL
+int
+zfs_ioctl_compat_pre(zfs_cmd_t *zc, int *vec, const int cflag)
+{
+ int error = 0;
+
+ /* are we creating a clone? */
+ if (*vec == ZFS_IOC_CREATE && zc->zc_value[0] != '\0')
+ *vec = ZFS_IOC_CLONE;
+
+ if (cflag == ZFS_CMD_COMPAT_V15) {
+ switch (*vec) {
+
+ case 7: /* ZFS_IOC_POOL_SCRUB (v15) */
+ zc->zc_cookie = POOL_SCAN_SCRUB;
+ break;
+ }
+ }
+
+ return (error);
+}
+
+void
+zfs_ioctl_compat_post(zfs_cmd_t *zc, int vec, const int cflag)
+{
+ if (cflag == ZFS_CMD_COMPAT_V15) {
+ switch (vec) {
+ case ZFS_IOC_POOL_CONFIGS:
+ case ZFS_IOC_POOL_STATS:
+ case ZFS_IOC_POOL_TRYIMPORT:
+ zfs_ioctl_compat_fix_stats(zc, vec);
+ break;
+ case 41: /* ZFS_IOC_POOL_GET_PROPS (v15) */
+ zfs_ioctl_compat_pool_get_props(zc);
+ break;
+ }
+ }
+}
+
+nvlist_t *
+zfs_ioctl_compat_innvl(zfs_cmd_t *zc, nvlist_t *innvl, const int vec,
+ const int cflag)
+{
+ nvlist_t *nvl, *tmpnvl, *hnvl;
+ nvpair_t *elem;
+ char *poolname, *snapname;
+ int err;
+
+ if (cflag == ZFS_CMD_COMPAT_NONE || cflag == ZFS_CMD_COMPAT_LZC ||
+ cflag == ZFS_CMD_COMPAT_ZCMD || cflag == ZFS_CMD_COMPAT_EDBP ||
+ cflag == ZFS_CMD_COMPAT_RESUME || cflag == ZFS_CMD_COMPAT_INLANES)
+ goto out;
+
+ switch (vec) {
+ case ZFS_IOC_CREATE:
+ nvl = fnvlist_alloc();
+ fnvlist_add_int32(nvl, "type", zc->zc_objset_type);
+ if (innvl != NULL) {
+ fnvlist_add_nvlist(nvl, "props", innvl);
+ nvlist_free(innvl);
+ }
+ return (nvl);
+ break;
+ case ZFS_IOC_CLONE:
+ nvl = fnvlist_alloc();
+ fnvlist_add_string(nvl, "origin", zc->zc_value);
+ if (innvl != NULL) {
+ fnvlist_add_nvlist(nvl, "props", innvl);
+ nvlist_free(innvl);
+ }
+ return (nvl);
+ break;
+ case ZFS_IOC_SNAPSHOT:
+ if (innvl == NULL)
+ goto out;
+ nvl = fnvlist_alloc();
+ fnvlist_add_nvlist(nvl, "props", innvl);
+ tmpnvl = fnvlist_alloc();
+ snapname = kmem_asprintf("%s@%s", zc->zc_name, zc->zc_value);
+ fnvlist_add_boolean(tmpnvl, snapname);
+ kmem_free(snapname, strlen(snapname + 1));
+ /* check if we are doing a recursive snapshot */
+ if (zc->zc_cookie)
+ dmu_get_recursive_snaps_nvl(zc->zc_name, zc->zc_value,
+ tmpnvl);
+ fnvlist_add_nvlist(nvl, "snaps", tmpnvl);
+ fnvlist_free(tmpnvl);
+ nvlist_free(innvl);
+ /* strip dataset part from zc->zc_name */
+ zc->zc_name[strcspn(zc->zc_name, "/@")] = '\0';
+ return (nvl);
+ break;
+ case ZFS_IOC_SPACE_SNAPS:
+ nvl = fnvlist_alloc();
+ fnvlist_add_string(nvl, "firstsnap", zc->zc_value);
+ if (innvl != NULL)
+ nvlist_free(innvl);
+ return (nvl);
+ break;
+ case ZFS_IOC_DESTROY_SNAPS:
+ if (innvl == NULL && cflag == ZFS_CMD_COMPAT_DEADMAN)
+ goto out;
+ nvl = fnvlist_alloc();
+ if (innvl != NULL) {
+ fnvlist_add_nvlist(nvl, "snaps", innvl);
+ } else {
+ /*
+ * We are probably called by even older binaries,
+ * allocate and populate nvlist with recursive
+ * snapshots
+ */
+ if (zfs_component_namecheck(zc->zc_value, NULL,
+ NULL) == 0) {
+ tmpnvl = fnvlist_alloc();
+ if (dmu_get_recursive_snaps_nvl(zc->zc_name,
+ zc->zc_value, tmpnvl) == 0)
+ fnvlist_add_nvlist(nvl, "snaps",
+ tmpnvl);
+ nvlist_free(tmpnvl);
+ }
+ }
+ if (innvl != NULL)
+ nvlist_free(innvl);
+ /* strip dataset part from zc->zc_name */
+ zc->zc_name[strcspn(zc->zc_name, "/@")] = '\0';
+ return (nvl);
+ break;
+ case ZFS_IOC_HOLD:
+ nvl = fnvlist_alloc();
+ tmpnvl = fnvlist_alloc();
+ if (zc->zc_cleanup_fd != -1)
+ fnvlist_add_int32(nvl, "cleanup_fd",
+ (int32_t)zc->zc_cleanup_fd);
+ if (zc->zc_cookie) {
+ hnvl = fnvlist_alloc();
+ if (dmu_get_recursive_snaps_nvl(zc->zc_name,
+ zc->zc_value, hnvl) == 0) {
+ elem = NULL;
+ while ((elem = nvlist_next_nvpair(hnvl,
+ elem)) != NULL) {
+ nvlist_add_string(tmpnvl,
+ nvpair_name(elem), zc->zc_string);
+ }
+ }
+ nvlist_free(hnvl);
+ } else {
+ snapname = kmem_asprintf("%s@%s", zc->zc_name,
+ zc->zc_value);
+ nvlist_add_string(tmpnvl, snapname, zc->zc_string);
+ kmem_free(snapname, strlen(snapname + 1));
+ }
+ fnvlist_add_nvlist(nvl, "holds", tmpnvl);
+ nvlist_free(tmpnvl);
+ if (innvl != NULL)
+ nvlist_free(innvl);
+ /* strip dataset part from zc->zc_name */
+ zc->zc_name[strcspn(zc->zc_name, "/@")] = '\0';
+ return (nvl);
+ break;
+ case ZFS_IOC_RELEASE:
+ nvl = fnvlist_alloc();
+ tmpnvl = fnvlist_alloc();
+ if (zc->zc_cookie) {
+ hnvl = fnvlist_alloc();
+ if (dmu_get_recursive_snaps_nvl(zc->zc_name,
+ zc->zc_value, hnvl) == 0) {
+ elem = NULL;
+ while ((elem = nvlist_next_nvpair(hnvl,
+ elem)) != NULL) {
+ fnvlist_add_boolean(tmpnvl,
+ zc->zc_string);
+ fnvlist_add_nvlist(nvl,
+ nvpair_name(elem), tmpnvl);
+ }
+ }
+ nvlist_free(hnvl);
+ } else {
+ snapname = kmem_asprintf("%s@%s", zc->zc_name,
+ zc->zc_value);
+ fnvlist_add_boolean(tmpnvl, zc->zc_string);
+ fnvlist_add_nvlist(nvl, snapname, tmpnvl);
+ kmem_free(snapname, strlen(snapname + 1));
+ }
+ nvlist_free(tmpnvl);
+ if (innvl != NULL)
+ nvlist_free(innvl);
+ /* strip dataset part from zc->zc_name */
+ zc->zc_name[strcspn(zc->zc_name, "/@")] = '\0';
+ return (nvl);
+ break;
+ }
+out:
+ return (innvl);
+}
+
+nvlist_t *
+zfs_ioctl_compat_outnvl(zfs_cmd_t *zc, nvlist_t *outnvl, const int vec,
+ const int cflag)
+{
+ nvlist_t *tmpnvl;
+
+ if (cflag == ZFS_CMD_COMPAT_NONE || cflag == ZFS_CMD_COMPAT_LZC ||
+ cflag == ZFS_CMD_COMPAT_ZCMD || cflag == ZFS_CMD_COMPAT_EDBP ||
+ cflag == ZFS_CMD_COMPAT_RESUME || cflag == ZFS_CMD_COMPAT_INLANES)
+ return (outnvl);
+
+ switch (vec) {
+ case ZFS_IOC_SPACE_SNAPS:
+ (void) nvlist_lookup_uint64(outnvl, "used", &zc->zc_cookie);
+ (void) nvlist_lookup_uint64(outnvl, "compressed",
+ &zc->zc_objset_type);
+ (void) nvlist_lookup_uint64(outnvl, "uncompressed",
+ &zc->zc_perm_action);
+ nvlist_free(outnvl);
+ /* return empty outnvl */
+ tmpnvl = fnvlist_alloc();
+ return (tmpnvl);
+ break;
+ case ZFS_IOC_CREATE:
+ case ZFS_IOC_CLONE:
+ case ZFS_IOC_HOLD:
+ case ZFS_IOC_RELEASE:
+ nvlist_free(outnvl);
+ /* return empty outnvl */
+ tmpnvl = fnvlist_alloc();
+ return (tmpnvl);
+ break;
+ }
+
+ return (outnvl);
+}
+#endif /* KERNEL */
diff --git a/lib/libzfs/os/freebsd/libzfs_zmount.c b/lib/libzfs/os/freebsd/libzfs_zmount.c
new file mode 100644
index 000000000000..2207fffc5573
--- /dev/null
+++ b/lib/libzfs/os/freebsd/libzfs_zmount.c
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) 2006 Pawel Jakub Dawidek <pjd@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * This file implements Solaris compatible zmount() function.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/mount.h>
+#include <sys/uio.h>
+#include <sys/mntent.h>
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mnttab.h>
+#include <sys/errno.h>
+#include <libzfs.h>
+
+#include "libzfs_impl.h"
+
+static void
+build_iovec(struct iovec **iov, int *iovlen, const char *name, void *val,
+ size_t len)
+{
+ int i;
+
+ if (*iovlen < 0)
+ return;
+ i = *iovlen;
+ *iov = realloc(*iov, sizeof (**iov) * (i + 2));
+ if (*iov == NULL) {
+ *iovlen = -1;
+ return;
+ }
+ (*iov)[i].iov_base = strdup(name);
+ (*iov)[i].iov_len = strlen(name) + 1;
+ i++;
+ (*iov)[i].iov_base = val;
+ if (len == (size_t)-1) {
+ if (val != NULL)
+ len = strlen(val) + 1;
+ else
+ len = 0;
+ }
+ (*iov)[i].iov_len = (int)len;
+ *iovlen = ++i;
+}
+
+static int
+do_mount_(const char *spec, const char *dir, int mflag, char *fstype,
+ char *dataptr, int datalen, char *optptr, int optlen)
+{
+ struct iovec *iov;
+ char *optstr, *p, *tofree;
+ int iovlen, rv;
+
+ assert(spec != NULL);
+ assert(dir != NULL);
+ assert(fstype != NULL);
+ assert(strcmp(fstype, MNTTYPE_ZFS) == 0);
+ assert(dataptr == NULL);
+ assert(datalen == 0);
+ assert(optptr != NULL);
+ assert(optlen > 0);
+
+ tofree = optstr = strdup(optptr);
+ assert(optstr != NULL);
+
+ iov = NULL;
+ iovlen = 0;
+ if (strstr(optstr, MNTOPT_REMOUNT) != NULL)
+ build_iovec(&iov, &iovlen, "update", NULL, 0);
+ if (strstr(optstr, MNTOPT_NOXATTR) == NULL &&
+ strstr(optstr, MNTOPT_XATTR) == NULL &&
+ strstr(optstr, MNTOPT_SAXATTR) == NULL &&
+ strstr(optstr, MNTOPT_DIRXATTR) == NULL)
+ build_iovec(&iov, &iovlen, "xattr", NULL, 0);
+ if (mflag & MS_RDONLY)
+ build_iovec(&iov, &iovlen, "ro", NULL, 0);
+ build_iovec(&iov, &iovlen, "fstype", fstype, (size_t)-1);
+ build_iovec(&iov, &iovlen, "fspath", __DECONST(char *, dir),
+ (size_t)-1);
+ build_iovec(&iov, &iovlen, "from", __DECONST(char *, spec), (size_t)-1);
+ while ((p = strsep(&optstr, ",/")) != NULL)
+ build_iovec(&iov, &iovlen, p, NULL, (size_t)-1);
+ rv = nmount(iov, iovlen, 0);
+ free(tofree);
+ if (rv < 0)
+ return (errno);
+ return (rv);
+}
+
+int
+do_mount(zfs_handle_t *zhp, const char *mntpt, char *opts, int flags)
+{
+
+ return (do_mount_(zfs_get_name(zhp), mntpt, flags, MNTTYPE_ZFS, NULL, 0,
+ opts, sizeof (mntpt)));
+}
+
+int
+do_unmount(const char *mntpt, int flags)
+{
+
+ return (unmount(mntpt, flags));
+}
+
+int
+zfs_mount_delegation_check(void)
+{
+ return (0);
+}
diff --git a/lib/libzfs/os/linux/libzfs_mount_os.c b/lib/libzfs/os/linux/libzfs_mount_os.c
new file mode 100644
index 000000000000..92052f28ffe7
--- /dev/null
+++ b/lib/libzfs/os/linux/libzfs_mount_os.c
@@ -0,0 +1,411 @@
+/*
+ * 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 2015 Nexenta Systems, Inc. All rights reserved.
+ * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2014, 2020 by Delphix. All rights reserved.
+ * Copyright 2016 Igor Kozhukhov <ikozhukhov@gmail.com>
+ * Copyright 2017 RackTop Systems.
+ * Copyright (c) 2018 Datto Inc.
+ * Copyright 2018 OmniOS Community Edition (OmniOSce) Association.
+ */
+
+#include <dirent.h>
+#include <dlfcn.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <libintl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <unistd.h>
+#include <zone.h>
+#include <sys/mntent.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/vfs.h>
+#include <sys/dsl_crypt.h>
+#include <libzfs.h>
+
+#include "libzfs_impl.h"
+#include <thread_pool.h>
+
+#define ZS_COMMENT 0x00000000 /* comment */
+#define ZS_ZFSUTIL 0x00000001 /* caller is zfs(8) */
+
+typedef struct option_map {
+ const char *name;
+ unsigned long mntmask;
+ unsigned long zfsmask;
+} option_map_t;
+
+static const option_map_t option_map[] = {
+ /* Canonicalized filesystem independent options from mount(8) */
+ { MNTOPT_NOAUTO, MS_COMMENT, ZS_COMMENT },
+ { MNTOPT_DEFAULTS, MS_COMMENT, ZS_COMMENT },
+ { MNTOPT_NODEVICES, MS_NODEV, ZS_COMMENT },
+ { MNTOPT_DEVICES, MS_COMMENT, ZS_COMMENT },
+ { MNTOPT_DIRSYNC, MS_DIRSYNC, ZS_COMMENT },
+ { MNTOPT_NOEXEC, MS_NOEXEC, ZS_COMMENT },
+ { MNTOPT_EXEC, MS_COMMENT, ZS_COMMENT },
+ { MNTOPT_GROUP, MS_GROUP, ZS_COMMENT },
+ { MNTOPT_NETDEV, MS_COMMENT, ZS_COMMENT },
+ { MNTOPT_NOFAIL, MS_COMMENT, ZS_COMMENT },
+ { MNTOPT_NOSUID, MS_NOSUID, ZS_COMMENT },
+ { MNTOPT_SUID, MS_COMMENT, ZS_COMMENT },
+ { MNTOPT_OWNER, MS_OWNER, ZS_COMMENT },
+ { MNTOPT_REMOUNT, MS_REMOUNT, ZS_COMMENT },
+ { MNTOPT_RO, MS_RDONLY, ZS_COMMENT },
+ { MNTOPT_RW, MS_COMMENT, ZS_COMMENT },
+ { MNTOPT_SYNC, MS_SYNCHRONOUS, ZS_COMMENT },
+ { MNTOPT_USER, MS_USERS, ZS_COMMENT },
+ { MNTOPT_USERS, MS_USERS, ZS_COMMENT },
+ /* acl flags passed with util-linux-2.24 mount command */
+ { MNTOPT_ACL, MS_POSIXACL, ZS_COMMENT },
+ { MNTOPT_NOACL, MS_COMMENT, ZS_COMMENT },
+ { MNTOPT_POSIXACL, MS_POSIXACL, ZS_COMMENT },
+#ifdef MS_NOATIME
+ { MNTOPT_NOATIME, MS_NOATIME, ZS_COMMENT },
+ { MNTOPT_ATIME, MS_COMMENT, ZS_COMMENT },
+#endif
+#ifdef MS_NODIRATIME
+ { MNTOPT_NODIRATIME, MS_NODIRATIME, ZS_COMMENT },
+ { MNTOPT_DIRATIME, MS_COMMENT, ZS_COMMENT },
+#endif
+#ifdef MS_RELATIME
+ { MNTOPT_RELATIME, MS_RELATIME, ZS_COMMENT },
+ { MNTOPT_NORELATIME, MS_COMMENT, ZS_COMMENT },
+#endif
+#ifdef MS_STRICTATIME
+ { MNTOPT_STRICTATIME, MS_STRICTATIME, ZS_COMMENT },
+ { MNTOPT_NOSTRICTATIME, MS_COMMENT, ZS_COMMENT },
+#endif
+#ifdef MS_LAZYTIME
+ { MNTOPT_LAZYTIME, MS_LAZYTIME, ZS_COMMENT },
+#endif
+ { MNTOPT_CONTEXT, MS_COMMENT, ZS_COMMENT },
+ { MNTOPT_FSCONTEXT, MS_COMMENT, ZS_COMMENT },
+ { MNTOPT_DEFCONTEXT, MS_COMMENT, ZS_COMMENT },
+ { MNTOPT_ROOTCONTEXT, MS_COMMENT, ZS_COMMENT },
+#ifdef MS_I_VERSION
+ { MNTOPT_IVERSION, MS_I_VERSION, ZS_COMMENT },
+#endif
+#ifdef MS_MANDLOCK
+ { MNTOPT_NBMAND, MS_MANDLOCK, ZS_COMMENT },
+ { MNTOPT_NONBMAND, MS_COMMENT, ZS_COMMENT },
+#endif
+ /* Valid options not found in mount(8) */
+ { MNTOPT_BIND, MS_BIND, ZS_COMMENT },
+#ifdef MS_REC
+ { MNTOPT_RBIND, MS_BIND|MS_REC, ZS_COMMENT },
+#endif
+ { MNTOPT_COMMENT, MS_COMMENT, ZS_COMMENT },
+#ifdef MS_NOSUB
+ { MNTOPT_NOSUB, MS_NOSUB, ZS_COMMENT },
+#endif
+#ifdef MS_SILENT
+ { MNTOPT_QUIET, MS_SILENT, ZS_COMMENT },
+#endif
+ /* Custom zfs options */
+ { MNTOPT_XATTR, MS_COMMENT, ZS_COMMENT },
+ { MNTOPT_NOXATTR, MS_COMMENT, ZS_COMMENT },
+ { MNTOPT_ZFSUTIL, MS_COMMENT, ZS_ZFSUTIL },
+ { NULL, 0, 0 } };
+
+/*
+ * Break the mount option in to a name/value pair. The name is
+ * validated against the option map and mount flags set accordingly.
+ */
+static int
+parse_option(char *mntopt, unsigned long *mntflags,
+ unsigned long *zfsflags, int sloppy)
+{
+ const option_map_t *opt;
+ char *ptr, *name, *value = NULL;
+ int error = 0;
+
+ name = strdup(mntopt);
+ if (name == NULL)
+ return (ENOMEM);
+
+ for (ptr = name; ptr && *ptr; ptr++) {
+ if (*ptr == '=') {
+ *ptr = '\0';
+ value = ptr+1;
+ VERIFY3P(value, !=, NULL);
+ break;
+ }
+ }
+
+ for (opt = option_map; opt->name != NULL; opt++) {
+ if (strncmp(name, opt->name, strlen(name)) == 0) {
+ *mntflags |= opt->mntmask;
+ *zfsflags |= opt->zfsmask;
+ error = 0;
+ goto out;
+ }
+ }
+
+ if (!sloppy)
+ error = ENOENT;
+out:
+ /* If required further process on the value may be done here */
+ free(name);
+ return (error);
+}
+
+/*
+ * Translate the mount option string in to MS_* mount flags for the
+ * kernel vfs. When sloppy is non-zero unknown options will be ignored
+ * otherwise they are considered fatal are copied in to badopt.
+ */
+int
+zfs_parse_mount_options(char *mntopts, unsigned long *mntflags,
+ unsigned long *zfsflags, int sloppy, char *badopt, char *mtabopt)
+{
+ int error = 0, quote = 0, flag = 0, count = 0;
+ char *ptr, *opt, *opts;
+
+ opts = strdup(mntopts);
+ if (opts == NULL)
+ return (ENOMEM);
+
+ *mntflags = 0;
+ opt = NULL;
+
+ /*
+ * Scan through all mount options which must be comma delimited.
+ * We must be careful to notice regions which are double quoted
+ * and skip commas in these regions. Each option is then checked
+ * to determine if it is a known option.
+ */
+ for (ptr = opts; ptr && !flag; ptr++) {
+ if (opt == NULL)
+ opt = ptr;
+
+ if (*ptr == '"')
+ quote = !quote;
+
+ if (quote)
+ continue;
+
+ if (*ptr == '\0')
+ flag = 1;
+
+ if ((*ptr == ',') || (*ptr == '\0')) {
+ *ptr = '\0';
+
+ error = parse_option(opt, mntflags, zfsflags, sloppy);
+ if (error) {
+ strcpy(badopt, opt);
+ goto out;
+
+ }
+
+ if (!(*mntflags & MS_REMOUNT) &&
+ !(*zfsflags & ZS_ZFSUTIL) &&
+ mtabopt != NULL) {
+ if (count > 0)
+ strlcat(mtabopt, ",", MNT_LINE_MAX);
+
+ strlcat(mtabopt, opt, MNT_LINE_MAX);
+ count++;
+ }
+
+ opt = NULL;
+ }
+ }
+
+out:
+ free(opts);
+ return (error);
+}
+
+static void
+append_mntopt(const char *name, const char *val, char *mntopts,
+ char *mtabopt, boolean_t quote)
+{
+ char tmp[MNT_LINE_MAX];
+
+ snprintf(tmp, MNT_LINE_MAX, quote ? ",%s=\"%s\"" : ",%s=%s", name, val);
+
+ if (mntopts)
+ strlcat(mntopts, tmp, MNT_LINE_MAX);
+
+ if (mtabopt)
+ strlcat(mtabopt, tmp, MNT_LINE_MAX);
+}
+
+static void
+zfs_selinux_setcontext(zfs_handle_t *zhp, zfs_prop_t zpt, const char *name,
+ char *mntopts, char *mtabopt)
+{
+ char context[ZFS_MAXPROPLEN];
+
+ if (zfs_prop_get(zhp, zpt, context, sizeof (context),
+ NULL, NULL, 0, B_FALSE) == 0) {
+ if (strcmp(context, "none") != 0)
+ append_mntopt(name, context, mntopts, mtabopt, B_TRUE);
+ }
+}
+
+void
+zfs_adjust_mount_options(zfs_handle_t *zhp, const char *mntpoint,
+ char *mntopts, char *mtabopt)
+{
+ char prop[ZFS_MAXPROPLEN];
+
+ /*
+ * Checks to see if the ZFS_PROP_SELINUX_CONTEXT exists
+ * if it does, create a tmp variable in case it's needed
+ * checks to see if the selinux context is set to the default
+ * if it is, allow the setting of the other context properties
+ * this is needed because the 'context' property overrides others
+ * if it is not the default, set the 'context' property
+ */
+ if (zfs_prop_get(zhp, ZFS_PROP_SELINUX_CONTEXT, prop, sizeof (prop),
+ NULL, NULL, 0, B_FALSE) == 0) {
+ if (strcmp(prop, "none") == 0) {
+ zfs_selinux_setcontext(zhp, ZFS_PROP_SELINUX_FSCONTEXT,
+ MNTOPT_FSCONTEXT, mntopts, mtabopt);
+ zfs_selinux_setcontext(zhp, ZFS_PROP_SELINUX_DEFCONTEXT,
+ MNTOPT_DEFCONTEXT, mntopts, mtabopt);
+ zfs_selinux_setcontext(zhp,
+ ZFS_PROP_SELINUX_ROOTCONTEXT, MNTOPT_ROOTCONTEXT,
+ mntopts, mtabopt);
+ } else {
+ append_mntopt(MNTOPT_CONTEXT, prop,
+ mntopts, mtabopt, B_TRUE);
+ }
+ }
+
+ /* A hint used to determine an auto-mounted snapshot mount point */
+ append_mntopt(MNTOPT_MNTPOINT, mntpoint, mntopts, NULL, B_FALSE);
+}
+
+/*
+ * By default the filesystem by preparing the mount options (i.e. parsing
+ * some flags from the "opts" parameter into the "flags" parameter) and then
+ * directly calling the system call mount(2). We don't need the mount utility
+ * or update /etc/mtab, because this is a symlink on all modern systems.
+ *
+ * If the environment variable ZFS_MOUNT_HELPER is set, we fall back to the
+ * previous behavior:
+ * The filesystem is mounted by invoking the system mount utility rather
+ * than by the system call mount(2). This ensures that the /etc/mtab
+ * file is correctly locked for the update. Performing our own locking
+ * and /etc/mtab update requires making an unsafe assumption about how
+ * the mount utility performs its locking. Unfortunately, this also means
+ * in the case of a mount failure we do not have the exact errno. We must
+ * make due with return value from the mount process.
+ */
+int
+do_mount(zfs_handle_t *zhp, const char *mntpt, char *opts, int flags)
+{
+ const char *src = zfs_get_name(zhp);
+ int error = 0;
+
+ if (!libzfs_envvar_is_set("ZFS_MOUNT_HELPER")) {
+ char badopt[MNT_LINE_MAX] = {0};
+ unsigned long mntflags = flags, zfsflags;
+ char myopts[MNT_LINE_MAX] = {0};
+
+ if (zfs_parse_mount_options(opts, &mntflags,
+ &zfsflags, 0, badopt, NULL)) {
+ return (EINVAL);
+ }
+ strlcat(myopts, opts, MNT_LINE_MAX);
+ zfs_adjust_mount_options(zhp, mntpt, myopts, NULL);
+ if (mount(src, mntpt, MNTTYPE_ZFS, mntflags, myopts)) {
+ return (errno);
+ }
+ } else {
+ char *argv[9] = {
+ "/bin/mount",
+ "--no-canonicalize",
+ "-t", MNTTYPE_ZFS,
+ "-o", opts,
+ (char *)src,
+ (char *)mntpt,
+ (char *)NULL };
+
+ /* Return only the most critical mount error */
+ error = libzfs_run_process(argv[0], argv,
+ STDOUT_VERBOSE|STDERR_VERBOSE);
+ if (error) {
+ if (error & MOUNT_FILEIO) {
+ error = EIO;
+ } else if (error & MOUNT_USER) {
+ error = EINTR;
+ } else if (error & MOUNT_SOFTWARE) {
+ error = EPIPE;
+ } else if (error & MOUNT_BUSY) {
+ error = EBUSY;
+ } else if (error & MOUNT_SYSERR) {
+ error = EAGAIN;
+ } else if (error & MOUNT_USAGE) {
+ error = EINVAL;
+ } else
+ error = ENXIO; /* Generic error */
+ }
+ }
+
+ return (error);
+}
+
+int
+do_unmount(const char *mntpt, int flags)
+{
+ if (!libzfs_envvar_is_set("ZFS_MOUNT_HELPER")) {
+ return (umount2(mntpt, flags));
+ }
+
+ char force_opt[] = "-f";
+ char lazy_opt[] = "-l";
+ char *argv[7] = {
+ "/bin/umount",
+ "-t", MNTTYPE_ZFS,
+ NULL, NULL, NULL, NULL };
+ int rc, count = 3;
+
+ if (flags & MS_FORCE) {
+ argv[count] = force_opt;
+ count++;
+ }
+
+ if (flags & MS_DETACH) {
+ argv[count] = lazy_opt;
+ count++;
+ }
+
+ argv[count] = (char *)mntpt;
+ rc = libzfs_run_process(argv[0], argv, STDOUT_VERBOSE|STDERR_VERBOSE);
+
+ return (rc ? EINVAL : 0);
+}
+
+int
+zfs_mount_delegation_check(void)
+{
+ return ((geteuid() != 0) ? EACCES : 0);
+}
diff --git a/lib/libzfs/os/linux/libzfs_pool_os.c b/lib/libzfs/os/linux/libzfs_pool_os.c
new file mode 100644
index 000000000000..5c6da5338dbc
--- /dev/null
+++ b/lib/libzfs/os/linux/libzfs_pool_os.c
@@ -0,0 +1,345 @@
+/*
+ * 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 2015 Nexenta Systems, Inc. All rights reserved.
+ * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 2018 by Delphix. All rights reserved.
+ * Copyright 2016 Igor Kozhukhov <ikozhukhov@gmail.com>
+ * Copyright (c) 2018 Datto Inc.
+ * Copyright (c) 2017 Open-E, Inc. All Rights Reserved.
+ * Copyright (c) 2017, Intel Corporation.
+ * Copyright (c) 2018, loli10K <ezomori.nozomu@gmail.com>
+ */
+
+#include <errno.h>
+#include <libintl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <unistd.h>
+#include <libgen.h>
+#include <zone.h>
+#include <sys/stat.h>
+#include <sys/efi_partition.h>
+#include <sys/systeminfo.h>
+#include <sys/vtoc.h>
+#include <sys/zfs_ioctl.h>
+#include <sys/vdev_disk.h>
+#include <dlfcn.h>
+#include <libzutil.h>
+
+#include "zfs_namecheck.h"
+#include "zfs_prop.h"
+#include "libzfs_impl.h"
+#include "zfs_comutil.h"
+#include "zfeature_common.h"
+
+/*
+ * If the device has being dynamically expanded then we need to relabel
+ * the disk to use the new unallocated space.
+ */
+int
+zpool_relabel_disk(libzfs_handle_t *hdl, const char *path, const char *msg)
+{
+ int fd, error;
+
+ if ((fd = open(path, O_RDWR|O_DIRECT)) < 0) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "cannot "
+ "relabel '%s': unable to open device: %d"), path, errno);
+ return (zfs_error(hdl, EZFS_OPENFAILED, msg));
+ }
+
+ /*
+ * It's possible that we might encounter an error if the device
+ * does not have any unallocated space left. If so, we simply
+ * ignore that error and continue on.
+ *
+ * Also, we don't call efi_rescan() - that would just return EBUSY.
+ * The module will do it for us in vdev_disk_open().
+ */
+ error = efi_use_whole_disk(fd);
+
+ /* Flush the buffers to disk and invalidate the page cache. */
+ (void) fsync(fd);
+ (void) ioctl(fd, BLKFLSBUF);
+
+ (void) close(fd);
+ if (error && error != VT_ENOSPC) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "cannot "
+ "relabel '%s': unable to read disk capacity"), path);
+ return (zfs_error(hdl, EZFS_NOCAP, msg));
+ }
+ return (0);
+}
+
+/*
+ * Read the EFI label from the config, if a label does not exist then
+ * pass back the error to the caller. If the caller has passed a non-NULL
+ * diskaddr argument then we set it to the starting address of the EFI
+ * partition.
+ */
+static int
+read_efi_label(nvlist_t *config, diskaddr_t *sb)
+{
+ char *path;
+ int fd;
+ char diskname[MAXPATHLEN];
+ int err = -1;
+
+ if (nvlist_lookup_string(config, ZPOOL_CONFIG_PATH, &path) != 0)
+ return (err);
+
+ (void) snprintf(diskname, sizeof (diskname), "%s%s", DISK_ROOT,
+ strrchr(path, '/'));
+ if ((fd = open(diskname, O_RDONLY|O_DIRECT)) >= 0) {
+ struct dk_gpt *vtoc;
+
+ if ((err = efi_alloc_and_read(fd, &vtoc)) >= 0) {
+ if (sb != NULL)
+ *sb = vtoc->efi_parts[0].p_start;
+ efi_free(vtoc);
+ }
+ (void) close(fd);
+ }
+ return (err);
+}
+
+/*
+ * determine where a partition starts on a disk in the current
+ * configuration
+ */
+static diskaddr_t
+find_start_block(nvlist_t *config)
+{
+ nvlist_t **child;
+ uint_t c, children;
+ diskaddr_t sb = MAXOFFSET_T;
+ uint64_t wholedisk;
+
+ if (nvlist_lookup_nvlist_array(config,
+ ZPOOL_CONFIG_CHILDREN, &child, &children) != 0) {
+ if (nvlist_lookup_uint64(config,
+ ZPOOL_CONFIG_WHOLE_DISK,
+ &wholedisk) != 0 || !wholedisk) {
+ return (MAXOFFSET_T);
+ }
+ if (read_efi_label(config, &sb) < 0)
+ sb = MAXOFFSET_T;
+ return (sb);
+ }
+
+ for (c = 0; c < children; c++) {
+ sb = find_start_block(child[c]);
+ if (sb != MAXOFFSET_T) {
+ return (sb);
+ }
+ }
+ return (MAXOFFSET_T);
+}
+
+static int
+zpool_label_disk_check(char *path)
+{
+ struct dk_gpt *vtoc;
+ int fd, err;
+
+ if ((fd = open(path, O_RDONLY|O_DIRECT)) < 0)
+ return (errno);
+
+ if ((err = efi_alloc_and_read(fd, &vtoc)) != 0) {
+ (void) close(fd);
+ return (err);
+ }
+
+ if (vtoc->efi_flags & EFI_GPT_PRIMARY_CORRUPT) {
+ efi_free(vtoc);
+ (void) close(fd);
+ return (EIDRM);
+ }
+
+ efi_free(vtoc);
+ (void) close(fd);
+ return (0);
+}
+
+/*
+ * Generate a unique partition name for the ZFS member. Partitions must
+ * have unique names to ensure udev will be able to create symlinks under
+ * /dev/disk/by-partlabel/ for all pool members. The partition names are
+ * of the form <pool>-<unique-id>.
+ */
+static void
+zpool_label_name(char *label_name, int label_size)
+{
+ uint64_t id = 0;
+ int fd;
+
+ fd = open("/dev/urandom", O_RDONLY);
+ if (fd >= 0) {
+ if (read(fd, &id, sizeof (id)) != sizeof (id))
+ id = 0;
+
+ close(fd);
+ }
+
+ if (id == 0)
+ id = (((uint64_t)rand()) << 32) | (uint64_t)rand();
+
+ snprintf(label_name, label_size, "zfs-%016llx", (u_longlong_t)id);
+}
+
+/*
+ * Label an individual disk. The name provided is the short name,
+ * stripped of any leading /dev path.
+ */
+int
+zpool_label_disk(libzfs_handle_t *hdl, zpool_handle_t *zhp, const char *name)
+{
+ char path[MAXPATHLEN];
+ struct dk_gpt *vtoc;
+ int rval, fd;
+ size_t resv = EFI_MIN_RESV_SIZE;
+ uint64_t slice_size;
+ diskaddr_t start_block;
+ char errbuf[1024];
+
+ /* prepare an error message just in case */
+ (void) snprintf(errbuf, sizeof (errbuf),
+ dgettext(TEXT_DOMAIN, "cannot label '%s'"), name);
+
+ if (zhp) {
+ nvlist_t *nvroot;
+
+ verify(nvlist_lookup_nvlist(zhp->zpool_config,
+ ZPOOL_CONFIG_VDEV_TREE, &nvroot) == 0);
+
+ if (zhp->zpool_start_block == 0)
+ start_block = find_start_block(nvroot);
+ else
+ start_block = zhp->zpool_start_block;
+ zhp->zpool_start_block = start_block;
+ } else {
+ /* new pool */
+ start_block = NEW_START_BLOCK;
+ }
+
+ (void) snprintf(path, sizeof (path), "%s/%s", DISK_ROOT, name);
+
+ if ((fd = open(path, O_RDWR|O_DIRECT|O_EXCL)) < 0) {
+ /*
+ * This shouldn't happen. We've long since verified that this
+ * is a valid device.
+ */
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "cannot "
+ "label '%s': unable to open device: %d"), path, errno);
+ return (zfs_error(hdl, EZFS_OPENFAILED, errbuf));
+ }
+
+ if (efi_alloc_and_init(fd, EFI_NUMPAR, &vtoc) != 0) {
+ /*
+ * The only way this can fail is if we run out of memory, or we
+ * were unable to read the disk's capacity
+ */
+ if (errno == ENOMEM)
+ (void) no_memory(hdl);
+
+ (void) close(fd);
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "cannot "
+ "label '%s': unable to read disk capacity"), path);
+
+ return (zfs_error(hdl, EZFS_NOCAP, errbuf));
+ }
+
+ slice_size = vtoc->efi_last_u_lba + 1;
+ slice_size -= EFI_MIN_RESV_SIZE;
+ if (start_block == MAXOFFSET_T)
+ start_block = NEW_START_BLOCK;
+ slice_size -= start_block;
+ slice_size = P2ALIGN(slice_size, PARTITION_END_ALIGNMENT);
+
+ vtoc->efi_parts[0].p_start = start_block;
+ vtoc->efi_parts[0].p_size = slice_size;
+
+ /*
+ * Why we use V_USR: V_BACKUP confuses users, and is considered
+ * disposable by some EFI utilities (since EFI doesn't have a backup
+ * slice). V_UNASSIGNED is supposed to be used only for zero size
+ * partitions, and efi_write() will fail if we use it. V_ROOT, V_BOOT,
+ * etc. were all pretty specific. V_USR is as close to reality as we
+ * can get, in the absence of V_OTHER.
+ */
+ vtoc->efi_parts[0].p_tag = V_USR;
+ zpool_label_name(vtoc->efi_parts[0].p_name, EFI_PART_NAME_LEN);
+
+ vtoc->efi_parts[8].p_start = slice_size + start_block;
+ vtoc->efi_parts[8].p_size = resv;
+ vtoc->efi_parts[8].p_tag = V_RESERVED;
+
+ rval = efi_write(fd, vtoc);
+
+ /* Flush the buffers to disk and invalidate the page cache. */
+ (void) fsync(fd);
+ (void) ioctl(fd, BLKFLSBUF);
+
+ if (rval == 0)
+ rval = efi_rescan(fd);
+
+ /*
+ * Some block drivers (like pcata) may not support EFI GPT labels.
+ * Print out a helpful error message directing the user to manually
+ * label the disk and give a specific slice.
+ */
+ if (rval != 0) {
+ (void) close(fd);
+ efi_free(vtoc);
+
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "try using "
+ "parted(8) and then provide a specific slice: %d"), rval);
+ return (zfs_error(hdl, EZFS_LABELFAILED, errbuf));
+ }
+
+ (void) close(fd);
+ efi_free(vtoc);
+
+ (void) snprintf(path, sizeof (path), "%s/%s", DISK_ROOT, name);
+ (void) zfs_append_partition(path, MAXPATHLEN);
+
+ /* Wait to udev to signal use the device has settled. */
+ rval = zpool_label_disk_wait(path, DISK_LABEL_WAIT);
+ if (rval) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "failed to "
+ "detect device partitions on '%s': %d"), path, rval);
+ return (zfs_error(hdl, EZFS_LABELFAILED, errbuf));
+ }
+
+ /* We can't be to paranoid. Read the label back and verify it. */
+ (void) snprintf(path, sizeof (path), "%s/%s", DISK_ROOT, name);
+ rval = zpool_label_disk_check(path);
+ if (rval) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "freshly written "
+ "EFI label on '%s' is damaged. Ensure\nthis device "
+ "is not in use, and is functioning properly: %d"),
+ path, rval);
+ return (zfs_error(hdl, EZFS_LABELFAILED, errbuf));
+ }
+ return (0);
+}
diff --git a/lib/libzfs/os/linux/libzfs_sendrecv_os.c b/lib/libzfs/os/linux/libzfs_sendrecv_os.c
new file mode 100644
index 000000000000..eeb1f07f2dea
--- /dev/null
+++ b/lib/libzfs/os/linux/libzfs_sendrecv_os.c
@@ -0,0 +1,52 @@
+/*
+ * 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
+ */
+
+
+#include <libzfs.h>
+
+#include "libzfs_impl.h"
+
+#ifndef F_SETPIPE_SZ
+#define F_SETPIPE_SZ (F_SETLEASE + 7)
+#endif /* F_SETPIPE_SZ */
+
+#ifndef F_GETPIPE_SZ
+#define F_GETPIPE_SZ (F_GETLEASE + 7)
+#endif /* F_GETPIPE_SZ */
+
+void
+libzfs_set_pipe_max(int infd)
+{
+ FILE *procf = fopen("/proc/sys/fs/pipe-max-size", "r");
+
+ if (procf != NULL) {
+ unsigned long max_psize;
+ long cur_psize;
+ if (fscanf(procf, "%lu", &max_psize) > 0) {
+ cur_psize = fcntl(infd, F_GETPIPE_SZ);
+ if (cur_psize > 0 &&
+ max_psize > (unsigned long) cur_psize)
+ fcntl(infd, F_SETPIPE_SZ,
+ max_psize);
+ }
+ fclose(procf);
+ }
+}
diff --git a/lib/libzfs/os/linux/libzfs_util_os.c b/lib/libzfs/os/linux/libzfs_util_os.c
new file mode 100644
index 000000000000..918a43f7d03d
--- /dev/null
+++ b/lib/libzfs/os/linux/libzfs_util_os.c
@@ -0,0 +1,215 @@
+/*
+ * 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
+ */
+
+
+#include <errno.h>
+#include <fcntl.h>
+#include <libintl.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <unistd.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 <libzutil.h>
+#include <sys/zfs_sysfs.h>
+
+#define ZDIFF_SHARESDIR "/.zfs/shares/"
+
+int
+zfs_ioctl(libzfs_handle_t *hdl, int request, zfs_cmd_t *zc)
+{
+ return (ioctl(hdl->libzfs_fd, request, zc));
+}
+
+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."));
+ 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."));
+ case ENOEXEC:
+ return (dgettext(TEXT_DOMAIN, "The ZFS modules cannot be "
+ "auto-loaded.\nTry running '/sbin/modprobe zfs' as "
+ "root to manually load them."));
+ case EACCES:
+ return (dgettext(TEXT_DOMAIN, "Permission denied the "
+ "ZFS utilities must be run as root."));
+ default:
+ return (dgettext(TEXT_DOMAIN, "Failed to initialize the "
+ "libzfs library."));
+ }
+}
+
+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);
+}
+
+/*
+ * 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_impl(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);
+}
+
+int
+libzfs_load_module(void)
+{
+ return (libzfs_load_module_impl(ZFS_DRIVER));
+}
+
+int
+find_shares_object(differ_info_t *di)
+{
+ char fullpath[MAXPATHLEN];
+ struct stat64 sb = { 0 };
+
+ (void) strlcpy(fullpath, di->dsmnt, MAXPATHLEN);
+ (void) strlcat(fullpath, ZDIFF_SHARESDIR, MAXPATHLEN);
+
+ if (stat64(fullpath, &sb) != 0) {
+ (void) snprintf(di->errbuf, sizeof (di->errbuf),
+ dgettext(TEXT_DOMAIN, "Cannot stat %s"), fullpath);
+ return (zfs_error(di->zhp->zfs_hdl, EZFS_DIFF, di->errbuf));
+ }
+
+ di->shares = (uint64_t)sb.st_ino;
+ return (0);
+}
+
+/*
+ * 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);
+}
diff --git a/lib/libzfs_core/.gitignore b/lib/libzfs_core/.gitignore
new file mode 100644
index 000000000000..c428d6369033
--- /dev/null
+++ b/lib/libzfs_core/.gitignore
@@ -0,0 +1 @@
+/libzfs_core.pc
diff --git a/lib/libzfs_core/Makefile.am b/lib/libzfs_core/Makefile.am
new file mode 100644
index 000000000000..e94ba85d275c
--- /dev/null
+++ b/lib/libzfs_core/Makefile.am
@@ -0,0 +1,29 @@
+include $(top_srcdir)/config/Rules.am
+
+pkgconfig_DATA = libzfs_core.pc
+
+lib_LTLIBRARIES = libzfs_core.la
+
+USER_C = \
+ libzfs_core.c
+
+libzfs_core_la_SOURCES = $(USER_C)
+
+libzfs_core_la_LIBADD = \
+ $(abs_top_builddir)/lib/libzutil/libzutil.la \
+ $(abs_top_builddir)/lib/libnvpair/libnvpair.la
+
+libzfs_core_la_LIBADD += $(LTLIBINTL)
+
+libzfs_core_la_LDFLAGS = -pthread
+
+if !ASAN_ENABLED
+libzfs_core_la_LDFLAGS += -Wl,-z,defs
+endif
+
+if BUILD_FREEBSD
+libzfs_core_la_LIBADD += -lutil -lgeom
+libzfs_core_la_LDFLAGS += -version-info 3:0:0
+else
+libzfs_core_la_LDFLAGS += -version-info 1:0:0
+endif
diff --git a/lib/libzfs_core/libzfs_core.c b/lib/libzfs_core/libzfs_core.c
new file mode 100644
index 000000000000..50b1b9dec260
--- /dev/null
+++ b/lib/libzfs_core/libzfs_core.c
@@ -0,0 +1,1644 @@
+/*
+ * 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) 2012, 2020 by Delphix. All rights reserved.
+ * Copyright (c) 2013 Steven Hartland. All rights reserved.
+ * Copyright (c) 2017 Datto Inc.
+ * Copyright 2017 RackTop Systems.
+ * Copyright (c) 2017 Open-E, Inc. All Rights Reserved.
+ * Copyright (c) 2019, 2020 by Christian Schwarz. All rights reserved.
+ */
+
+/*
+ * LibZFS_Core (lzc) is intended to replace most functionality in libzfs.
+ * It has the following characteristics:
+ *
+ * - Thread Safe. libzfs_core is accessible concurrently from multiple
+ * threads. This is accomplished primarily by avoiding global data
+ * (e.g. caching). Since it's thread-safe, there is no reason for a
+ * process to have multiple libzfs "instances". Therefore, we store
+ * our few pieces of data (e.g. the file descriptor) in global
+ * variables. The fd is reference-counted so that the libzfs_core
+ * library can be "initialized" multiple times (e.g. by different
+ * consumers within the same process).
+ *
+ * - Committed Interface. The libzfs_core interface will be committed,
+ * therefore consumers can compile against it and be confident that
+ * their code will continue to work on future releases of this code.
+ * Currently, the interface is Evolving (not Committed), but we intend
+ * to commit to it once it is more complete and we determine that it
+ * meets the needs of all consumers.
+ *
+ * - Programmatic Error Handling. libzfs_core communicates errors with
+ * defined error numbers, and doesn't print anything to stdout/stderr.
+ *
+ * - Thin Layer. libzfs_core is a thin layer, marshaling arguments
+ * to/from the kernel ioctls. There is generally a 1:1 correspondence
+ * between libzfs_core functions and ioctls to ZFS_DEV.
+ *
+ * - Clear Atomicity. Because libzfs_core functions are generally 1:1
+ * with kernel ioctls, and kernel ioctls are general atomic, each
+ * libzfs_core function is atomic. For example, creating multiple
+ * snapshots with a single call to lzc_snapshot() is atomic -- it
+ * can't fail with only some of the requested snapshots created, even
+ * in the event of power loss or system crash.
+ *
+ * - Continued libzfs Support. Some higher-level operations (e.g.
+ * support for "zfs send -R") are too complicated to fit the scope of
+ * libzfs_core. This functionality will continue to live in libzfs.
+ * Where appropriate, libzfs will use the underlying atomic operations
+ * of libzfs_core. For example, libzfs may implement "zfs send -R |
+ * zfs receive" by using individual "send one snapshot", rename,
+ * destroy, and "receive one snapshot" operations in libzfs_core.
+ * /sbin/zfs and /sbin/zpool will link with both libzfs and
+ * libzfs_core. Other consumers should aim to use only libzfs_core,
+ * since that will be the supported, stable interface going forwards.
+ */
+
+#include <libzfs_core.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef ZFS_DEBUG
+#include <stdio.h>
+#endif
+#include <errno.h>
+#include <fcntl.h>
+#include <pthread.h>
+#include <libzutil.h>
+#include <sys/nvpair.h>
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/zfs_ioctl.h>
+
+static int g_fd = -1;
+static pthread_mutex_t g_lock = PTHREAD_MUTEX_INITIALIZER;
+static int g_refcount;
+
+#ifdef ZFS_DEBUG
+static zfs_ioc_t fail_ioc_cmd = ZFS_IOC_LAST;
+static zfs_errno_t fail_ioc_err;
+
+static void
+libzfs_core_debug_ioc(void)
+{
+ /*
+ * To test running newer user space binaries with kernel's
+ * that don't yet support an ioctl or a new ioctl arg we
+ * provide an override to intentionally fail an ioctl.
+ *
+ * USAGE:
+ * The override variable, ZFS_IOC_TEST, is of the form "cmd:err"
+ *
+ * For example, to fail a ZFS_IOC_POOL_CHECKPOINT with a
+ * ZFS_ERR_IOC_CMD_UNAVAIL, the string would be "0x5a4d:1029"
+ *
+ * $ sudo sh -c "ZFS_IOC_TEST=0x5a4d:1029 zpool checkpoint tank"
+ * cannot checkpoint 'tank': the loaded zfs module does not support
+ * this operation. A reboot may be required to enable this operation.
+ */
+ if (fail_ioc_cmd == ZFS_IOC_LAST) {
+ char *ioc_test = getenv("ZFS_IOC_TEST");
+ unsigned int ioc_num = 0, ioc_err = 0;
+
+ if (ioc_test != NULL &&
+ sscanf(ioc_test, "%i:%i", &ioc_num, &ioc_err) == 2 &&
+ ioc_num < ZFS_IOC_LAST) {
+ fail_ioc_cmd = ioc_num;
+ fail_ioc_err = ioc_err;
+ }
+ }
+}
+#endif
+
+int
+libzfs_core_init(void)
+{
+ (void) pthread_mutex_lock(&g_lock);
+ if (g_refcount == 0) {
+ g_fd = open(ZFS_DEV, O_RDWR);
+ if (g_fd < 0) {
+ (void) pthread_mutex_unlock(&g_lock);
+ return (errno);
+ }
+ }
+ g_refcount++;
+
+#ifdef ZFS_DEBUG
+ libzfs_core_debug_ioc();
+#endif
+ (void) pthread_mutex_unlock(&g_lock);
+ return (0);
+}
+
+void
+libzfs_core_fini(void)
+{
+ (void) pthread_mutex_lock(&g_lock);
+ ASSERT3S(g_refcount, >, 0);
+
+ if (g_refcount > 0)
+ g_refcount--;
+
+ if (g_refcount == 0 && g_fd != -1) {
+ (void) close(g_fd);
+ g_fd = -1;
+ }
+ (void) pthread_mutex_unlock(&g_lock);
+}
+
+static int
+lzc_ioctl(zfs_ioc_t ioc, const char *name,
+ nvlist_t *source, nvlist_t **resultp)
+{
+ zfs_cmd_t zc = {"\0"};
+ int error = 0;
+ char *packed = NULL;
+ size_t size = 0;
+
+ ASSERT3S(g_refcount, >, 0);
+ VERIFY3S(g_fd, !=, -1);
+
+#ifdef ZFS_DEBUG
+ if (ioc == fail_ioc_cmd)
+ return (fail_ioc_err);
+#endif
+
+ if (name != NULL)
+ (void) strlcpy(zc.zc_name, name, sizeof (zc.zc_name));
+
+ if (source != NULL) {
+ packed = fnvlist_pack(source, &size);
+ zc.zc_nvlist_src = (uint64_t)(uintptr_t)packed;
+ zc.zc_nvlist_src_size = size;
+ }
+
+ if (resultp != NULL) {
+ *resultp = NULL;
+ if (ioc == ZFS_IOC_CHANNEL_PROGRAM) {
+ zc.zc_nvlist_dst_size = fnvlist_lookup_uint64(source,
+ ZCP_ARG_MEMLIMIT);
+ } else {
+ zc.zc_nvlist_dst_size = MAX(size * 2, 128 * 1024);
+ }
+ zc.zc_nvlist_dst = (uint64_t)(uintptr_t)
+ malloc(zc.zc_nvlist_dst_size);
+ if (zc.zc_nvlist_dst == (uint64_t)0) {
+ error = ENOMEM;
+ goto out;
+ }
+ }
+
+ while (zfs_ioctl_fd(g_fd, ioc, &zc) != 0) {
+ /*
+ * If ioctl exited with ENOMEM, we retry the ioctl after
+ * increasing the size of the destination nvlist.
+ *
+ * Channel programs that exit with ENOMEM ran over the
+ * lua memory sandbox; they should not be retried.
+ */
+ if (errno == ENOMEM && resultp != NULL &&
+ ioc != ZFS_IOC_CHANNEL_PROGRAM) {
+ free((void *)(uintptr_t)zc.zc_nvlist_dst);
+ zc.zc_nvlist_dst_size *= 2;
+ zc.zc_nvlist_dst = (uint64_t)(uintptr_t)
+ malloc(zc.zc_nvlist_dst_size);
+ if (zc.zc_nvlist_dst == (uint64_t)0) {
+ error = ENOMEM;
+ goto out;
+ }
+ } else {
+ error = errno;
+ break;
+ }
+ }
+ if (zc.zc_nvlist_dst_filled) {
+ *resultp = fnvlist_unpack((void *)(uintptr_t)zc.zc_nvlist_dst,
+ zc.zc_nvlist_dst_size);
+ }
+
+out:
+ if (packed != NULL)
+ fnvlist_pack_free(packed, size);
+ free((void *)(uintptr_t)zc.zc_nvlist_dst);
+ return (error);
+}
+
+int
+lzc_create(const char *fsname, enum lzc_dataset_type type, nvlist_t *props,
+ uint8_t *wkeydata, uint_t wkeylen)
+{
+ int error;
+ nvlist_t *hidden_args = NULL;
+ nvlist_t *args = fnvlist_alloc();
+
+ fnvlist_add_int32(args, "type", (dmu_objset_type_t)type);
+ if (props != NULL)
+ fnvlist_add_nvlist(args, "props", props);
+
+ if (wkeydata != NULL) {
+ hidden_args = fnvlist_alloc();
+ fnvlist_add_uint8_array(hidden_args, "wkeydata", wkeydata,
+ wkeylen);
+ fnvlist_add_nvlist(args, ZPOOL_HIDDEN_ARGS, hidden_args);
+ }
+
+ error = lzc_ioctl(ZFS_IOC_CREATE, fsname, args, NULL);
+ nvlist_free(hidden_args);
+ nvlist_free(args);
+ return (error);
+}
+
+int
+lzc_clone(const char *fsname, const char *origin, nvlist_t *props)
+{
+ int error;
+ nvlist_t *hidden_args = NULL;
+ nvlist_t *args = fnvlist_alloc();
+
+ fnvlist_add_string(args, "origin", origin);
+ if (props != NULL)
+ fnvlist_add_nvlist(args, "props", props);
+ error = lzc_ioctl(ZFS_IOC_CLONE, fsname, args, NULL);
+ nvlist_free(hidden_args);
+ nvlist_free(args);
+ return (error);
+}
+
+int
+lzc_promote(const char *fsname, char *snapnamebuf, int snapnamelen)
+{
+ /*
+ * The promote ioctl is still legacy, so we need to construct our
+ * own zfs_cmd_t rather than using lzc_ioctl().
+ */
+ zfs_cmd_t zc = {"\0"};
+
+ ASSERT3S(g_refcount, >, 0);
+ VERIFY3S(g_fd, !=, -1);
+
+ (void) strlcpy(zc.zc_name, fsname, sizeof (zc.zc_name));
+ if (zfs_ioctl_fd(g_fd, ZFS_IOC_PROMOTE, &zc) != 0) {
+ int error = errno;
+ if (error == EEXIST && snapnamebuf != NULL)
+ (void) strlcpy(snapnamebuf, zc.zc_string, snapnamelen);
+ return (error);
+ }
+ return (0);
+}
+
+int
+lzc_rename(const char *source, const char *target)
+{
+ zfs_cmd_t zc = {"\0"};
+ int error;
+
+ ASSERT3S(g_refcount, >, 0);
+ VERIFY3S(g_fd, !=, -1);
+ (void) strlcpy(zc.zc_name, source, sizeof (zc.zc_name));
+ (void) strlcpy(zc.zc_value, target, sizeof (zc.zc_value));
+ error = zfs_ioctl_fd(g_fd, ZFS_IOC_RENAME, &zc);
+ if (error != 0)
+ error = errno;
+ return (error);
+}
+int
+lzc_destroy(const char *fsname)
+{
+ int error;
+ nvlist_t *args = fnvlist_alloc();
+ error = lzc_ioctl(ZFS_IOC_DESTROY, fsname, args, NULL);
+ nvlist_free(args);
+ return (error);
+}
+
+/*
+ * Creates snapshots.
+ *
+ * The keys in the snaps nvlist are the snapshots to be created.
+ * They must all be in the same pool.
+ *
+ * The props nvlist is properties to set. Currently only user properties
+ * are supported. { user:prop_name -> string value }
+ *
+ * The returned results nvlist will have an entry for each snapshot that failed.
+ * The value will be the (int32) error code.
+ *
+ * The return value will be 0 if all snapshots were created, otherwise it will
+ * be the errno of a (unspecified) snapshot that failed.
+ */
+int
+lzc_snapshot(nvlist_t *snaps, nvlist_t *props, nvlist_t **errlist)
+{
+ nvpair_t *elem;
+ nvlist_t *args;
+ int error;
+ char pool[ZFS_MAX_DATASET_NAME_LEN];
+
+ *errlist = NULL;
+
+ /* determine the pool name */
+ elem = nvlist_next_nvpair(snaps, NULL);
+ if (elem == NULL)
+ return (0);
+ (void) strlcpy(pool, nvpair_name(elem), sizeof (pool));
+ pool[strcspn(pool, "/@")] = '\0';
+
+ args = fnvlist_alloc();
+ fnvlist_add_nvlist(args, "snaps", snaps);
+ if (props != NULL)
+ fnvlist_add_nvlist(args, "props", props);
+
+ error = lzc_ioctl(ZFS_IOC_SNAPSHOT, pool, args, errlist);
+ nvlist_free(args);
+
+ return (error);
+}
+
+/*
+ * Destroys snapshots.
+ *
+ * The keys in the snaps nvlist are the snapshots to be destroyed.
+ * They must all be in the same pool.
+ *
+ * Snapshots that do not exist will be silently ignored.
+ *
+ * If 'defer' is not set, and a snapshot has user holds or clones, the
+ * destroy operation will fail and none of the snapshots will be
+ * destroyed.
+ *
+ * If 'defer' is set, and a snapshot has user holds or clones, it will be
+ * marked for deferred destruction, and will be destroyed when the last hold
+ * or clone is removed/destroyed.
+ *
+ * The return value will be 0 if all snapshots were destroyed (or marked for
+ * later destruction if 'defer' is set) or didn't exist to begin with.
+ *
+ * Otherwise the return value will be the errno of a (unspecified) snapshot
+ * that failed, no snapshots will be destroyed, and the errlist will have an
+ * entry for each snapshot that failed. The value in the errlist will be
+ * the (int32) error code.
+ */
+int
+lzc_destroy_snaps(nvlist_t *snaps, boolean_t defer, nvlist_t **errlist)
+{
+ nvpair_t *elem;
+ nvlist_t *args;
+ int error;
+ char pool[ZFS_MAX_DATASET_NAME_LEN];
+
+ /* determine the pool name */
+ elem = nvlist_next_nvpair(snaps, NULL);
+ if (elem == NULL)
+ return (0);
+ (void) strlcpy(pool, nvpair_name(elem), sizeof (pool));
+ pool[strcspn(pool, "/@")] = '\0';
+
+ args = fnvlist_alloc();
+ fnvlist_add_nvlist(args, "snaps", snaps);
+ if (defer)
+ fnvlist_add_boolean(args, "defer");
+
+ error = lzc_ioctl(ZFS_IOC_DESTROY_SNAPS, pool, args, errlist);
+ nvlist_free(args);
+
+ return (error);
+}
+
+int
+lzc_snaprange_space(const char *firstsnap, const char *lastsnap,
+ uint64_t *usedp)
+{
+ nvlist_t *args;
+ nvlist_t *result;
+ int err;
+ char fs[ZFS_MAX_DATASET_NAME_LEN];
+ char *atp;
+
+ /* determine the fs name */
+ (void) strlcpy(fs, firstsnap, sizeof (fs));
+ atp = strchr(fs, '@');
+ if (atp == NULL)
+ return (EINVAL);
+ *atp = '\0';
+
+ args = fnvlist_alloc();
+ fnvlist_add_string(args, "firstsnap", firstsnap);
+
+ err = lzc_ioctl(ZFS_IOC_SPACE_SNAPS, lastsnap, args, &result);
+ nvlist_free(args);
+ if (err == 0)
+ *usedp = fnvlist_lookup_uint64(result, "used");
+ fnvlist_free(result);
+
+ return (err);
+}
+
+boolean_t
+lzc_exists(const char *dataset)
+{
+ /*
+ * The objset_stats ioctl is still legacy, so we need to construct our
+ * own zfs_cmd_t rather than using lzc_ioctl().
+ */
+ zfs_cmd_t zc = {"\0"};
+
+ ASSERT3S(g_refcount, >, 0);
+ VERIFY3S(g_fd, !=, -1);
+
+ (void) strlcpy(zc.zc_name, dataset, sizeof (zc.zc_name));
+ return (zfs_ioctl_fd(g_fd, ZFS_IOC_OBJSET_STATS, &zc) == 0);
+}
+
+/*
+ * outnvl is unused.
+ * It was added to preserve the function signature in case it is
+ * needed in the future.
+ */
+/*ARGSUSED*/
+int
+lzc_sync(const char *pool_name, nvlist_t *innvl, nvlist_t **outnvl)
+{
+ return (lzc_ioctl(ZFS_IOC_POOL_SYNC, pool_name, innvl, NULL));
+}
+
+/*
+ * Create "user holds" on snapshots. If there is a hold on a snapshot,
+ * the snapshot can not be destroyed. (However, it can be marked for deletion
+ * by lzc_destroy_snaps(defer=B_TRUE).)
+ *
+ * The keys in the nvlist are snapshot names.
+ * The snapshots must all be in the same pool.
+ * The value is the name of the hold (string type).
+ *
+ * If cleanup_fd is not -1, it must be the result of open(ZFS_DEV, O_EXCL).
+ * In this case, when the cleanup_fd is closed (including on process
+ * termination), the holds will be released. If the system is shut down
+ * uncleanly, the holds will be released when the pool is next opened
+ * or imported.
+ *
+ * Holds for snapshots which don't exist will be skipped and have an entry
+ * added to errlist, but will not cause an overall failure.
+ *
+ * The return value will be 0 if all holds, for snapshots that existed,
+ * were successfully created.
+ *
+ * Otherwise the return value will be the errno of a (unspecified) hold that
+ * failed and no holds will be created.
+ *
+ * In all cases the errlist will have an entry for each hold that failed
+ * (name = snapshot), with its value being the error code (int32).
+ */
+int
+lzc_hold(nvlist_t *holds, int cleanup_fd, nvlist_t **errlist)
+{
+ char pool[ZFS_MAX_DATASET_NAME_LEN];
+ nvlist_t *args;
+ nvpair_t *elem;
+ int error;
+
+ /* determine the pool name */
+ elem = nvlist_next_nvpair(holds, NULL);
+ if (elem == NULL)
+ return (0);
+ (void) strlcpy(pool, nvpair_name(elem), sizeof (pool));
+ pool[strcspn(pool, "/@")] = '\0';
+
+ args = fnvlist_alloc();
+ fnvlist_add_nvlist(args, "holds", holds);
+ if (cleanup_fd != -1)
+ fnvlist_add_int32(args, "cleanup_fd", cleanup_fd);
+
+ error = lzc_ioctl(ZFS_IOC_HOLD, pool, args, errlist);
+ nvlist_free(args);
+ return (error);
+}
+
+/*
+ * Release "user holds" on snapshots. If the snapshot has been marked for
+ * deferred destroy (by lzc_destroy_snaps(defer=B_TRUE)), it does not have
+ * any clones, and all the user holds are removed, then the snapshot will be
+ * destroyed.
+ *
+ * The keys in the nvlist are snapshot names.
+ * The snapshots must all be in the same pool.
+ * The value is an nvlist whose keys are the holds to remove.
+ *
+ * Holds which failed to release because they didn't exist will have an entry
+ * added to errlist, but will not cause an overall failure.
+ *
+ * The return value will be 0 if the nvl holds was empty or all holds that
+ * existed, were successfully removed.
+ *
+ * Otherwise the return value will be the errno of a (unspecified) hold that
+ * failed to release and no holds will be released.
+ *
+ * In all cases the errlist will have an entry for each hold that failed to
+ * to release.
+ */
+int
+lzc_release(nvlist_t *holds, nvlist_t **errlist)
+{
+ char pool[ZFS_MAX_DATASET_NAME_LEN];
+ nvpair_t *elem;
+
+ /* determine the pool name */
+ elem = nvlist_next_nvpair(holds, NULL);
+ if (elem == NULL)
+ return (0);
+ (void) strlcpy(pool, nvpair_name(elem), sizeof (pool));
+ pool[strcspn(pool, "/@")] = '\0';
+
+ return (lzc_ioctl(ZFS_IOC_RELEASE, pool, holds, errlist));
+}
+
+/*
+ * Retrieve list of user holds on the specified snapshot.
+ *
+ * On success, *holdsp will be set to an nvlist which the caller must free.
+ * The keys are the names of the holds, and the value is the creation time
+ * of the hold (uint64) in seconds since the epoch.
+ */
+int
+lzc_get_holds(const char *snapname, nvlist_t **holdsp)
+{
+ return (lzc_ioctl(ZFS_IOC_GET_HOLDS, snapname, NULL, holdsp));
+}
+
+/*
+ * Generate a zfs send stream for the specified snapshot and write it to
+ * the specified file descriptor.
+ *
+ * "snapname" is the full name of the snapshot to send (e.g. "pool/fs@snap")
+ *
+ * If "from" is NULL, a full (non-incremental) stream will be sent.
+ * If "from" is non-NULL, it must be the full name of a snapshot or
+ * bookmark to send an incremental from (e.g. "pool/fs@earlier_snap" or
+ * "pool/fs#earlier_bmark"). If non-NULL, the specified snapshot or
+ * bookmark must represent an earlier point in the history of "snapname").
+ * It can be an earlier snapshot in the same filesystem or zvol as "snapname",
+ * or it can be the origin of "snapname"'s filesystem, or an earlier
+ * snapshot in the origin, etc.
+ *
+ * "fd" is the file descriptor to write the send stream to.
+ *
+ * If "flags" contains LZC_SEND_FLAG_LARGE_BLOCK, the stream is permitted
+ * to contain DRR_WRITE records with drr_length > 128K, and DRR_OBJECT
+ * records with drr_blksz > 128K.
+ *
+ * If "flags" contains LZC_SEND_FLAG_EMBED_DATA, the stream is permitted
+ * to contain DRR_WRITE_EMBEDDED records with drr_etype==BP_EMBEDDED_TYPE_DATA,
+ * which the receiving system must support (as indicated by support
+ * for the "embedded_data" feature).
+ *
+ * If "flags" contains LZC_SEND_FLAG_COMPRESS, the stream is generated by using
+ * compressed WRITE records for blocks which are compressed on disk and in
+ * memory. If the lz4_compress feature is active on the sending system, then
+ * the receiving system must have that feature enabled as well.
+ *
+ * If "flags" contains LZC_SEND_FLAG_RAW, the stream is generated, for encrypted
+ * datasets, by sending data exactly as it exists on disk. This allows backups
+ * to be taken even if encryption keys are not currently loaded.
+ */
+int
+lzc_send(const char *snapname, const char *from, int fd,
+ enum lzc_send_flags flags)
+{
+ return (lzc_send_resume_redacted(snapname, from, fd, flags, 0, 0,
+ NULL));
+}
+
+int
+lzc_send_redacted(const char *snapname, const char *from, int fd,
+ enum lzc_send_flags flags, const char *redactbook)
+{
+ return (lzc_send_resume_redacted(snapname, from, fd, flags, 0, 0,
+ redactbook));
+}
+
+int
+lzc_send_resume(const char *snapname, const char *from, int fd,
+ enum lzc_send_flags flags, uint64_t resumeobj, uint64_t resumeoff)
+{
+ return (lzc_send_resume_redacted(snapname, from, fd, flags, resumeobj,
+ resumeoff, NULL));
+}
+
+/*
+ * snapname: The name of the "tosnap", or the snapshot whose contents we are
+ * sending.
+ * from: The name of the "fromsnap", or the incremental source.
+ * fd: File descriptor to write the stream to.
+ * flags: flags that determine features to be used by the stream.
+ * resumeobj: Object to resume from, for resuming send
+ * resumeoff: Offset to resume from, for resuming send.
+ * redactnv: nvlist of string -> boolean(ignored) containing the names of all
+ * the snapshots that we should redact with respect to.
+ * redactbook: Name of the redaction bookmark to create.
+ */
+int
+lzc_send_resume_redacted(const char *snapname, const char *from, int fd,
+ enum lzc_send_flags flags, uint64_t resumeobj, uint64_t resumeoff,
+ const char *redactbook)
+{
+ nvlist_t *args;
+ int err;
+
+ args = fnvlist_alloc();
+ fnvlist_add_int32(args, "fd", fd);
+ if (from != NULL)
+ fnvlist_add_string(args, "fromsnap", from);
+ if (flags & LZC_SEND_FLAG_LARGE_BLOCK)
+ fnvlist_add_boolean(args, "largeblockok");
+ if (flags & LZC_SEND_FLAG_EMBED_DATA)
+ fnvlist_add_boolean(args, "embedok");
+ if (flags & LZC_SEND_FLAG_COMPRESS)
+ fnvlist_add_boolean(args, "compressok");
+ if (flags & LZC_SEND_FLAG_RAW)
+ fnvlist_add_boolean(args, "rawok");
+ if (flags & LZC_SEND_FLAG_SAVED)
+ fnvlist_add_boolean(args, "savedok");
+ if (resumeobj != 0 || resumeoff != 0) {
+ fnvlist_add_uint64(args, "resume_object", resumeobj);
+ fnvlist_add_uint64(args, "resume_offset", resumeoff);
+ }
+ if (redactbook != NULL)
+ fnvlist_add_string(args, "redactbook", redactbook);
+
+ err = lzc_ioctl(ZFS_IOC_SEND_NEW, snapname, args, NULL);
+ nvlist_free(args);
+ return (err);
+}
+
+/*
+ * "from" can be NULL, a snapshot, or a bookmark.
+ *
+ * If from is NULL, a full (non-incremental) stream will be estimated. This
+ * is calculated very efficiently.
+ *
+ * If from is a snapshot, lzc_send_space uses the deadlists attached to
+ * each snapshot to efficiently estimate the stream size.
+ *
+ * If from is a bookmark, the indirect blocks in the destination snapshot
+ * are traversed, looking for blocks with a birth time since the creation TXG of
+ * the snapshot this bookmark was created from. This will result in
+ * significantly more I/O and be less efficient than a send space estimation on
+ * an equivalent snapshot. This process is also used if redact_snaps is
+ * non-null.
+ */
+int
+lzc_send_space_resume_redacted(const char *snapname, const char *from,
+ enum lzc_send_flags flags, uint64_t resumeobj, uint64_t resumeoff,
+ uint64_t resume_bytes, const char *redactbook, int fd, uint64_t *spacep)
+{
+ nvlist_t *args;
+ nvlist_t *result;
+ int err;
+
+ args = fnvlist_alloc();
+ if (from != NULL)
+ fnvlist_add_string(args, "from", from);
+ if (flags & LZC_SEND_FLAG_LARGE_BLOCK)
+ fnvlist_add_boolean(args, "largeblockok");
+ if (flags & LZC_SEND_FLAG_EMBED_DATA)
+ fnvlist_add_boolean(args, "embedok");
+ if (flags & LZC_SEND_FLAG_COMPRESS)
+ fnvlist_add_boolean(args, "compressok");
+ if (flags & LZC_SEND_FLAG_RAW)
+ fnvlist_add_boolean(args, "rawok");
+ if (resumeobj != 0 || resumeoff != 0) {
+ fnvlist_add_uint64(args, "resume_object", resumeobj);
+ fnvlist_add_uint64(args, "resume_offset", resumeoff);
+ fnvlist_add_uint64(args, "bytes", resume_bytes);
+ }
+ if (redactbook != NULL)
+ fnvlist_add_string(args, "redactbook", redactbook);
+ if (fd != -1)
+ fnvlist_add_int32(args, "fd", fd);
+
+ err = lzc_ioctl(ZFS_IOC_SEND_SPACE, snapname, args, &result);
+ nvlist_free(args);
+ if (err == 0)
+ *spacep = fnvlist_lookup_uint64(result, "space");
+ nvlist_free(result);
+ return (err);
+}
+
+int
+lzc_send_space(const char *snapname, const char *from,
+ enum lzc_send_flags flags, uint64_t *spacep)
+{
+ return (lzc_send_space_resume_redacted(snapname, from, flags, 0, 0, 0,
+ NULL, -1, spacep));
+}
+
+static int
+recv_read(int fd, void *buf, int ilen)
+{
+ char *cp = buf;
+ int rv;
+ int len = ilen;
+
+ do {
+ rv = read(fd, cp, len);
+ cp += rv;
+ len -= rv;
+ } while (rv > 0);
+
+ if (rv < 0 || len != 0)
+ return (EIO);
+
+ return (0);
+}
+
+/*
+ * Linux adds ZFS_IOC_RECV_NEW for resumable and raw streams and preserves the
+ * legacy ZFS_IOC_RECV user/kernel interface. The new interface supports all
+ * stream options but is currently only used for resumable streams. This way
+ * updated user space utilities will interoperate with older kernel modules.
+ *
+ * Non-Linux OpenZFS platforms have opted to modify the legacy interface.
+ */
+static int
+recv_impl(const char *snapname, nvlist_t *recvdprops, nvlist_t *localprops,
+ uint8_t *wkeydata, uint_t wkeylen, const char *origin, boolean_t force,
+ boolean_t resumable, boolean_t raw, int input_fd,
+ const dmu_replay_record_t *begin_record, uint64_t *read_bytes,
+ uint64_t *errflags, nvlist_t **errors)
+{
+ dmu_replay_record_t drr;
+ char fsname[MAXPATHLEN];
+ char *atp;
+ int error;
+ boolean_t payload = B_FALSE;
+
+ ASSERT3S(g_refcount, >, 0);
+ VERIFY3S(g_fd, !=, -1);
+
+ /* Set 'fsname' to the name of containing filesystem */
+ (void) strlcpy(fsname, snapname, sizeof (fsname));
+ atp = strchr(fsname, '@');
+ if (atp == NULL)
+ return (EINVAL);
+ *atp = '\0';
+
+ /* If the fs does not exist, try its parent. */
+ if (!lzc_exists(fsname)) {
+ char *slashp = strrchr(fsname, '/');
+ if (slashp == NULL)
+ return (ENOENT);
+ *slashp = '\0';
+ }
+
+ /*
+ * The begin_record is normally a non-byteswapped BEGIN record.
+ * For resumable streams it may be set to any non-byteswapped
+ * dmu_replay_record_t.
+ */
+ if (begin_record == NULL) {
+ error = recv_read(input_fd, &drr, sizeof (drr));
+ if (error != 0)
+ return (error);
+ } else {
+ drr = *begin_record;
+ payload = (begin_record->drr_payloadlen != 0);
+ }
+
+ /*
+ * All receives with a payload should use the new interface.
+ */
+ if (resumable || raw || wkeydata != NULL || payload) {
+ nvlist_t *outnvl = NULL;
+ nvlist_t *innvl = fnvlist_alloc();
+
+ fnvlist_add_string(innvl, "snapname", snapname);
+
+ if (recvdprops != NULL)
+ fnvlist_add_nvlist(innvl, "props", recvdprops);
+
+ if (localprops != NULL)
+ fnvlist_add_nvlist(innvl, "localprops", localprops);
+
+ if (wkeydata != NULL) {
+ /*
+ * wkeydata must be placed in the special
+ * ZPOOL_HIDDEN_ARGS nvlist so that it
+ * will not be printed to the zpool history.
+ */
+ nvlist_t *hidden_args = fnvlist_alloc();
+ fnvlist_add_uint8_array(hidden_args, "wkeydata",
+ wkeydata, wkeylen);
+ fnvlist_add_nvlist(innvl, ZPOOL_HIDDEN_ARGS,
+ hidden_args);
+ nvlist_free(hidden_args);
+ }
+
+ if (origin != NULL && strlen(origin))
+ fnvlist_add_string(innvl, "origin", origin);
+
+ fnvlist_add_byte_array(innvl, "begin_record",
+ (uchar_t *)&drr, sizeof (drr));
+
+ fnvlist_add_int32(innvl, "input_fd", input_fd);
+
+ if (force)
+ fnvlist_add_boolean(innvl, "force");
+
+ if (resumable)
+ fnvlist_add_boolean(innvl, "resumable");
+
+
+ error = lzc_ioctl(ZFS_IOC_RECV_NEW, fsname, innvl, &outnvl);
+
+ if (error == 0 && read_bytes != NULL)
+ error = nvlist_lookup_uint64(outnvl, "read_bytes",
+ read_bytes);
+
+ if (error == 0 && errflags != NULL)
+ error = nvlist_lookup_uint64(outnvl, "error_flags",
+ errflags);
+
+ if (error == 0 && errors != NULL) {
+ nvlist_t *nvl;
+ error = nvlist_lookup_nvlist(outnvl, "errors", &nvl);
+ if (error == 0)
+ *errors = fnvlist_dup(nvl);
+ }
+
+ fnvlist_free(innvl);
+ fnvlist_free(outnvl);
+ } else {
+ zfs_cmd_t zc = {"\0"};
+ char *packed = NULL;
+ size_t size;
+
+ ASSERT3S(g_refcount, >, 0);
+
+ (void) strlcpy(zc.zc_name, fsname, sizeof (zc.zc_name));
+ (void) strlcpy(zc.zc_value, snapname, sizeof (zc.zc_value));
+
+ if (recvdprops != NULL) {
+ packed = fnvlist_pack(recvdprops, &size);
+ zc.zc_nvlist_src = (uint64_t)(uintptr_t)packed;
+ zc.zc_nvlist_src_size = size;
+ }
+
+ if (localprops != NULL) {
+ packed = fnvlist_pack(localprops, &size);
+ zc.zc_nvlist_conf = (uint64_t)(uintptr_t)packed;
+ zc.zc_nvlist_conf_size = size;
+ }
+
+ if (origin != NULL)
+ (void) strlcpy(zc.zc_string, origin,
+ sizeof (zc.zc_string));
+
+ ASSERT3S(drr.drr_type, ==, DRR_BEGIN);
+ zc.zc_begin_record = drr.drr_u.drr_begin;
+ zc.zc_guid = force;
+ zc.zc_cookie = input_fd;
+ zc.zc_cleanup_fd = -1;
+ zc.zc_action_handle = 0;
+
+ zc.zc_nvlist_dst_size = 128 * 1024;
+ zc.zc_nvlist_dst = (uint64_t)(uintptr_t)
+ malloc(zc.zc_nvlist_dst_size);
+
+ error = zfs_ioctl_fd(g_fd, ZFS_IOC_RECV, &zc);
+ if (error != 0) {
+ error = errno;
+ } else {
+ if (read_bytes != NULL)
+ *read_bytes = zc.zc_cookie;
+
+ if (errflags != NULL)
+ *errflags = zc.zc_obj;
+
+ if (errors != NULL)
+ VERIFY0(nvlist_unpack(
+ (void *)(uintptr_t)zc.zc_nvlist_dst,
+ zc.zc_nvlist_dst_size, errors, KM_SLEEP));
+ }
+
+ if (packed != NULL)
+ fnvlist_pack_free(packed, size);
+ free((void *)(uintptr_t)zc.zc_nvlist_dst);
+ }
+
+ return (error);
+}
+
+/*
+ * The simplest receive case: receive from the specified fd, creating the
+ * specified snapshot. Apply the specified properties as "received" properties
+ * (which can be overridden by locally-set properties). If the stream is a
+ * clone, its origin snapshot must be specified by 'origin'. The 'force'
+ * flag will cause the target filesystem to be rolled back or destroyed if
+ * necessary to receive.
+ *
+ * Return 0 on success or an errno on failure.
+ *
+ * Note: this interface does not work on dedup'd streams
+ * (those with DMU_BACKUP_FEATURE_DEDUP).
+ */
+int
+lzc_receive(const char *snapname, nvlist_t *props, const char *origin,
+ boolean_t force, boolean_t raw, int fd)
+{
+ return (recv_impl(snapname, props, NULL, NULL, 0, origin, force,
+ B_FALSE, raw, fd, NULL, NULL, NULL, NULL));
+}
+
+/*
+ * Like lzc_receive, but if the receive fails due to premature stream
+ * termination, the intermediate state will be preserved on disk. In this
+ * case, ECKSUM will be returned. The receive may subsequently be resumed
+ * with a resuming send stream generated by lzc_send_resume().
+ */
+int
+lzc_receive_resumable(const char *snapname, nvlist_t *props, const char *origin,
+ boolean_t force, boolean_t raw, int fd)
+{
+ return (recv_impl(snapname, props, NULL, NULL, 0, origin, force,
+ B_TRUE, raw, fd, NULL, NULL, NULL, NULL));
+}
+
+/*
+ * Like lzc_receive, but allows the caller to read the begin record and then to
+ * pass it in. That could be useful if the caller wants to derive, for example,
+ * the snapname or the origin parameters based on the information contained in
+ * the begin record.
+ * The begin record must be in its original form as read from the stream,
+ * in other words, it should not be byteswapped.
+ *
+ * The 'resumable' parameter allows to obtain the same behavior as with
+ * lzc_receive_resumable.
+ */
+int
+lzc_receive_with_header(const char *snapname, nvlist_t *props,
+ const char *origin, boolean_t force, boolean_t resumable, boolean_t raw,
+ int fd, const dmu_replay_record_t *begin_record)
+{
+ if (begin_record == NULL)
+ return (EINVAL);
+
+ return (recv_impl(snapname, props, NULL, NULL, 0, origin, force,
+ resumable, raw, fd, begin_record, NULL, NULL, NULL));
+}
+
+/*
+ * Like lzc_receive, but allows the caller to pass all supported arguments
+ * and retrieve all values returned. The only additional input parameter
+ * is 'cleanup_fd' which is used to set a cleanup-on-exit file descriptor.
+ *
+ * The following parameters all provide return values. Several may be set
+ * in the failure case and will contain additional information.
+ *
+ * The 'read_bytes' value will be set to the total number of bytes read.
+ *
+ * The 'errflags' value will contain zprop_errflags_t flags which are
+ * used to describe any failures.
+ *
+ * The 'action_handle' and 'cleanup_fd' are no longer used, and are ignored.
+ *
+ * The 'errors' nvlist contains an entry for each unapplied received
+ * property. Callers are responsible for freeing this nvlist.
+ */
+int lzc_receive_one(const char *snapname, nvlist_t *props,
+ const char *origin, boolean_t force, boolean_t resumable, boolean_t raw,
+ int input_fd, const dmu_replay_record_t *begin_record, int cleanup_fd,
+ uint64_t *read_bytes, uint64_t *errflags, uint64_t *action_handle,
+ nvlist_t **errors)
+{
+ return (recv_impl(snapname, props, NULL, NULL, 0, origin, force,
+ resumable, raw, input_fd, begin_record,
+ read_bytes, errflags, errors));
+}
+
+/*
+ * Like lzc_receive_one, but allows the caller to pass an additional 'cmdprops'
+ * argument.
+ *
+ * The 'cmdprops' nvlist contains both override ('zfs receive -o') and
+ * exclude ('zfs receive -x') properties. Callers are responsible for freeing
+ * this nvlist
+ */
+int lzc_receive_with_cmdprops(const char *snapname, nvlist_t *props,
+ nvlist_t *cmdprops, uint8_t *wkeydata, uint_t wkeylen, const char *origin,
+ boolean_t force, boolean_t resumable, boolean_t raw, int input_fd,
+ const dmu_replay_record_t *begin_record, int cleanup_fd,
+ uint64_t *read_bytes, uint64_t *errflags, uint64_t *action_handle,
+ nvlist_t **errors)
+{
+ return (recv_impl(snapname, props, cmdprops, wkeydata, wkeylen, origin,
+ force, resumable, raw, input_fd, begin_record,
+ read_bytes, errflags, errors));
+}
+
+/*
+ * Roll back this filesystem or volume to its most recent snapshot.
+ * If snapnamebuf is not NULL, it will be filled in with the name
+ * of the most recent snapshot.
+ * Note that the latest snapshot may change if a new one is concurrently
+ * created or the current one is destroyed. lzc_rollback_to can be used
+ * to roll back to a specific latest snapshot.
+ *
+ * Return 0 on success or an errno on failure.
+ */
+int
+lzc_rollback(const char *fsname, char *snapnamebuf, int snapnamelen)
+{
+ nvlist_t *args;
+ nvlist_t *result;
+ int err;
+
+ args = fnvlist_alloc();
+ err = lzc_ioctl(ZFS_IOC_ROLLBACK, fsname, args, &result);
+ nvlist_free(args);
+ if (err == 0 && snapnamebuf != NULL) {
+ const char *snapname = fnvlist_lookup_string(result, "target");
+ (void) strlcpy(snapnamebuf, snapname, snapnamelen);
+ }
+ nvlist_free(result);
+
+ return (err);
+}
+
+/*
+ * Roll back this filesystem or volume to the specified snapshot,
+ * if possible.
+ *
+ * Return 0 on success or an errno on failure.
+ */
+int
+lzc_rollback_to(const char *fsname, const char *snapname)
+{
+ nvlist_t *args;
+ nvlist_t *result;
+ int err;
+
+ args = fnvlist_alloc();
+ fnvlist_add_string(args, "target", snapname);
+ err = lzc_ioctl(ZFS_IOC_ROLLBACK, fsname, args, &result);
+ nvlist_free(args);
+ nvlist_free(result);
+ return (err);
+}
+
+/*
+ * Creates new bookmarks from existing snapshot or bookmark.
+ *
+ * The bookmarks nvlist maps from the full name of the new bookmark to
+ * the full name of the source snapshot or bookmark.
+ * All the bookmarks and snapshots must be in the same pool.
+ * The new bookmarks names must be unique.
+ * => see function dsl_bookmark_create_nvl_validate
+ *
+ * The returned results nvlist will have an entry for each bookmark that failed.
+ * The value will be the (int32) error code.
+ *
+ * The return value will be 0 if all bookmarks were created, otherwise it will
+ * be the errno of a (undetermined) bookmarks that failed.
+ */
+int
+lzc_bookmark(nvlist_t *bookmarks, nvlist_t **errlist)
+{
+ nvpair_t *elem;
+ int error;
+ char pool[ZFS_MAX_DATASET_NAME_LEN];
+
+ /* determine pool name from first bookmark */
+ elem = nvlist_next_nvpair(bookmarks, NULL);
+ if (elem == NULL)
+ return (0);
+ (void) strlcpy(pool, nvpair_name(elem), sizeof (pool));
+ pool[strcspn(pool, "/#")] = '\0';
+
+ error = lzc_ioctl(ZFS_IOC_BOOKMARK, pool, bookmarks, errlist);
+
+ return (error);
+}
+
+/*
+ * Retrieve bookmarks.
+ *
+ * Retrieve the list of bookmarks for the given file system. The props
+ * parameter is an nvlist of property names (with no values) that will be
+ * returned for each bookmark.
+ *
+ * The following are valid properties on bookmarks, most of which are numbers
+ * (represented as uint64 in the nvlist), except redact_snaps, which is a
+ * uint64 array, and redact_complete, which is a boolean
+ *
+ * "guid" - globally unique identifier of the snapshot it refers to
+ * "createtxg" - txg when the snapshot it refers to was created
+ * "creation" - timestamp when the snapshot it refers to was created
+ * "ivsetguid" - IVset guid for identifying encrypted snapshots
+ * "redact_snaps" - list of guids of the redaction snapshots for the specified
+ * bookmark. If the bookmark is not a redaction bookmark, the nvlist will
+ * not contain an entry for this value. If it is redacted with respect to
+ * no snapshots, it will contain value -> NULL uint64 array
+ * "redact_complete" - boolean value; true if the redaction bookmark is
+ * complete, false otherwise.
+ *
+ * The format of the returned nvlist as follows:
+ * <short name of bookmark> -> {
+ * <name of property> -> {
+ * "value" -> uint64
+ * }
+ * ...
+ * "redact_snaps" -> {
+ * "value" -> uint64 array
+ * }
+ * "redact_complete" -> {
+ * "value" -> boolean value
+ * }
+ * }
+ */
+int
+lzc_get_bookmarks(const char *fsname, nvlist_t *props, nvlist_t **bmarks)
+{
+ return (lzc_ioctl(ZFS_IOC_GET_BOOKMARKS, fsname, props, bmarks));
+}
+
+/*
+ * Get bookmark properties.
+ *
+ * Given a bookmark's full name, retrieve all properties for the bookmark.
+ *
+ * The format of the returned property list is as follows:
+ * {
+ * <name of property> -> {
+ * "value" -> uint64
+ * }
+ * ...
+ * "redact_snaps" -> {
+ * "value" -> uint64 array
+ * }
+ */
+int
+lzc_get_bookmark_props(const char *bookmark, nvlist_t **props)
+{
+ int error;
+
+ nvlist_t *innvl = fnvlist_alloc();
+ error = lzc_ioctl(ZFS_IOC_GET_BOOKMARK_PROPS, bookmark, innvl, props);
+ fnvlist_free(innvl);
+
+ return (error);
+}
+
+/*
+ * Destroys bookmarks.
+ *
+ * The keys in the bmarks nvlist are the bookmarks to be destroyed.
+ * They must all be in the same pool. Bookmarks are specified as
+ * <fs>#<bmark>.
+ *
+ * Bookmarks that do not exist will be silently ignored.
+ *
+ * The return value will be 0 if all bookmarks that existed were destroyed.
+ *
+ * Otherwise the return value will be the errno of a (undetermined) bookmark
+ * that failed, no bookmarks will be destroyed, and the errlist will have an
+ * entry for each bookmarks that failed. The value in the errlist will be
+ * the (int32) error code.
+ */
+int
+lzc_destroy_bookmarks(nvlist_t *bmarks, nvlist_t **errlist)
+{
+ nvpair_t *elem;
+ int error;
+ char pool[ZFS_MAX_DATASET_NAME_LEN];
+
+ /* determine the pool name */
+ elem = nvlist_next_nvpair(bmarks, NULL);
+ if (elem == NULL)
+ return (0);
+ (void) strlcpy(pool, nvpair_name(elem), sizeof (pool));
+ pool[strcspn(pool, "/#")] = '\0';
+
+ error = lzc_ioctl(ZFS_IOC_DESTROY_BOOKMARKS, pool, bmarks, errlist);
+
+ return (error);
+}
+
+static int
+lzc_channel_program_impl(const char *pool, const char *program, boolean_t sync,
+ uint64_t instrlimit, uint64_t memlimit, nvlist_t *argnvl, nvlist_t **outnvl)
+{
+ int error;
+ nvlist_t *args;
+
+ args = fnvlist_alloc();
+ fnvlist_add_string(args, ZCP_ARG_PROGRAM, program);
+ fnvlist_add_nvlist(args, ZCP_ARG_ARGLIST, argnvl);
+ fnvlist_add_boolean_value(args, ZCP_ARG_SYNC, sync);
+ fnvlist_add_uint64(args, ZCP_ARG_INSTRLIMIT, instrlimit);
+ fnvlist_add_uint64(args, ZCP_ARG_MEMLIMIT, memlimit);
+ error = lzc_ioctl(ZFS_IOC_CHANNEL_PROGRAM, pool, args, outnvl);
+ fnvlist_free(args);
+
+ return (error);
+}
+
+/*
+ * Executes a channel program.
+ *
+ * If this function returns 0 the channel program was successfully loaded and
+ * ran without failing. Note that individual commands the channel program ran
+ * may have failed and the channel program is responsible for reporting such
+ * errors through outnvl if they are important.
+ *
+ * This method may also return:
+ *
+ * EINVAL The program contains syntax errors, or an invalid memory or time
+ * limit was given. No part of the channel program was executed.
+ * If caused by syntax errors, 'outnvl' contains information about the
+ * errors.
+ *
+ * ECHRNG The program was executed, but encountered a runtime error, such as
+ * calling a function with incorrect arguments, invoking the error()
+ * function directly, failing an assert() command, etc. Some portion
+ * of the channel program may have executed and committed changes.
+ * Information about the failure can be found in 'outnvl'.
+ *
+ * ENOMEM The program fully executed, but the output buffer was not large
+ * enough to store the returned value. No output is returned through
+ * 'outnvl'.
+ *
+ * ENOSPC The program was terminated because it exceeded its memory usage
+ * limit. Some portion of the channel program may have executed and
+ * committed changes to disk. No output is returned through 'outnvl'.
+ *
+ * ETIME The program was terminated because it exceeded its Lua instruction
+ * limit. Some portion of the channel program may have executed and
+ * committed changes to disk. No output is returned through 'outnvl'.
+ */
+int
+lzc_channel_program(const char *pool, const char *program, uint64_t instrlimit,
+ uint64_t memlimit, nvlist_t *argnvl, nvlist_t **outnvl)
+{
+ return (lzc_channel_program_impl(pool, program, B_TRUE, instrlimit,
+ memlimit, argnvl, outnvl));
+}
+
+/*
+ * Creates a checkpoint for the specified pool.
+ *
+ * If this function returns 0 the pool was successfully checkpointed.
+ *
+ * This method may also return:
+ *
+ * ZFS_ERR_CHECKPOINT_EXISTS
+ * The pool already has a checkpoint. A pools can only have one
+ * checkpoint at most, at any given time.
+ *
+ * ZFS_ERR_DISCARDING_CHECKPOINT
+ * ZFS is in the middle of discarding a checkpoint for this pool.
+ * The pool can be checkpointed again once the discard is done.
+ *
+ * ZFS_DEVRM_IN_PROGRESS
+ * A vdev is currently being removed. The pool cannot be
+ * checkpointed until the device removal is done.
+ *
+ * ZFS_VDEV_TOO_BIG
+ * One or more top-level vdevs exceed the maximum vdev size
+ * supported for this feature.
+ */
+int
+lzc_pool_checkpoint(const char *pool)
+{
+ int error;
+
+ nvlist_t *result = NULL;
+ nvlist_t *args = fnvlist_alloc();
+
+ error = lzc_ioctl(ZFS_IOC_POOL_CHECKPOINT, pool, args, &result);
+
+ fnvlist_free(args);
+ fnvlist_free(result);
+
+ return (error);
+}
+
+/*
+ * Discard the checkpoint from the specified pool.
+ *
+ * If this function returns 0 the checkpoint was successfully discarded.
+ *
+ * This method may also return:
+ *
+ * ZFS_ERR_NO_CHECKPOINT
+ * The pool does not have a checkpoint.
+ *
+ * ZFS_ERR_DISCARDING_CHECKPOINT
+ * ZFS is already in the middle of discarding the checkpoint.
+ */
+int
+lzc_pool_checkpoint_discard(const char *pool)
+{
+ int error;
+
+ nvlist_t *result = NULL;
+ nvlist_t *args = fnvlist_alloc();
+
+ error = lzc_ioctl(ZFS_IOC_POOL_DISCARD_CHECKPOINT, pool, args, &result);
+
+ fnvlist_free(args);
+ fnvlist_free(result);
+
+ return (error);
+}
+
+/*
+ * Executes a read-only channel program.
+ *
+ * A read-only channel program works programmatically the same way as a
+ * normal channel program executed with lzc_channel_program(). The only
+ * difference is it runs exclusively in open-context and therefore can
+ * return faster. The downside to that, is that the program cannot change
+ * on-disk state by calling functions from the zfs.sync submodule.
+ *
+ * The return values of this function (and their meaning) are exactly the
+ * same as the ones described in lzc_channel_program().
+ */
+int
+lzc_channel_program_nosync(const char *pool, const char *program,
+ uint64_t timeout, uint64_t memlimit, nvlist_t *argnvl, nvlist_t **outnvl)
+{
+ return (lzc_channel_program_impl(pool, program, B_FALSE, timeout,
+ memlimit, argnvl, outnvl));
+}
+
+/*
+ * Performs key management functions
+ *
+ * crypto_cmd should be a value from dcp_cmd_t. If the command specifies to
+ * load or change a wrapping key, the key should be specified in the
+ * hidden_args nvlist so that it is not logged.
+ */
+int
+lzc_load_key(const char *fsname, boolean_t noop, uint8_t *wkeydata,
+ uint_t wkeylen)
+{
+ int error;
+ nvlist_t *ioc_args;
+ nvlist_t *hidden_args;
+
+ if (wkeydata == NULL)
+ return (EINVAL);
+
+ ioc_args = fnvlist_alloc();
+ hidden_args = fnvlist_alloc();
+ fnvlist_add_uint8_array(hidden_args, "wkeydata", wkeydata, wkeylen);
+ fnvlist_add_nvlist(ioc_args, ZPOOL_HIDDEN_ARGS, hidden_args);
+ if (noop)
+ fnvlist_add_boolean(ioc_args, "noop");
+ error = lzc_ioctl(ZFS_IOC_LOAD_KEY, fsname, ioc_args, NULL);
+ nvlist_free(hidden_args);
+ nvlist_free(ioc_args);
+
+ return (error);
+}
+
+int
+lzc_unload_key(const char *fsname)
+{
+ return (lzc_ioctl(ZFS_IOC_UNLOAD_KEY, fsname, NULL, NULL));
+}
+
+int
+lzc_change_key(const char *fsname, uint64_t crypt_cmd, nvlist_t *props,
+ uint8_t *wkeydata, uint_t wkeylen)
+{
+ int error;
+ nvlist_t *ioc_args = fnvlist_alloc();
+ nvlist_t *hidden_args = NULL;
+
+ fnvlist_add_uint64(ioc_args, "crypt_cmd", crypt_cmd);
+
+ if (wkeydata != NULL) {
+ hidden_args = fnvlist_alloc();
+ fnvlist_add_uint8_array(hidden_args, "wkeydata", wkeydata,
+ wkeylen);
+ fnvlist_add_nvlist(ioc_args, ZPOOL_HIDDEN_ARGS, hidden_args);
+ }
+
+ if (props != NULL)
+ fnvlist_add_nvlist(ioc_args, "props", props);
+
+ error = lzc_ioctl(ZFS_IOC_CHANGE_KEY, fsname, ioc_args, NULL);
+ nvlist_free(hidden_args);
+ nvlist_free(ioc_args);
+
+ return (error);
+}
+
+int
+lzc_reopen(const char *pool_name, boolean_t scrub_restart)
+{
+ nvlist_t *args = fnvlist_alloc();
+ int error;
+
+ fnvlist_add_boolean_value(args, "scrub_restart", scrub_restart);
+
+ error = lzc_ioctl(ZFS_IOC_POOL_REOPEN, pool_name, args, NULL);
+ nvlist_free(args);
+ return (error);
+}
+
+/*
+ * Changes initializing state.
+ *
+ * vdevs should be a list of (<key>, guid) where guid is a uint64 vdev GUID.
+ * The key is ignored.
+ *
+ * If there are errors related to vdev arguments, per-vdev errors are returned
+ * in an nvlist with the key "vdevs". Each error is a (guid, errno) pair where
+ * guid is stringified with PRIu64, and errno is one of the following as
+ * an int64_t:
+ * - ENODEV if the device was not found
+ * - EINVAL if the devices is not a leaf or is not concrete (e.g. missing)
+ * - EROFS if the device is not writeable
+ * - EBUSY start requested but the device is already being either
+ * initialized or trimmed
+ * - ESRCH cancel/suspend requested but device is not being initialized
+ *
+ * If the errlist is empty, then return value will be:
+ * - EINVAL if one or more arguments was invalid
+ * - Other spa_open failures
+ * - 0 if the operation succeeded
+ */
+int
+lzc_initialize(const char *poolname, pool_initialize_func_t cmd_type,
+ nvlist_t *vdevs, nvlist_t **errlist)
+{
+ int error;
+
+ nvlist_t *args = fnvlist_alloc();
+ fnvlist_add_uint64(args, ZPOOL_INITIALIZE_COMMAND, (uint64_t)cmd_type);
+ fnvlist_add_nvlist(args, ZPOOL_INITIALIZE_VDEVS, vdevs);
+
+ error = lzc_ioctl(ZFS_IOC_POOL_INITIALIZE, poolname, args, errlist);
+
+ fnvlist_free(args);
+
+ return (error);
+}
+
+/*
+ * Changes TRIM state.
+ *
+ * vdevs should be a list of (<key>, guid) where guid is a uint64 vdev GUID.
+ * The key is ignored.
+ *
+ * If there are errors related to vdev arguments, per-vdev errors are returned
+ * in an nvlist with the key "vdevs". Each error is a (guid, errno) pair where
+ * guid is stringified with PRIu64, and errno is one of the following as
+ * an int64_t:
+ * - ENODEV if the device was not found
+ * - EINVAL if the devices is not a leaf or is not concrete (e.g. missing)
+ * - EROFS if the device is not writeable
+ * - EBUSY start requested but the device is already being either trimmed
+ * or initialized
+ * - ESRCH cancel/suspend requested but device is not being initialized
+ * - EOPNOTSUPP if the device does not support TRIM (or secure TRIM)
+ *
+ * If the errlist is empty, then return value will be:
+ * - EINVAL if one or more arguments was invalid
+ * - Other spa_open failures
+ * - 0 if the operation succeeded
+ */
+int
+lzc_trim(const char *poolname, pool_trim_func_t cmd_type, uint64_t rate,
+ boolean_t secure, nvlist_t *vdevs, nvlist_t **errlist)
+{
+ int error;
+
+ nvlist_t *args = fnvlist_alloc();
+ fnvlist_add_uint64(args, ZPOOL_TRIM_COMMAND, (uint64_t)cmd_type);
+ fnvlist_add_nvlist(args, ZPOOL_TRIM_VDEVS, vdevs);
+ fnvlist_add_uint64(args, ZPOOL_TRIM_RATE, rate);
+ fnvlist_add_boolean_value(args, ZPOOL_TRIM_SECURE, secure);
+
+ error = lzc_ioctl(ZFS_IOC_POOL_TRIM, poolname, args, errlist);
+
+ fnvlist_free(args);
+
+ return (error);
+}
+
+/*
+ * Create a redaction bookmark named bookname by redacting snapshot with respect
+ * to all the snapshots in snapnv.
+ */
+int
+lzc_redact(const char *snapshot, const char *bookname, nvlist_t *snapnv)
+{
+ nvlist_t *args = fnvlist_alloc();
+ fnvlist_add_string(args, "bookname", bookname);
+ fnvlist_add_nvlist(args, "snapnv", snapnv);
+ int error = lzc_ioctl(ZFS_IOC_REDACT, snapshot, args, NULL);
+ fnvlist_free(args);
+ return (error);
+}
+
+static int
+wait_common(const char *pool, zpool_wait_activity_t activity, boolean_t use_tag,
+ uint64_t tag, boolean_t *waited)
+{
+ nvlist_t *args = fnvlist_alloc();
+ nvlist_t *result = NULL;
+
+ fnvlist_add_int32(args, ZPOOL_WAIT_ACTIVITY, activity);
+ if (use_tag)
+ fnvlist_add_uint64(args, ZPOOL_WAIT_TAG, tag);
+
+ int error = lzc_ioctl(ZFS_IOC_WAIT, pool, args, &result);
+
+ if (error == 0 && waited != NULL)
+ *waited = fnvlist_lookup_boolean_value(result,
+ ZPOOL_WAIT_WAITED);
+
+ fnvlist_free(args);
+ fnvlist_free(result);
+
+ return (error);
+}
+
+int
+lzc_wait(const char *pool, zpool_wait_activity_t activity, boolean_t *waited)
+{
+ return (wait_common(pool, activity, B_FALSE, 0, waited));
+}
+
+int
+lzc_wait_tag(const char *pool, zpool_wait_activity_t activity, uint64_t tag,
+ boolean_t *waited)
+{
+ return (wait_common(pool, activity, B_TRUE, tag, waited));
+}
+
+int
+lzc_wait_fs(const char *fs, zfs_wait_activity_t activity, boolean_t *waited)
+{
+ nvlist_t *args = fnvlist_alloc();
+ nvlist_t *result = NULL;
+
+ fnvlist_add_int32(args, ZFS_WAIT_ACTIVITY, activity);
+
+ int error = lzc_ioctl(ZFS_IOC_WAIT_FS, fs, args, &result);
+
+ if (error == 0 && waited != NULL)
+ *waited = fnvlist_lookup_boolean_value(result,
+ ZFS_WAIT_WAITED);
+
+ fnvlist_free(args);
+ fnvlist_free(result);
+
+ return (error);
+}
+
+/*
+ * Set the bootenv contents for the given pool.
+ */
+int
+lzc_set_bootenv(const char *pool, const char *env)
+{
+ nvlist_t *args = fnvlist_alloc();
+ fnvlist_add_string(args, "envmap", env);
+ int error = lzc_ioctl(ZFS_IOC_SET_BOOTENV, pool, args, NULL);
+ fnvlist_free(args);
+ return (error);
+}
+
+/*
+ * Get the contents of the bootenv of the given pool.
+ */
+int
+lzc_get_bootenv(const char *pool, nvlist_t **outnvl)
+{
+ return (lzc_ioctl(ZFS_IOC_GET_BOOTENV, pool, NULL, outnvl));
+}
diff --git a/lib/libzfs_core/libzfs_core.pc.in b/lib/libzfs_core/libzfs_core.pc.in
new file mode 100644
index 000000000000..e14d42d11a5d
--- /dev/null
+++ b/lib/libzfs_core/libzfs_core.pc.in
@@ -0,0 +1,13 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: libzfs_core
+Description: LibZFS core library
+Version: @VERSION@
+URL: https://zfsonlinux.org
+Requires.private: blkid uuid libtirpc zlib
+Cflags: -I${includedir}/libzfs -I${includedir}/libspl
+Libs: -L${libdir} -lzfs_core -lnvpair
+Libs.private: @LIBCLOCK_GETTIME@ @LIBUDEV_LIBS@ -lm -pthread
diff --git a/lib/libzpool/Makefile.am b/lib/libzpool/Makefile.am
new file mode 100644
index 000000000000..9c1b81bf5373
--- /dev/null
+++ b/lib/libzpool/Makefile.am
@@ -0,0 +1,230 @@
+include $(top_srcdir)/config/Rules.am
+
+VPATH = \
+ $(top_srcdir)/module/zfs \
+ $(top_srcdir)/module/zcommon \
+ $(top_srcdir)/module/lua \
+ $(top_srcdir)/module/os/linux/zfs \
+ $(top_srcdir)/lib/libzpool
+
+# Unconditionally enable debugging for libzpool
+AM_CPPFLAGS += -DDEBUG -UNDEBUG -DZFS_DEBUG
+
+# Suppress unused but set variable warnings often due to ASSERTs
+AM_CFLAGS += $(NO_UNUSED_BUT_SET_VARIABLE)
+
+# Includes kernel code generate warnings for large stack frames
+AM_CFLAGS += $(FRAME_LARGER_THAN)
+
+AM_CFLAGS += $(ZLIB_CFLAGS)
+
+AM_CFLAGS += -DLIB_ZPOOL_BUILD
+
+lib_LTLIBRARIES = libzpool.la
+
+USER_C = \
+ kernel.c \
+ taskq.c \
+ util.c
+
+KERNEL_C = \
+ zfeature_common.c \
+ zfs_comutil.c \
+ zfs_deleg.c \
+ zfs_fletcher.c \
+ zfs_fletcher_aarch64_neon.c \
+ zfs_fletcher_avx512.c \
+ zfs_fletcher_intel.c \
+ zfs_fletcher_sse.c \
+ zfs_fletcher_superscalar.c \
+ zfs_fletcher_superscalar4.c \
+ zfs_namecheck.c \
+ zfs_prop.c \
+ zfs_uio.c \
+ zpool_prop.c \
+ zprop_common.c \
+ abd.c \
+ abd_os.c \
+ aggsum.c \
+ arc.c \
+ arc_os.c \
+ blkptr.c \
+ bplist.c \
+ bpobj.c \
+ bptree.c \
+ btree.c \
+ bqueue.c \
+ cityhash.c \
+ dbuf.c \
+ dbuf_stats.c \
+ ddt.c \
+ ddt_zap.c \
+ dmu.c \
+ dmu_diff.c \
+ dmu_object.c \
+ dmu_objset.c \
+ dmu_recv.c \
+ dmu_redact.c \
+ dmu_send.c \
+ dmu_traverse.c \
+ dmu_tx.c \
+ dmu_zfetch.c \
+ dnode.c \
+ dnode_sync.c \
+ dsl_bookmark.c \
+ dsl_dataset.c \
+ dsl_deadlist.c \
+ dsl_deleg.c \
+ dsl_dir.c \
+ dsl_crypt.c \
+ dsl_pool.c \
+ dsl_prop.c \
+ dsl_scan.c \
+ dsl_synctask.c \
+ dsl_destroy.c \
+ dsl_userhold.c \
+ edonr_zfs.c \
+ hkdf.c \
+ fm.c \
+ gzip.c \
+ lzjb.c \
+ lz4.c \
+ metaslab.c \
+ mmp.c \
+ multilist.c \
+ objlist.c \
+ pathname.c \
+ range_tree.c \
+ refcount.c \
+ rrwlock.c \
+ sa.c \
+ sha256.c \
+ skein_zfs.c \
+ spa.c \
+ spa_boot.c \
+ spa_checkpoint.c \
+ spa_config.c \
+ spa_errlog.c \
+ spa_history.c \
+ spa_log_spacemap.c \
+ spa_misc.c \
+ spa_stats.c \
+ space_map.c \
+ space_reftree.c \
+ txg.c \
+ trace.c \
+ uberblock.c \
+ unique.c \
+ vdev.c \
+ vdev_cache.c \
+ vdev_file.c \
+ vdev_indirect_births.c \
+ vdev_indirect.c \
+ vdev_indirect_mapping.c \
+ vdev_initialize.c \
+ vdev_label.c \
+ vdev_mirror.c \
+ vdev_missing.c \
+ vdev_queue.c \
+ vdev_raidz.c \
+ vdev_raidz_math_aarch64_neon.c \
+ vdev_raidz_math_aarch64_neonx2.c \
+ vdev_raidz_math_avx2.c \
+ vdev_raidz_math_avx512bw.c \
+ vdev_raidz_math_avx512f.c \
+ vdev_raidz_math.c \
+ vdev_raidz_math_scalar.c \
+ vdev_raidz_math_sse2.c \
+ vdev_raidz_math_ssse3.c \
+ vdev_raidz_math_powerpc_altivec.c \
+ vdev_rebuild.c \
+ vdev_removal.c \
+ vdev_root.c \
+ vdev_trim.c \
+ zap.c \
+ zap_leaf.c \
+ zap_micro.c \
+ zcp.c \
+ zcp_get.c \
+ zcp_global.c \
+ zcp_iter.c \
+ zcp_set.c \
+ zcp_synctask.c \
+ zfeature.c \
+ zfs_byteswap.c \
+ zfs_debug.c \
+ zfs_fm.c \
+ zfs_fuid.c \
+ zfs_sa.c \
+ zfs_znode.c \
+ zfs_ratelimit.c \
+ zfs_rlock.c \
+ zil.c \
+ zio.c \
+ zio_checksum.c \
+ zio_compress.c \
+ zio_crypt.c \
+ zio_inject.c \
+ zle.c \
+ zrlock.c \
+ zthr.c
+
+LUA_C = \
+ lapi.c \
+ lauxlib.c \
+ lbaselib.c \
+ lcode.c \
+ lcompat.c \
+ lcorolib.c \
+ lctype.c \
+ ldebug.c \
+ ldo.c \
+ lfunc.c \
+ lgc.c \
+ llex.c \
+ lmem.c \
+ lobject.c \
+ lopcodes.c \
+ lparser.c \
+ lstate.c \
+ lstring.c \
+ lstrlib.c \
+ ltable.c \
+ ltablib.c \
+ ltm.c \
+ lvm.c \
+ lzio.c
+
+dist_libzpool_la_SOURCES = \
+ $(USER_C)
+
+nodist_libzpool_la_SOURCES = \
+ $(KERNEL_C) \
+ $(LUA_C)
+
+libzpool_la_LIBADD = \
+ $(abs_top_builddir)/lib/libicp/libicp.la \
+ $(abs_top_builddir)/lib/libunicode/libunicode.la \
+ $(abs_top_builddir)/lib/libzfs_core/libzfs_core.la \
+ $(abs_top_builddir)/lib/libnvpair/libnvpair.la \
+ $(abs_top_builddir)/lib/libzstd/libzstd.la
+
+libzpool_la_LIBADD += $(LIBCLOCK_GETTIME) $(ZLIB_LIBS) -ldl
+
+libzpool_la_LDFLAGS = -pthread
+
+if !ASAN_ENABLED
+libzpool_la_LDFLAGS += -Wl,-z,defs
+endif
+
+if BUILD_FREEBSD
+libzpool_la_LIBADD += -lgeom
+libzpool_la_LDFLAGS += -version-info 4:0:0
+else
+libzpool_la_LDFLAGS += -version-info 2:0:0
+endif
+
+if TARGET_CPU_POWERPC
+vdev_raidz_math_powerpc_altivec.$(OBJEXT): CFLAGS += -maltivec
+vdev_raidz_math_powerpc_altivec.l$(OBJEXT): CFLAGS += -maltivec
+endif
diff --git a/lib/libzpool/kernel.c b/lib/libzpool/kernel.c
new file mode 100644
index 000000000000..cba1d242b843
--- /dev/null
+++ b/lib/libzpool/kernel.c
@@ -0,0 +1,1410 @@
+/*
+ * 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) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2018 by Delphix. All rights reserved.
+ * Copyright (c) 2016 Actifio, Inc. All rights reserved.
+ */
+
+#include <assert.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <poll.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/crypto/icp.h>
+#include <sys/processor.h>
+#include <sys/rrwlock.h>
+#include <sys/spa.h>
+#include <sys/stat.h>
+#include <sys/systeminfo.h>
+#include <sys/time.h>
+#include <sys/utsname.h>
+#include <sys/zfs_context.h>
+#include <sys/zfs_onexit.h>
+#include <sys/zstd/zstd.h>
+#include <sys/zvol.h>
+#include <zfs_fletcher.h>
+#include <zlib.h>
+
+/*
+ * Emulation of kernel services in userland.
+ */
+
+uint64_t physmem;
+char hw_serial[HW_HOSTID_LEN];
+struct utsname hw_utsname;
+
+/* If set, all blocks read will be copied to the specified directory. */
+char *vn_dumpdir = NULL;
+
+/* this only exists to have its address taken */
+struct proc p0;
+
+/*
+ * =========================================================================
+ * threads
+ * =========================================================================
+ *
+ * TS_STACK_MIN is dictated by the minimum allowed pthread stack size. While
+ * TS_STACK_MAX is somewhat arbitrary, it was selected to be large enough for
+ * the expected stack depth while small enough to avoid exhausting address
+ * space with high thread counts.
+ */
+#define TS_STACK_MIN MAX(PTHREAD_STACK_MIN, 32768)
+#define TS_STACK_MAX (256 * 1024)
+
+/*ARGSUSED*/
+kthread_t *
+zk_thread_create(void (*func)(void *), void *arg, size_t stksize, int state)
+{
+ pthread_attr_t attr;
+ pthread_t tid;
+ char *stkstr;
+ int detachstate = PTHREAD_CREATE_DETACHED;
+
+ VERIFY0(pthread_attr_init(&attr));
+
+ if (state & TS_JOINABLE)
+ detachstate = PTHREAD_CREATE_JOINABLE;
+
+ VERIFY0(pthread_attr_setdetachstate(&attr, detachstate));
+
+ /*
+ * We allow the default stack size in user space to be specified by
+ * setting the ZFS_STACK_SIZE environment variable. This allows us
+ * the convenience of observing and debugging stack overruns in
+ * user space. Explicitly specified stack sizes will be honored.
+ * The usage of ZFS_STACK_SIZE is discussed further in the
+ * ENVIRONMENT VARIABLES sections of the ztest(1) man page.
+ */
+ if (stksize == 0) {
+ stkstr = getenv("ZFS_STACK_SIZE");
+
+ if (stkstr == NULL)
+ stksize = TS_STACK_MAX;
+ else
+ stksize = MAX(atoi(stkstr), TS_STACK_MIN);
+ }
+
+ VERIFY3S(stksize, >, 0);
+ stksize = P2ROUNDUP(MAX(stksize, TS_STACK_MIN), PAGESIZE);
+
+ /*
+ * If this ever fails, it may be because the stack size is not a
+ * multiple of system page size.
+ */
+ VERIFY0(pthread_attr_setstacksize(&attr, stksize));
+ VERIFY0(pthread_attr_setguardsize(&attr, PAGESIZE));
+
+ VERIFY0(pthread_create(&tid, &attr, (void *(*)(void *))func, arg));
+ VERIFY0(pthread_attr_destroy(&attr));
+
+ return ((void *)(uintptr_t)tid);
+}
+
+/*
+ * =========================================================================
+ * kstats
+ * =========================================================================
+ */
+/*ARGSUSED*/
+kstat_t *
+kstat_create(const char *module, int instance, const char *name,
+ const char *class, uchar_t type, ulong_t ndata, uchar_t ks_flag)
+{
+ return (NULL);
+}
+
+/*ARGSUSED*/
+void
+kstat_install(kstat_t *ksp)
+{}
+
+/*ARGSUSED*/
+void
+kstat_delete(kstat_t *ksp)
+{}
+
+/*ARGSUSED*/
+void
+kstat_waitq_enter(kstat_io_t *kiop)
+{}
+
+/*ARGSUSED*/
+void
+kstat_waitq_exit(kstat_io_t *kiop)
+{}
+
+/*ARGSUSED*/
+void
+kstat_runq_enter(kstat_io_t *kiop)
+{}
+
+/*ARGSUSED*/
+void
+kstat_runq_exit(kstat_io_t *kiop)
+{}
+
+/*ARGSUSED*/
+void
+kstat_waitq_to_runq(kstat_io_t *kiop)
+{}
+
+/*ARGSUSED*/
+void
+kstat_runq_back_to_waitq(kstat_io_t *kiop)
+{}
+
+void
+kstat_set_raw_ops(kstat_t *ksp,
+ int (*headers)(char *buf, size_t size),
+ int (*data)(char *buf, size_t size, void *data),
+ void *(*addr)(kstat_t *ksp, loff_t index))
+{}
+
+/*
+ * =========================================================================
+ * mutexes
+ * =========================================================================
+ */
+
+void
+mutex_init(kmutex_t *mp, char *name, int type, void *cookie)
+{
+ VERIFY0(pthread_mutex_init(&mp->m_lock, NULL));
+ memset(&mp->m_owner, 0, sizeof (pthread_t));
+}
+
+void
+mutex_destroy(kmutex_t *mp)
+{
+ VERIFY0(pthread_mutex_destroy(&mp->m_lock));
+}
+
+void
+mutex_enter(kmutex_t *mp)
+{
+ VERIFY0(pthread_mutex_lock(&mp->m_lock));
+ mp->m_owner = pthread_self();
+}
+
+int
+mutex_tryenter(kmutex_t *mp)
+{
+ int error;
+
+ error = pthread_mutex_trylock(&mp->m_lock);
+ if (error == 0) {
+ mp->m_owner = pthread_self();
+ return (1);
+ } else {
+ VERIFY3S(error, ==, EBUSY);
+ return (0);
+ }
+}
+
+void
+mutex_exit(kmutex_t *mp)
+{
+ memset(&mp->m_owner, 0, sizeof (pthread_t));
+ VERIFY0(pthread_mutex_unlock(&mp->m_lock));
+}
+
+/*
+ * =========================================================================
+ * rwlocks
+ * =========================================================================
+ */
+
+void
+rw_init(krwlock_t *rwlp, char *name, int type, void *arg)
+{
+ VERIFY0(pthread_rwlock_init(&rwlp->rw_lock, NULL));
+ rwlp->rw_readers = 0;
+ rwlp->rw_owner = 0;
+}
+
+void
+rw_destroy(krwlock_t *rwlp)
+{
+ VERIFY0(pthread_rwlock_destroy(&rwlp->rw_lock));
+}
+
+void
+rw_enter(krwlock_t *rwlp, krw_t rw)
+{
+ if (rw == RW_READER) {
+ VERIFY0(pthread_rwlock_rdlock(&rwlp->rw_lock));
+ atomic_inc_uint(&rwlp->rw_readers);
+ } else {
+ VERIFY0(pthread_rwlock_wrlock(&rwlp->rw_lock));
+ rwlp->rw_owner = pthread_self();
+ }
+}
+
+void
+rw_exit(krwlock_t *rwlp)
+{
+ if (RW_READ_HELD(rwlp))
+ atomic_dec_uint(&rwlp->rw_readers);
+ else
+ rwlp->rw_owner = 0;
+
+ VERIFY0(pthread_rwlock_unlock(&rwlp->rw_lock));
+}
+
+int
+rw_tryenter(krwlock_t *rwlp, krw_t rw)
+{
+ int error;
+
+ if (rw == RW_READER)
+ error = pthread_rwlock_tryrdlock(&rwlp->rw_lock);
+ else
+ error = pthread_rwlock_trywrlock(&rwlp->rw_lock);
+
+ if (error == 0) {
+ if (rw == RW_READER)
+ atomic_inc_uint(&rwlp->rw_readers);
+ else
+ rwlp->rw_owner = pthread_self();
+
+ return (1);
+ }
+
+ VERIFY3S(error, ==, EBUSY);
+
+ return (0);
+}
+
+/* ARGSUSED */
+uint32_t
+zone_get_hostid(void *zonep)
+{
+ /*
+ * We're emulating the system's hostid in userland.
+ */
+ return (strtoul(hw_serial, NULL, 10));
+}
+
+int
+rw_tryupgrade(krwlock_t *rwlp)
+{
+ return (0);
+}
+
+/*
+ * =========================================================================
+ * condition variables
+ * =========================================================================
+ */
+
+void
+cv_init(kcondvar_t *cv, char *name, int type, void *arg)
+{
+ VERIFY0(pthread_cond_init(cv, NULL));
+}
+
+void
+cv_destroy(kcondvar_t *cv)
+{
+ VERIFY0(pthread_cond_destroy(cv));
+}
+
+void
+cv_wait(kcondvar_t *cv, kmutex_t *mp)
+{
+ memset(&mp->m_owner, 0, sizeof (pthread_t));
+ VERIFY0(pthread_cond_wait(cv, &mp->m_lock));
+ mp->m_owner = pthread_self();
+}
+
+int
+cv_wait_sig(kcondvar_t *cv, kmutex_t *mp)
+{
+ cv_wait(cv, mp);
+ return (1);
+}
+
+int
+cv_timedwait(kcondvar_t *cv, kmutex_t *mp, clock_t abstime)
+{
+ int error;
+ struct timeval tv;
+ struct timespec ts;
+ clock_t delta;
+
+ delta = abstime - ddi_get_lbolt();
+ if (delta <= 0)
+ return (-1);
+
+ VERIFY(gettimeofday(&tv, NULL) == 0);
+
+ ts.tv_sec = tv.tv_sec + delta / hz;
+ ts.tv_nsec = tv.tv_usec * NSEC_PER_USEC + (delta % hz) * (NANOSEC / hz);
+ if (ts.tv_nsec >= NANOSEC) {
+ ts.tv_sec++;
+ ts.tv_nsec -= NANOSEC;
+ }
+
+ memset(&mp->m_owner, 0, sizeof (pthread_t));
+ error = pthread_cond_timedwait(cv, &mp->m_lock, &ts);
+ mp->m_owner = pthread_self();
+
+ if (error == ETIMEDOUT)
+ return (-1);
+
+ VERIFY0(error);
+
+ return (1);
+}
+
+/*ARGSUSED*/
+int
+cv_timedwait_hires(kcondvar_t *cv, kmutex_t *mp, hrtime_t tim, hrtime_t res,
+ int flag)
+{
+ int error;
+ struct timeval tv;
+ struct timespec ts;
+ hrtime_t delta;
+
+ ASSERT(flag == 0 || flag == CALLOUT_FLAG_ABSOLUTE);
+
+ delta = tim;
+ if (flag & CALLOUT_FLAG_ABSOLUTE)
+ delta -= gethrtime();
+
+ if (delta <= 0)
+ return (-1);
+
+ VERIFY0(gettimeofday(&tv, NULL));
+
+ ts.tv_sec = tv.tv_sec + delta / NANOSEC;
+ ts.tv_nsec = tv.tv_usec * NSEC_PER_USEC + (delta % NANOSEC);
+ if (ts.tv_nsec >= NANOSEC) {
+ ts.tv_sec++;
+ ts.tv_nsec -= NANOSEC;
+ }
+
+ memset(&mp->m_owner, 0, sizeof (pthread_t));
+ error = pthread_cond_timedwait(cv, &mp->m_lock, &ts);
+ mp->m_owner = pthread_self();
+
+ if (error == ETIMEDOUT)
+ return (-1);
+
+ VERIFY0(error);
+
+ return (1);
+}
+
+void
+cv_signal(kcondvar_t *cv)
+{
+ VERIFY0(pthread_cond_signal(cv));
+}
+
+void
+cv_broadcast(kcondvar_t *cv)
+{
+ VERIFY0(pthread_cond_broadcast(cv));
+}
+
+/*
+ * =========================================================================
+ * procfs list
+ * =========================================================================
+ */
+
+void
+seq_printf(struct seq_file *m, const char *fmt, ...)
+{}
+
+void
+procfs_list_install(const char *module,
+ const char *name,
+ mode_t mode,
+ procfs_list_t *procfs_list,
+ int (*show)(struct seq_file *f, void *p),
+ int (*show_header)(struct seq_file *f),
+ int (*clear)(procfs_list_t *procfs_list),
+ size_t procfs_list_node_off)
+{
+ mutex_init(&procfs_list->pl_lock, NULL, MUTEX_DEFAULT, NULL);
+ list_create(&procfs_list->pl_list,
+ procfs_list_node_off + sizeof (procfs_list_node_t),
+ procfs_list_node_off + offsetof(procfs_list_node_t, pln_link));
+ procfs_list->pl_next_id = 1;
+ procfs_list->pl_node_offset = procfs_list_node_off;
+}
+
+void
+procfs_list_uninstall(procfs_list_t *procfs_list)
+{}
+
+void
+procfs_list_destroy(procfs_list_t *procfs_list)
+{
+ ASSERT(list_is_empty(&procfs_list->pl_list));
+ list_destroy(&procfs_list->pl_list);
+ mutex_destroy(&procfs_list->pl_lock);
+}
+
+#define NODE_ID(procfs_list, obj) \
+ (((procfs_list_node_t *)(((char *)obj) + \
+ (procfs_list)->pl_node_offset))->pln_id)
+
+void
+procfs_list_add(procfs_list_t *procfs_list, void *p)
+{
+ ASSERT(MUTEX_HELD(&procfs_list->pl_lock));
+ NODE_ID(procfs_list, p) = procfs_list->pl_next_id++;
+ list_insert_tail(&procfs_list->pl_list, p);
+}
+
+/*
+ * =========================================================================
+ * vnode operations
+ * =========================================================================
+ */
+
+/*
+ * =========================================================================
+ * Figure out which debugging statements to print
+ * =========================================================================
+ */
+
+static char *dprintf_string;
+static int dprintf_print_all;
+
+int
+dprintf_find_string(const char *string)
+{
+ char *tmp_str = dprintf_string;
+ int len = strlen(string);
+
+ /*
+ * Find out if this is a string we want to print.
+ * String format: file1.c,function_name1,file2.c,file3.c
+ */
+
+ while (tmp_str != NULL) {
+ if (strncmp(tmp_str, string, len) == 0 &&
+ (tmp_str[len] == ',' || tmp_str[len] == '\0'))
+ return (1);
+ tmp_str = strchr(tmp_str, ',');
+ if (tmp_str != NULL)
+ tmp_str++; /* Get rid of , */
+ }
+ return (0);
+}
+
+void
+dprintf_setup(int *argc, char **argv)
+{
+ int i, j;
+
+ /*
+ * Debugging can be specified two ways: by setting the
+ * environment variable ZFS_DEBUG, or by including a
+ * "debug=..." argument on the command line. The command
+ * line setting overrides the environment variable.
+ */
+
+ for (i = 1; i < *argc; i++) {
+ int len = strlen("debug=");
+ /* First look for a command line argument */
+ if (strncmp("debug=", argv[i], len) == 0) {
+ dprintf_string = argv[i] + len;
+ /* Remove from args */
+ for (j = i; j < *argc; j++)
+ argv[j] = argv[j+1];
+ argv[j] = NULL;
+ (*argc)--;
+ }
+ }
+
+ if (dprintf_string == NULL) {
+ /* Look for ZFS_DEBUG environment variable */
+ dprintf_string = getenv("ZFS_DEBUG");
+ }
+
+ /*
+ * Are we just turning on all debugging?
+ */
+ if (dprintf_find_string("on"))
+ dprintf_print_all = 1;
+
+ if (dprintf_string != NULL)
+ zfs_flags |= ZFS_DEBUG_DPRINTF;
+}
+
+/*
+ * =========================================================================
+ * debug printfs
+ * =========================================================================
+ */
+void
+__dprintf(boolean_t dprint, const char *file, const char *func,
+ int line, const char *fmt, ...)
+{
+ const char *newfile;
+ va_list adx;
+
+ /*
+ * Get rid of annoying "../common/" prefix to filename.
+ */
+ newfile = strrchr(file, '/');
+ if (newfile != NULL) {
+ newfile = newfile + 1; /* Get rid of leading / */
+ } else {
+ newfile = file;
+ }
+
+ if (dprint) {
+ /* dprintf messages are printed immediately */
+
+ if (!dprintf_print_all &&
+ !dprintf_find_string(newfile) &&
+ !dprintf_find_string(func))
+ return;
+
+ /* Print out just the function name if requested */
+ flockfile(stdout);
+ if (dprintf_find_string("pid"))
+ (void) printf("%d ", getpid());
+ if (dprintf_find_string("tid"))
+ (void) printf("%ju ",
+ (uintmax_t)(uintptr_t)pthread_self());
+ if (dprintf_find_string("cpu"))
+ (void) printf("%u ", getcpuid());
+ if (dprintf_find_string("time"))
+ (void) printf("%llu ", gethrtime());
+ if (dprintf_find_string("long"))
+ (void) printf("%s, line %d: ", newfile, line);
+ (void) printf("dprintf: %s: ", func);
+ va_start(adx, fmt);
+ (void) vprintf(fmt, adx);
+ va_end(adx);
+ funlockfile(stdout);
+ } else {
+ /* zfs_dbgmsg is logged for dumping later */
+ size_t size;
+ char *buf;
+ int i;
+
+ size = 1024;
+ buf = umem_alloc(size, UMEM_NOFAIL);
+ i = snprintf(buf, size, "%s:%d:%s(): ", newfile, line, func);
+
+ if (i < size) {
+ va_start(adx, fmt);
+ (void) vsnprintf(buf + i, size - i, fmt, adx);
+ va_end(adx);
+ }
+
+ __zfs_dbgmsg(buf);
+
+ umem_free(buf, size);
+ }
+}
+
+/*
+ * =========================================================================
+ * cmn_err() and panic()
+ * =========================================================================
+ */
+static char ce_prefix[CE_IGNORE][10] = { "", "NOTICE: ", "WARNING: ", "" };
+static char ce_suffix[CE_IGNORE][2] = { "", "\n", "\n", "" };
+
+void
+vpanic(const char *fmt, va_list adx)
+{
+ (void) fprintf(stderr, "error: ");
+ (void) vfprintf(stderr, fmt, adx);
+ (void) fprintf(stderr, "\n");
+
+ abort(); /* think of it as a "user-level crash dump" */
+}
+
+void
+panic(const char *fmt, ...)
+{
+ va_list adx;
+
+ va_start(adx, fmt);
+ vpanic(fmt, adx);
+ va_end(adx);
+}
+
+void
+vcmn_err(int ce, const char *fmt, va_list adx)
+{
+ if (ce == CE_PANIC)
+ vpanic(fmt, adx);
+ if (ce != CE_NOTE) { /* suppress noise in userland stress testing */
+ (void) fprintf(stderr, "%s", ce_prefix[ce]);
+ (void) vfprintf(stderr, fmt, adx);
+ (void) fprintf(stderr, "%s", ce_suffix[ce]);
+ }
+}
+
+/*PRINTFLIKE2*/
+void
+cmn_err(int ce, const char *fmt, ...)
+{
+ va_list adx;
+
+ va_start(adx, fmt);
+ vcmn_err(ce, fmt, adx);
+ va_end(adx);
+}
+
+/*
+ * =========================================================================
+ * misc routines
+ * =========================================================================
+ */
+
+void
+delay(clock_t ticks)
+{
+ (void) poll(0, 0, ticks * (1000 / hz));
+}
+
+/*
+ * Find highest one bit set.
+ * Returns bit number + 1 of highest bit that is set, otherwise returns 0.
+ * The __builtin_clzll() function is supported by both GCC and Clang.
+ */
+int
+highbit64(uint64_t i)
+{
+ if (i == 0)
+ return (0);
+
+ return (NBBY * sizeof (uint64_t) - __builtin_clzll(i));
+}
+
+/*
+ * Find lowest one bit set.
+ * Returns bit number + 1 of lowest bit that is set, otherwise returns 0.
+ * The __builtin_ffsll() function is supported by both GCC and Clang.
+ */
+int
+lowbit64(uint64_t i)
+{
+ if (i == 0)
+ return (0);
+
+ return (__builtin_ffsll(i));
+}
+
+char *random_path = "/dev/random";
+char *urandom_path = "/dev/urandom";
+static int random_fd = -1, urandom_fd = -1;
+
+void
+random_init(void)
+{
+ VERIFY((random_fd = open(random_path, O_RDONLY)) != -1);
+ VERIFY((urandom_fd = open(urandom_path, O_RDONLY)) != -1);
+}
+
+void
+random_fini(void)
+{
+ close(random_fd);
+ close(urandom_fd);
+
+ random_fd = -1;
+ urandom_fd = -1;
+}
+
+static int
+random_get_bytes_common(uint8_t *ptr, size_t len, int fd)
+{
+ size_t resid = len;
+ ssize_t bytes;
+
+ ASSERT(fd != -1);
+
+ while (resid != 0) {
+ bytes = read(fd, ptr, resid);
+ ASSERT3S(bytes, >=, 0);
+ ptr += bytes;
+ resid -= bytes;
+ }
+
+ return (0);
+}
+
+int
+random_get_bytes(uint8_t *ptr, size_t len)
+{
+ return (random_get_bytes_common(ptr, len, random_fd));
+}
+
+int
+random_get_pseudo_bytes(uint8_t *ptr, size_t len)
+{
+ return (random_get_bytes_common(ptr, len, urandom_fd));
+}
+
+int
+ddi_strtoul(const char *hw_serial, char **nptr, int base, unsigned long *result)
+{
+ char *end;
+
+ *result = strtoul(hw_serial, &end, base);
+ if (*result == 0)
+ return (errno);
+ return (0);
+}
+
+int
+ddi_strtoull(const char *str, char **nptr, int base, u_longlong_t *result)
+{
+ char *end;
+
+ *result = strtoull(str, &end, base);
+ if (*result == 0)
+ return (errno);
+ return (0);
+}
+
+utsname_t *
+utsname(void)
+{
+ return (&hw_utsname);
+}
+
+/*
+ * =========================================================================
+ * kernel emulation setup & teardown
+ * =========================================================================
+ */
+static int
+umem_out_of_memory(void)
+{
+ char errmsg[] = "out of memory -- generating core dump\n";
+
+ (void) fprintf(stderr, "%s", errmsg);
+ abort();
+ return (0);
+}
+
+void
+kernel_init(int mode)
+{
+ extern uint_t rrw_tsd_key;
+
+ umem_nofail_callback(umem_out_of_memory);
+
+ physmem = sysconf(_SC_PHYS_PAGES);
+
+ dprintf("physmem = %llu pages (%.2f GB)\n", physmem,
+ (double)physmem * sysconf(_SC_PAGE_SIZE) / (1ULL << 30));
+
+ (void) snprintf(hw_serial, sizeof (hw_serial), "%ld",
+ (mode & SPA_MODE_WRITE) ? get_system_hostid() : 0);
+
+ random_init();
+
+ VERIFY0(uname(&hw_utsname));
+
+ system_taskq_init();
+ icp_init();
+
+ zstd_init();
+
+ spa_init((spa_mode_t)mode);
+
+ fletcher_4_init();
+
+ tsd_create(&rrw_tsd_key, rrw_tsd_destroy);
+}
+
+void
+kernel_fini(void)
+{
+ fletcher_4_fini();
+ spa_fini();
+
+ zstd_fini();
+
+ icp_fini();
+ system_taskq_fini();
+
+ random_fini();
+}
+
+uid_t
+crgetuid(cred_t *cr)
+{
+ return (0);
+}
+
+uid_t
+crgetruid(cred_t *cr)
+{
+ return (0);
+}
+
+gid_t
+crgetgid(cred_t *cr)
+{
+ return (0);
+}
+
+int
+crgetngroups(cred_t *cr)
+{
+ return (0);
+}
+
+gid_t *
+crgetgroups(cred_t *cr)
+{
+ return (NULL);
+}
+
+int
+zfs_secpolicy_snapshot_perms(const char *name, cred_t *cr)
+{
+ return (0);
+}
+
+int
+zfs_secpolicy_rename_perms(const char *from, const char *to, cred_t *cr)
+{
+ return (0);
+}
+
+int
+zfs_secpolicy_destroy_perms(const char *name, cred_t *cr)
+{
+ return (0);
+}
+
+int
+secpolicy_zfs(const cred_t *cr)
+{
+ return (0);
+}
+
+int
+secpolicy_zfs_proc(const cred_t *cr, proc_t *proc)
+{
+ return (0);
+}
+
+ksiddomain_t *
+ksid_lookupdomain(const char *dom)
+{
+ ksiddomain_t *kd;
+
+ kd = umem_zalloc(sizeof (ksiddomain_t), UMEM_NOFAIL);
+ kd->kd_name = spa_strdup(dom);
+ return (kd);
+}
+
+void
+ksiddomain_rele(ksiddomain_t *ksid)
+{
+ spa_strfree(ksid->kd_name);
+ umem_free(ksid, sizeof (ksiddomain_t));
+}
+
+char *
+kmem_vasprintf(const char *fmt, va_list adx)
+{
+ char *buf = NULL;
+ va_list adx_copy;
+
+ va_copy(adx_copy, adx);
+ VERIFY(vasprintf(&buf, fmt, adx_copy) != -1);
+ va_end(adx_copy);
+
+ return (buf);
+}
+
+char *
+kmem_asprintf(const char *fmt, ...)
+{
+ char *buf = NULL;
+ va_list adx;
+
+ va_start(adx, fmt);
+ VERIFY(vasprintf(&buf, fmt, adx) != -1);
+ va_end(adx);
+
+ return (buf);
+}
+
+/* ARGSUSED */
+int
+zfs_onexit_fd_hold(int fd, minor_t *minorp)
+{
+ *minorp = 0;
+ return (0);
+}
+
+/* ARGSUSED */
+void
+zfs_onexit_fd_rele(int fd)
+{
+}
+
+/* ARGSUSED */
+int
+zfs_onexit_add_cb(minor_t minor, void (*func)(void *), void *data,
+ uint64_t *action_handle)
+{
+ return (0);
+}
+
+fstrans_cookie_t
+spl_fstrans_mark(void)
+{
+ return ((fstrans_cookie_t)0);
+}
+
+void
+spl_fstrans_unmark(fstrans_cookie_t cookie)
+{
+}
+
+int
+__spl_pf_fstrans_check(void)
+{
+ return (0);
+}
+
+int
+kmem_cache_reap_active(void)
+{
+ return (0);
+}
+
+void *zvol_tag = "zvol_tag";
+
+void
+zvol_create_minor(const char *name)
+{
+}
+
+void
+zvol_create_minors_recursive(const char *name)
+{
+}
+
+void
+zvol_remove_minors(spa_t *spa, const char *name, boolean_t async)
+{
+}
+
+void
+zvol_rename_minors(spa_t *spa, const char *oldname, const char *newname,
+ boolean_t async)
+{
+}
+
+/*
+ * Open file
+ *
+ * path - fully qualified path to file
+ * flags - file attributes O_READ / O_WRITE / O_EXCL
+ * fpp - pointer to return file pointer
+ *
+ * Returns 0 on success underlying error on failure.
+ */
+int
+zfs_file_open(const char *path, int flags, int mode, zfs_file_t **fpp)
+{
+ int fd = -1;
+ int dump_fd = -1;
+ int err;
+ int old_umask = 0;
+ zfs_file_t *fp;
+ struct stat64 st;
+
+ if (!(flags & O_CREAT) && stat64(path, &st) == -1)
+ return (errno);
+
+ if (!(flags & O_CREAT) && S_ISBLK(st.st_mode))
+ flags |= O_DIRECT;
+
+ if (flags & O_CREAT)
+ old_umask = umask(0);
+
+ fd = open64(path, flags, mode);
+ if (fd == -1)
+ return (errno);
+
+ if (flags & O_CREAT)
+ (void) umask(old_umask);
+
+ if (vn_dumpdir != NULL) {
+ char *dumppath = umem_zalloc(MAXPATHLEN, UMEM_NOFAIL);
+ char *inpath = basename((char *)(uintptr_t)path);
+
+ (void) snprintf(dumppath, MAXPATHLEN,
+ "%s/%s", vn_dumpdir, inpath);
+ dump_fd = open64(dumppath, O_CREAT | O_WRONLY, 0666);
+ umem_free(dumppath, MAXPATHLEN);
+ if (dump_fd == -1) {
+ err = errno;
+ close(fd);
+ return (err);
+ }
+ } else {
+ dump_fd = -1;
+ }
+
+ (void) fcntl(fd, F_SETFD, FD_CLOEXEC);
+
+ fp = umem_zalloc(sizeof (zfs_file_t), UMEM_NOFAIL);
+ fp->f_fd = fd;
+ fp->f_dump_fd = dump_fd;
+ *fpp = fp;
+
+ return (0);
+}
+
+void
+zfs_file_close(zfs_file_t *fp)
+{
+ close(fp->f_fd);
+ if (fp->f_dump_fd != -1)
+ close(fp->f_dump_fd);
+
+ umem_free(fp, sizeof (zfs_file_t));
+}
+
+/*
+ * Stateful write - use os internal file pointer to determine where to
+ * write and update on successful completion.
+ *
+ * fp - pointer to file (pipe, socket, etc) to write to
+ * buf - buffer to write
+ * count - # of bytes to write
+ * resid - pointer to count of unwritten bytes (if short write)
+ *
+ * Returns 0 on success errno on failure.
+ */
+int
+zfs_file_write(zfs_file_t *fp, const void *buf, size_t count, ssize_t *resid)
+{
+ ssize_t rc;
+
+ rc = write(fp->f_fd, buf, count);
+ if (rc < 0)
+ return (errno);
+
+ if (resid) {
+ *resid = count - rc;
+ } else if (rc != count) {
+ return (EIO);
+ }
+
+ return (0);
+}
+
+/*
+ * Stateless write - os internal file pointer is not updated.
+ *
+ * fp - pointer to file (pipe, socket, etc) to write to
+ * buf - buffer to write
+ * count - # of bytes to write
+ * off - file offset to write to (only valid for seekable types)
+ * resid - pointer to count of unwritten bytes
+ *
+ * Returns 0 on success errno on failure.
+ */
+int
+zfs_file_pwrite(zfs_file_t *fp, const void *buf,
+ size_t count, loff_t pos, ssize_t *resid)
+{
+ ssize_t rc, split, done;
+ int sectors;
+
+ /*
+ * To simulate partial disk writes, we split writes into two
+ * system calls so that the process can be killed in between.
+ * This is used by ztest to simulate realistic failure modes.
+ */
+ sectors = count >> SPA_MINBLOCKSHIFT;
+ split = (sectors > 0 ? rand() % sectors : 0) << SPA_MINBLOCKSHIFT;
+ rc = pwrite64(fp->f_fd, buf, split, pos);
+ if (rc != -1) {
+ done = rc;
+ rc = pwrite64(fp->f_fd, (char *)buf + split,
+ count - split, pos + split);
+ }
+#ifdef __linux__
+ if (rc == -1 && errno == EINVAL) {
+ /*
+ * Under Linux, this most likely means an alignment issue
+ * (memory or disk) due to O_DIRECT, so we abort() in order
+ * to catch the offender.
+ */
+ abort();
+ }
+#endif
+
+ if (rc < 0)
+ return (errno);
+
+ done += rc;
+
+ if (resid) {
+ *resid = count - done;
+ } else if (done != count) {
+ return (EIO);
+ }
+
+ return (0);
+}
+
+/*
+ * Stateful read - use os internal file pointer to determine where to
+ * read and update on successful completion.
+ *
+ * fp - pointer to file (pipe, socket, etc) to read from
+ * buf - buffer to write
+ * count - # of bytes to read
+ * resid - pointer to count of unread bytes (if short read)
+ *
+ * Returns 0 on success errno on failure.
+ */
+int
+zfs_file_read(zfs_file_t *fp, void *buf, size_t count, ssize_t *resid)
+{
+ int rc;
+
+ rc = read(fp->f_fd, buf, count);
+ if (rc < 0)
+ return (errno);
+
+ if (resid) {
+ *resid = count - rc;
+ } else if (rc != count) {
+ return (EIO);
+ }
+
+ return (0);
+}
+
+/*
+ * Stateless read - os internal file pointer is not updated.
+ *
+ * fp - pointer to file (pipe, socket, etc) to read from
+ * buf - buffer to write
+ * count - # of bytes to write
+ * off - file offset to read from (only valid for seekable types)
+ * resid - pointer to count of unwritten bytes (if short write)
+ *
+ * Returns 0 on success errno on failure.
+ */
+int
+zfs_file_pread(zfs_file_t *fp, void *buf, size_t count, loff_t off,
+ ssize_t *resid)
+{
+ ssize_t rc;
+
+ rc = pread64(fp->f_fd, buf, count, off);
+ if (rc < 0) {
+#ifdef __linux__
+ /*
+ * Under Linux, this most likely means an alignment issue
+ * (memory or disk) due to O_DIRECT, so we abort() in order to
+ * catch the offender.
+ */
+ if (errno == EINVAL)
+ abort();
+#endif
+ return (errno);
+ }
+
+ if (fp->f_dump_fd != -1) {
+ int status;
+
+ status = pwrite64(fp->f_dump_fd, buf, rc, off);
+ ASSERT(status != -1);
+ }
+
+ if (resid) {
+ *resid = count - rc;
+ } else if (rc != count) {
+ return (EIO);
+ }
+
+ return (0);
+}
+
+/*
+ * lseek - set / get file pointer
+ *
+ * fp - pointer to file (pipe, socket, etc) to read from
+ * offp - value to seek to, returns current value plus passed offset
+ * whence - see man pages for standard lseek whence values
+ *
+ * Returns 0 on success errno on failure (ESPIPE for non seekable types)
+ */
+int
+zfs_file_seek(zfs_file_t *fp, loff_t *offp, int whence)
+{
+ loff_t rc;
+
+ rc = lseek(fp->f_fd, *offp, whence);
+ if (rc < 0)
+ return (errno);
+
+ *offp = rc;
+
+ return (0);
+}
+
+/*
+ * Get file attributes
+ *
+ * filp - file pointer
+ * zfattr - pointer to file attr structure
+ *
+ * Currently only used for fetching size and file mode
+ *
+ * Returns 0 on success or error code of underlying getattr call on failure.
+ */
+int
+zfs_file_getattr(zfs_file_t *fp, zfs_file_attr_t *zfattr)
+{
+ struct stat64 st;
+
+ if (fstat64_blk(fp->f_fd, &st) == -1)
+ return (errno);
+
+ zfattr->zfa_size = st.st_size;
+ zfattr->zfa_mode = st.st_mode;
+
+ return (0);
+}
+
+/*
+ * Sync file to disk
+ *
+ * filp - file pointer
+ * flags - O_SYNC and or O_DSYNC
+ *
+ * Returns 0 on success or error code of underlying sync call on failure.
+ */
+int
+zfs_file_fsync(zfs_file_t *fp, int flags)
+{
+ int rc;
+
+ rc = fsync(fp->f_fd);
+ if (rc < 0)
+ return (errno);
+
+ return (0);
+}
+
+/*
+ * fallocate - allocate or free space on disk
+ *
+ * fp - file pointer
+ * mode (non-standard options for hole punching etc)
+ * offset - offset to start allocating or freeing from
+ * len - length to free / allocate
+ *
+ * OPTIONAL
+ */
+int
+zfs_file_fallocate(zfs_file_t *fp, int mode, loff_t offset, loff_t len)
+{
+#ifdef __linux__
+ return (fallocate(fp->f_fd, mode, offset, len));
+#else
+ return (EOPNOTSUPP);
+#endif
+}
+
+/*
+ * Request current file pointer offset
+ *
+ * fp - pointer to file
+ *
+ * Returns current file offset.
+ */
+loff_t
+zfs_file_off(zfs_file_t *fp)
+{
+ return (lseek(fp->f_fd, SEEK_CUR, 0));
+}
+
+/*
+ * unlink file
+ *
+ * path - fully qualified file path
+ *
+ * Returns 0 on success.
+ *
+ * OPTIONAL
+ */
+int
+zfs_file_unlink(const char *path)
+{
+ return (remove(path));
+}
+
+/*
+ * Get reference to file pointer
+ *
+ * fd - input file descriptor
+ * fpp - pointer to file pointer
+ *
+ * Returns 0 on success EBADF on failure.
+ * Unsupported in user space.
+ */
+int
+zfs_file_get(int fd, zfs_file_t **fpp)
+{
+ abort();
+
+ return (EOPNOTSUPP);
+}
+
+/*
+ * Drop reference to file pointer
+ *
+ * fd - input file descriptor
+ *
+ * Unsupported in user space.
+ */
+void
+zfs_file_put(int fd)
+{
+ abort();
+}
diff --git a/lib/libzpool/taskq.c b/lib/libzpool/taskq.c
new file mode 100644
index 000000000000..456080f7f247
--- /dev/null
+++ b/lib/libzpool/taskq.c
@@ -0,0 +1,380 @@
+/*
+ * 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 2010 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+/*
+ * Copyright 2011 Nexenta Systems, Inc. All rights reserved.
+ * Copyright 2012 Garrett D'Amore <garrett@damore.org>. All rights reserved.
+ * Copyright (c) 2014 by Delphix. All rights reserved.
+ */
+
+#include <sys/zfs_context.h>
+
+int taskq_now;
+taskq_t *system_taskq;
+taskq_t *system_delay_taskq;
+
+static pthread_key_t taskq_tsd;
+
+#define TASKQ_ACTIVE 0x00010000
+
+static taskq_ent_t *
+task_alloc(taskq_t *tq, int tqflags)
+{
+ taskq_ent_t *t;
+ int rv;
+
+again: if ((t = tq->tq_freelist) != NULL && tq->tq_nalloc >= tq->tq_minalloc) {
+ ASSERT(!(t->tqent_flags & TQENT_FLAG_PREALLOC));
+ tq->tq_freelist = t->tqent_next;
+ } else {
+ if (tq->tq_nalloc >= tq->tq_maxalloc) {
+ if (!(tqflags & KM_SLEEP))
+ return (NULL);
+
+ /*
+ * We don't want to exceed tq_maxalloc, but we can't
+ * wait for other tasks to complete (and thus free up
+ * task structures) without risking deadlock with
+ * the caller. So, we just delay for one second
+ * to throttle the allocation rate. If we have tasks
+ * complete before one second timeout expires then
+ * taskq_ent_free will signal us and we will
+ * immediately retry the allocation.
+ */
+ tq->tq_maxalloc_wait++;
+ rv = cv_timedwait(&tq->tq_maxalloc_cv,
+ &tq->tq_lock, ddi_get_lbolt() + hz);
+ tq->tq_maxalloc_wait--;
+ if (rv > 0)
+ goto again; /* signaled */
+ }
+ mutex_exit(&tq->tq_lock);
+
+ t = kmem_alloc(sizeof (taskq_ent_t), tqflags);
+
+ mutex_enter(&tq->tq_lock);
+ if (t != NULL) {
+ /* Make sure we start without any flags */
+ t->tqent_flags = 0;
+ tq->tq_nalloc++;
+ }
+ }
+ return (t);
+}
+
+static void
+task_free(taskq_t *tq, taskq_ent_t *t)
+{
+ if (tq->tq_nalloc <= tq->tq_minalloc) {
+ t->tqent_next = tq->tq_freelist;
+ tq->tq_freelist = t;
+ } else {
+ tq->tq_nalloc--;
+ mutex_exit(&tq->tq_lock);
+ kmem_free(t, sizeof (taskq_ent_t));
+ mutex_enter(&tq->tq_lock);
+ }
+
+ if (tq->tq_maxalloc_wait)
+ cv_signal(&tq->tq_maxalloc_cv);
+}
+
+taskqid_t
+taskq_dispatch(taskq_t *tq, task_func_t func, void *arg, uint_t tqflags)
+{
+ taskq_ent_t *t;
+
+ if (taskq_now) {
+ func(arg);
+ return (1);
+ }
+
+ mutex_enter(&tq->tq_lock);
+ ASSERT(tq->tq_flags & TASKQ_ACTIVE);
+ if ((t = task_alloc(tq, tqflags)) == NULL) {
+ mutex_exit(&tq->tq_lock);
+ return (0);
+ }
+ if (tqflags & TQ_FRONT) {
+ t->tqent_next = tq->tq_task.tqent_next;
+ t->tqent_prev = &tq->tq_task;
+ } else {
+ t->tqent_next = &tq->tq_task;
+ t->tqent_prev = tq->tq_task.tqent_prev;
+ }
+ t->tqent_next->tqent_prev = t;
+ t->tqent_prev->tqent_next = t;
+ t->tqent_func = func;
+ t->tqent_arg = arg;
+ t->tqent_flags = 0;
+ cv_signal(&tq->tq_dispatch_cv);
+ mutex_exit(&tq->tq_lock);
+ return (1);
+}
+
+taskqid_t
+taskq_dispatch_delay(taskq_t *tq, task_func_t func, void *arg, uint_t tqflags,
+ clock_t expire_time)
+{
+ return (0);
+}
+
+int
+taskq_empty_ent(taskq_ent_t *t)
+{
+ return (t->tqent_next == NULL);
+}
+
+void
+taskq_init_ent(taskq_ent_t *t)
+{
+ t->tqent_next = NULL;
+ t->tqent_prev = NULL;
+ t->tqent_func = NULL;
+ t->tqent_arg = NULL;
+ t->tqent_flags = 0;
+}
+
+void
+taskq_dispatch_ent(taskq_t *tq, task_func_t func, void *arg, uint_t flags,
+ taskq_ent_t *t)
+{
+ ASSERT(func != NULL);
+
+ /*
+ * Mark it as a prealloc'd task. This is important
+ * to ensure that we don't free it later.
+ */
+ t->tqent_flags |= TQENT_FLAG_PREALLOC;
+ /*
+ * Enqueue the task to the underlying queue.
+ */
+ mutex_enter(&tq->tq_lock);
+
+ if (flags & TQ_FRONT) {
+ t->tqent_next = tq->tq_task.tqent_next;
+ t->tqent_prev = &tq->tq_task;
+ } else {
+ t->tqent_next = &tq->tq_task;
+ t->tqent_prev = tq->tq_task.tqent_prev;
+ }
+ t->tqent_next->tqent_prev = t;
+ t->tqent_prev->tqent_next = t;
+ t->tqent_func = func;
+ t->tqent_arg = arg;
+ cv_signal(&tq->tq_dispatch_cv);
+ mutex_exit(&tq->tq_lock);
+}
+
+void
+taskq_wait(taskq_t *tq)
+{
+ mutex_enter(&tq->tq_lock);
+ while (tq->tq_task.tqent_next != &tq->tq_task || tq->tq_active != 0)
+ cv_wait(&tq->tq_wait_cv, &tq->tq_lock);
+ mutex_exit(&tq->tq_lock);
+}
+
+void
+taskq_wait_id(taskq_t *tq, taskqid_t id)
+{
+ taskq_wait(tq);
+}
+
+void
+taskq_wait_outstanding(taskq_t *tq, taskqid_t id)
+{
+ taskq_wait(tq);
+}
+
+static void
+taskq_thread(void *arg)
+{
+ taskq_t *tq = arg;
+ taskq_ent_t *t;
+ boolean_t prealloc;
+
+ VERIFY0(pthread_setspecific(taskq_tsd, tq));
+
+ mutex_enter(&tq->tq_lock);
+ while (tq->tq_flags & TASKQ_ACTIVE) {
+ if ((t = tq->tq_task.tqent_next) == &tq->tq_task) {
+ if (--tq->tq_active == 0)
+ cv_broadcast(&tq->tq_wait_cv);
+ cv_wait(&tq->tq_dispatch_cv, &tq->tq_lock);
+ tq->tq_active++;
+ continue;
+ }
+ t->tqent_prev->tqent_next = t->tqent_next;
+ t->tqent_next->tqent_prev = t->tqent_prev;
+ t->tqent_next = NULL;
+ t->tqent_prev = NULL;
+ prealloc = t->tqent_flags & TQENT_FLAG_PREALLOC;
+ mutex_exit(&tq->tq_lock);
+
+ rw_enter(&tq->tq_threadlock, RW_READER);
+ t->tqent_func(t->tqent_arg);
+ rw_exit(&tq->tq_threadlock);
+
+ mutex_enter(&tq->tq_lock);
+ if (!prealloc)
+ task_free(tq, t);
+ }
+ tq->tq_nthreads--;
+ cv_broadcast(&tq->tq_wait_cv);
+ mutex_exit(&tq->tq_lock);
+ thread_exit();
+}
+
+/*ARGSUSED*/
+taskq_t *
+taskq_create(const char *name, int nthreads, pri_t pri,
+ int minalloc, int maxalloc, uint_t flags)
+{
+ taskq_t *tq = kmem_zalloc(sizeof (taskq_t), KM_SLEEP);
+ int t;
+
+ if (flags & TASKQ_THREADS_CPU_PCT) {
+ int pct;
+ ASSERT3S(nthreads, >=, 0);
+ ASSERT3S(nthreads, <=, 100);
+ pct = MIN(nthreads, 100);
+ pct = MAX(pct, 0);
+
+ nthreads = (sysconf(_SC_NPROCESSORS_ONLN) * pct) / 100;
+ nthreads = MAX(nthreads, 1); /* need at least 1 thread */
+ } else {
+ ASSERT3S(nthreads, >=, 1);
+ }
+
+ rw_init(&tq->tq_threadlock, NULL, RW_DEFAULT, NULL);
+ mutex_init(&tq->tq_lock, NULL, MUTEX_DEFAULT, NULL);
+ cv_init(&tq->tq_dispatch_cv, NULL, CV_DEFAULT, NULL);
+ cv_init(&tq->tq_wait_cv, NULL, CV_DEFAULT, NULL);
+ cv_init(&tq->tq_maxalloc_cv, NULL, CV_DEFAULT, NULL);
+ (void) strncpy(tq->tq_name, name, TASKQ_NAMELEN);
+ tq->tq_flags = flags | TASKQ_ACTIVE;
+ tq->tq_active = nthreads;
+ tq->tq_nthreads = nthreads;
+ tq->tq_minalloc = minalloc;
+ tq->tq_maxalloc = maxalloc;
+ tq->tq_task.tqent_next = &tq->tq_task;
+ tq->tq_task.tqent_prev = &tq->tq_task;
+ tq->tq_threadlist = kmem_alloc(nthreads * sizeof (kthread_t *),
+ KM_SLEEP);
+
+ if (flags & TASKQ_PREPOPULATE) {
+ mutex_enter(&tq->tq_lock);
+ while (minalloc-- > 0)
+ task_free(tq, task_alloc(tq, KM_SLEEP));
+ mutex_exit(&tq->tq_lock);
+ }
+
+ for (t = 0; t < nthreads; t++)
+ VERIFY((tq->tq_threadlist[t] = thread_create(NULL, 0,
+ taskq_thread, tq, 0, &p0, TS_RUN, pri)) != NULL);
+
+ return (tq);
+}
+
+void
+taskq_destroy(taskq_t *tq)
+{
+ int nthreads = tq->tq_nthreads;
+
+ taskq_wait(tq);
+
+ mutex_enter(&tq->tq_lock);
+
+ tq->tq_flags &= ~TASKQ_ACTIVE;
+ cv_broadcast(&tq->tq_dispatch_cv);
+
+ while (tq->tq_nthreads != 0)
+ cv_wait(&tq->tq_wait_cv, &tq->tq_lock);
+
+ tq->tq_minalloc = 0;
+ while (tq->tq_nalloc != 0) {
+ ASSERT(tq->tq_freelist != NULL);
+ task_free(tq, task_alloc(tq, KM_SLEEP));
+ }
+
+ mutex_exit(&tq->tq_lock);
+
+ kmem_free(tq->tq_threadlist, nthreads * sizeof (kthread_t *));
+
+ rw_destroy(&tq->tq_threadlock);
+ mutex_destroy(&tq->tq_lock);
+ cv_destroy(&tq->tq_dispatch_cv);
+ cv_destroy(&tq->tq_wait_cv);
+ cv_destroy(&tq->tq_maxalloc_cv);
+
+ kmem_free(tq, sizeof (taskq_t));
+}
+
+int
+taskq_member(taskq_t *tq, kthread_t *t)
+{
+ int i;
+
+ if (taskq_now)
+ return (1);
+
+ for (i = 0; i < tq->tq_nthreads; i++)
+ if (tq->tq_threadlist[i] == t)
+ return (1);
+
+ return (0);
+}
+
+taskq_t *
+taskq_of_curthread(void)
+{
+ return (pthread_getspecific(taskq_tsd));
+}
+
+int
+taskq_cancel_id(taskq_t *tq, taskqid_t id)
+{
+ return (ENOENT);
+}
+
+void
+system_taskq_init(void)
+{
+ VERIFY0(pthread_key_create(&taskq_tsd, NULL));
+ system_taskq = taskq_create("system_taskq", 64, maxclsyspri, 4, 512,
+ TASKQ_DYNAMIC | TASKQ_PREPOPULATE);
+ system_delay_taskq = taskq_create("delay_taskq", 4, maxclsyspri, 4,
+ 512, TASKQ_DYNAMIC | TASKQ_PREPOPULATE);
+}
+
+void
+system_taskq_fini(void)
+{
+ taskq_destroy(system_taskq);
+ system_taskq = NULL; /* defensive */
+ taskq_destroy(system_delay_taskq);
+ system_delay_taskq = NULL;
+ VERIFY0(pthread_key_delete(taskq_tsd));
+}
diff --git a/lib/libzpool/util.c b/lib/libzpool/util.c
new file mode 100644
index 000000000000..ebfaa9b41a2a
--- /dev/null
+++ b/lib/libzpool/util.c
@@ -0,0 +1,257 @@
+/*
+ * 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) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016 by Delphix. All rights reserved.
+ * Copyright 2017 Jason King
+ * Copyright (c) 2017, Intel Corporation.
+ */
+
+#include <assert.h>
+#include <sys/zfs_context.h>
+#include <sys/avl.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/spa.h>
+#include <sys/fs/zfs.h>
+#include <sys/zfs_refcount.h>
+#include <sys/zfs_ioctl.h>
+#include <dlfcn.h>
+#include <libzutil.h>
+
+/*
+ * Routines needed by more than one client of libzpool.
+ */
+
+static void
+show_vdev_stats(const char *desc, const char *ctype, nvlist_t *nv, int indent)
+{
+ vdev_stat_t *vs;
+ vdev_stat_t *v0 = { 0 };
+ uint64_t sec;
+ uint64_t is_log = 0;
+ nvlist_t **child;
+ uint_t c, children;
+ char used[6], avail[6];
+ char rops[6], wops[6], rbytes[6], wbytes[6], rerr[6], werr[6], cerr[6];
+
+ v0 = umem_zalloc(sizeof (*v0), UMEM_NOFAIL);
+
+ if (indent == 0 && desc != NULL) {
+ (void) printf(" "
+ " capacity operations bandwidth ---- errors ----\n");
+ (void) printf("description "
+ "used avail read write read write read write cksum\n");
+ }
+
+ if (desc != NULL) {
+ char *suffix = "", *bias = NULL;
+ char bias_suffix[32];
+
+ (void) nvlist_lookup_uint64(nv, ZPOOL_CONFIG_IS_LOG, &is_log);
+ (void) nvlist_lookup_string(nv, ZPOOL_CONFIG_ALLOCATION_BIAS,
+ &bias);
+ if (nvlist_lookup_uint64_array(nv, ZPOOL_CONFIG_VDEV_STATS,
+ (uint64_t **)&vs, &c) != 0)
+ vs = v0;
+
+ if (bias != NULL) {
+ (void) snprintf(bias_suffix, sizeof (bias_suffix),
+ " (%s)", bias);
+ suffix = bias_suffix;
+ } else if (is_log) {
+ suffix = " (log)";
+ }
+
+ sec = MAX(1, vs->vs_timestamp / NANOSEC);
+
+ nicenum(vs->vs_alloc, used, sizeof (used));
+ nicenum(vs->vs_space - vs->vs_alloc, avail, sizeof (avail));
+ nicenum(vs->vs_ops[ZIO_TYPE_READ] / sec, rops, sizeof (rops));
+ nicenum(vs->vs_ops[ZIO_TYPE_WRITE] / sec, wops, sizeof (wops));
+ nicenum(vs->vs_bytes[ZIO_TYPE_READ] / sec, rbytes,
+ sizeof (rbytes));
+ nicenum(vs->vs_bytes[ZIO_TYPE_WRITE] / sec, wbytes,
+ sizeof (wbytes));
+ nicenum(vs->vs_read_errors, rerr, sizeof (rerr));
+ nicenum(vs->vs_write_errors, werr, sizeof (werr));
+ nicenum(vs->vs_checksum_errors, cerr, sizeof (cerr));
+
+ (void) printf("%*s%s%*s%*s%*s %5s %5s %5s %5s %5s %5s %5s\n",
+ indent, "",
+ desc,
+ (int)(indent+strlen(desc)-25-(vs->vs_space ? 0 : 12)),
+ suffix,
+ vs->vs_space ? 6 : 0, vs->vs_space ? used : "",
+ vs->vs_space ? 6 : 0, vs->vs_space ? avail : "",
+ rops, wops, rbytes, wbytes, rerr, werr, cerr);
+ }
+ umem_free(v0, sizeof (*v0));
+
+ if (nvlist_lookup_nvlist_array(nv, ctype, &child, &children) != 0)
+ return;
+
+ for (c = 0; c < children; c++) {
+ nvlist_t *cnv = child[c];
+ char *cname = NULL, *tname;
+ uint64_t np;
+ int len;
+ if (nvlist_lookup_string(cnv, ZPOOL_CONFIG_PATH, &cname) &&
+ nvlist_lookup_string(cnv, ZPOOL_CONFIG_TYPE, &cname))
+ cname = "<unknown>";
+ len = strlen(cname) + 2;
+ tname = umem_zalloc(len, UMEM_NOFAIL);
+ (void) strlcpy(tname, cname, len);
+ if (nvlist_lookup_uint64(cnv, ZPOOL_CONFIG_NPARITY, &np) == 0)
+ tname[strlen(tname)] = '0' + np;
+ show_vdev_stats(tname, ctype, cnv, indent + 2);
+ umem_free(tname, len);
+ }
+}
+
+void
+show_pool_stats(spa_t *spa)
+{
+ nvlist_t *config, *nvroot;
+ char *name;
+
+ VERIFY(spa_get_stats(spa_name(spa), &config, NULL, 0) == 0);
+
+ VERIFY(nvlist_lookup_nvlist(config, ZPOOL_CONFIG_VDEV_TREE,
+ &nvroot) == 0);
+ VERIFY(nvlist_lookup_string(config, ZPOOL_CONFIG_POOL_NAME,
+ &name) == 0);
+
+ show_vdev_stats(name, ZPOOL_CONFIG_CHILDREN, nvroot, 0);
+ show_vdev_stats(NULL, ZPOOL_CONFIG_L2CACHE, nvroot, 0);
+ show_vdev_stats(NULL, ZPOOL_CONFIG_SPARES, nvroot, 0);
+
+ nvlist_free(config);
+}
+
+/*
+ * Sets given global variable in libzpool to given unsigned 32-bit value.
+ * arg: "<variable>=<value>"
+ */
+int
+set_global_var(char *arg)
+{
+ void *zpoolhdl;
+ char *varname = arg, *varval;
+ u_longlong_t val;
+
+#ifndef _LITTLE_ENDIAN
+ /*
+ * On big endian systems changing a 64-bit variable would set the high
+ * 32 bits instead of the low 32 bits, which could cause unexpected
+ * results.
+ */
+ fprintf(stderr, "Setting global variables is only supported on "
+ "little-endian systems\n");
+ return (ENOTSUP);
+#endif
+ if (arg != NULL && (varval = strchr(arg, '=')) != NULL) {
+ *varval = '\0';
+ varval++;
+ val = strtoull(varval, NULL, 0);
+ if (val > UINT32_MAX) {
+ fprintf(stderr, "Value for global variable '%s' must "
+ "be a 32-bit unsigned integer\n", varname);
+ return (EOVERFLOW);
+ }
+ } else {
+ return (EINVAL);
+ }
+
+ zpoolhdl = dlopen("libzpool.so", RTLD_LAZY);
+ if (zpoolhdl != NULL) {
+ uint32_t *var;
+ var = dlsym(zpoolhdl, varname);
+ if (var == NULL) {
+ fprintf(stderr, "Global variable '%s' does not exist "
+ "in libzpool.so\n", varname);
+ return (EINVAL);
+ }
+ *var = (uint32_t)val;
+
+ dlclose(zpoolhdl);
+ } else {
+ fprintf(stderr, "Failed to open libzpool.so to set global "
+ "variable\n");
+ return (EIO);
+ }
+
+ return (0);
+}
+
+static nvlist_t *
+refresh_config(void *unused, nvlist_t *tryconfig)
+{
+ return (spa_tryimport(tryconfig));
+}
+
+static int
+pool_active(void *unused, const char *name, uint64_t guid,
+ boolean_t *isactive)
+{
+ zfs_cmd_t *zcp;
+ nvlist_t *innvl;
+ char *packed = NULL;
+ size_t size = 0;
+ int fd, ret;
+
+ /*
+ * Use ZFS_IOC_POOL_SYNC to confirm if a pool is active
+ */
+
+ fd = open(ZFS_DEV, O_RDWR);
+ if (fd < 0)
+ return (-1);
+
+ zcp = umem_zalloc(sizeof (zfs_cmd_t), UMEM_NOFAIL);
+
+ innvl = fnvlist_alloc();
+ fnvlist_add_boolean_value(innvl, "force", B_FALSE);
+
+ (void) strlcpy(zcp->zc_name, name, sizeof (zcp->zc_name));
+ packed = fnvlist_pack(innvl, &size);
+ zcp->zc_nvlist_src = (uint64_t)(uintptr_t)packed;
+ zcp->zc_nvlist_src_size = size;
+
+ ret = zfs_ioctl_fd(fd, ZFS_IOC_POOL_SYNC, zcp);
+
+ fnvlist_pack_free(packed, size);
+ free((void *)(uintptr_t)zcp->zc_nvlist_dst);
+ nvlist_free(innvl);
+ umem_free(zcp, sizeof (zfs_cmd_t));
+
+ (void) close(fd);
+
+ *isactive = (ret == 0);
+
+ return (0);
+}
+
+const pool_config_ops_t libzpool_config_ops = {
+ .pco_refresh_config = refresh_config,
+ .pco_pool_active = pool_active,
+};
diff --git a/lib/libzstd/Makefile.am b/lib/libzstd/Makefile.am
new file mode 100644
index 000000000000..df31d8b23d16
--- /dev/null
+++ b/lib/libzstd/Makefile.am
@@ -0,0 +1,21 @@
+include $(top_srcdir)/config/Rules.am
+
+VPATH = $(top_srcdir)/module/zstd
+
+# -fno-tree-vectorize is set for gcc in zstd/common/compiler.h
+# Set it for other compilers, too.
+AM_CFLAGS += -fno-tree-vectorize
+
+noinst_LTLIBRARIES = libzstd.la
+
+KERNEL_C = \
+ lib/zstd.c \
+ zfs_zstd.c
+
+nodist_libzstd_la_SOURCES = $(KERNEL_C)
+
+lib/zstd.$(OBJEXT): CFLAGS += -fno-tree-vectorize -include $(top_srcdir)/module/zstd/include/zstd_compat_wrapper.h -Wp,-w
+lib/zstd.l$(OBJEXT): CFLAGS += -fno-tree-vectorize -include $(top_srcdir)/module/zstd/include/zstd_compat_wrapper.h -Wp,-w
+
+zfs_zstd.$(OBJEXT): CFLAGS += -include $(top_srcdir)/module/zstd/include/zstd_compat_wrapper.h
+zfs_zstd.l$(OBJEXT): CFLAGS += -include $(top_srcdir)/module/zstd/include/zstd_compat_wrapper.h
diff --git a/lib/libzutil/Makefile.am b/lib/libzutil/Makefile.am
new file mode 100644
index 000000000000..cd03ba19d508
--- /dev/null
+++ b/lib/libzutil/Makefile.am
@@ -0,0 +1,50 @@
+include $(top_srcdir)/config/Rules.am
+
+# Suppress unused but set variable warnings often due to ASSERTs
+AM_CFLAGS += $(NO_UNUSED_BUT_SET_VARIABLE)
+AM_CFLAGS += $(LIBBLKID_CFLAGS) $(LIBUDEV_CFLAGS)
+
+DEFAULT_INCLUDES += -I$(srcdir)
+
+noinst_LTLIBRARIES = libzutil.la
+
+USER_C = \
+ zutil_device_path.c \
+ zutil_import.c \
+ zutil_import.h \
+ zutil_nicenum.c \
+ zutil_pool.c
+
+if BUILD_LINUX
+USER_C += \
+ os/linux/zutil_device_path_os.c \
+ os/linux/zutil_import_os.c \
+ os/linux/zutil_compat.c
+endif
+
+if BUILD_FREEBSD
+DEFAULT_INCLUDES += -I$(top_srcdir)/include/os/freebsd/zfs
+
+USER_C += \
+ os/freebsd/zutil_device_path_os.c \
+ os/freebsd/zutil_import_os.c \
+ os/freebsd/zutil_compat.c
+
+VPATH += $(top_srcdir)/module/os/freebsd/zfs
+
+nodist_libzutil_la_SOURCES = zfs_ioctl_compat.c
+endif
+
+libzutil_la_SOURCES = $(USER_C)
+
+libzutil_la_LIBADD = \
+ $(abs_top_builddir)/lib/libavl/libavl.la \
+ $(abs_top_builddir)/lib/libtpool/libtpool.la \
+ $(abs_top_builddir)/lib/libspl/libspl.la
+
+if BUILD_LINUX
+libzutil_la_LIBADD += \
+ $(abs_top_builddir)/lib/libefi/libefi.la
+endif
+
+libzutil_la_LIBADD += -lm $(LIBBLKID_LIBS) $(LIBUDEV_LIBS)
diff --git a/lib/libzutil/os/freebsd/zutil_compat.c b/lib/libzutil/os/freebsd/zutil_compat.c
new file mode 100644
index 000000000000..3e70fef1ec3d
--- /dev/null
+++ b/lib/libzutil/os/freebsd/zutil_compat.c
@@ -0,0 +1,124 @@
+/*
+ * 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
+ */
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/sysctl.h>
+#include <sys/zfs_ioctl.h>
+#include <os/freebsd/zfs/sys/zfs_ioctl_compat.h>
+#include <libzutil.h>
+
+#include <err.h>
+
+int zfs_ioctl_version = ZFS_IOCVER_UNDEF;
+
+/*
+ * Get zfs_ioctl_version
+ */
+static int
+get_zfs_ioctl_version(void)
+{
+ size_t ver_size;
+ int ver = ZFS_IOCVER_NONE;
+
+ ver_size = sizeof (ver);
+ sysctlbyname("vfs.zfs.version.ioctl", &ver, &ver_size, NULL, 0);
+
+ return (ver);
+}
+
+static int
+zcmd_ioctl_compat(int fd, int request, zfs_cmd_t *zc, const int cflag)
+{
+ int newrequest, ret;
+ void *zc_c = NULL;
+ unsigned long ncmd;
+ zfs_iocparm_t zp;
+
+ switch (cflag) {
+ case ZFS_CMD_COMPAT_NONE:
+ ncmd = _IOWR('Z', request, zfs_iocparm_t);
+ zp.zfs_cmd = (uint64_t)zc;
+ zp.zfs_cmd_size = sizeof (zfs_cmd_t);
+ zp.zfs_ioctl_version = ZFS_IOCVER_OZFS;
+ break;
+ case ZFS_CMD_COMPAT_LEGACY:
+ newrequest = zfs_ioctl_ozfs_to_legacy(request);
+ ncmd = _IOWR('Z', newrequest, zfs_iocparm_t);
+ zc_c = malloc(sizeof (zfs_cmd_legacy_t));
+ zfs_cmd_ozfs_to_legacy(zc, zc_c);
+ zp.zfs_cmd = (uint64_t)zc_c;
+ zp.zfs_cmd_size = sizeof (zfs_cmd_legacy_t);
+ zp.zfs_ioctl_version = ZFS_IOCVER_LEGACY;
+ break;
+ default:
+ abort();
+ return (EINVAL);
+ }
+
+ ret = ioctl(fd, ncmd, &zp);
+ if (ret) {
+ if (zc_c)
+ free(zc_c);
+ return (ret);
+ }
+ if (zc_c) {
+ zfs_cmd_legacy_to_ozfs(zc_c, zc);
+ free(zc_c);
+ }
+ return (ret);
+}
+
+/*
+ * This is FreeBSD version of ioctl, because Solaris' ioctl() updates
+ * zc_nvlist_dst_size even if an error is returned, on FreeBSD if an
+ * error is returned zc_nvlist_dst_size won't be updated.
+ */
+int
+zfs_ioctl_fd(int fd, unsigned long request, zfs_cmd_t *zc)
+{
+ size_t oldsize;
+ int ret, cflag = ZFS_CMD_COMPAT_NONE;
+
+ if (zfs_ioctl_version == ZFS_IOCVER_UNDEF)
+ zfs_ioctl_version = get_zfs_ioctl_version();
+
+ switch (zfs_ioctl_version) {
+ case ZFS_IOCVER_LEGACY:
+ cflag = ZFS_CMD_COMPAT_LEGACY;
+ break;
+ case ZFS_IOCVER_OZFS:
+ cflag = ZFS_CMD_COMPAT_NONE;
+ break;
+ default:
+ errx(1, "unrecognized zfs ioctl version %d",
+ zfs_ioctl_version);
+ }
+
+ oldsize = zc->zc_nvlist_dst_size;
+ ret = zcmd_ioctl_compat(fd, request, zc, cflag);
+
+ if (ret == 0 && oldsize < zc->zc_nvlist_dst_size) {
+ ret = -1;
+ errno = ENOMEM;
+ }
+
+ return (ret);
+}
diff --git a/lib/libzutil/os/freebsd/zutil_device_path_os.c b/lib/libzutil/os/freebsd/zutil_device_path_os.c
new file mode 100644
index 000000000000..71c936005242
--- /dev/null
+++ b/lib/libzutil/os/freebsd/zutil_device_path_os.c
@@ -0,0 +1,132 @@
+/*
+ * 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
+ */
+
+#include <ctype.h>
+#include <fcntl.h>
+#include <paths.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <sys/stat.h>
+
+#include <libgeom.h>
+
+#include <libzutil.h>
+
+/*
+ * We don't strip/append partitions on FreeBSD.
+ */
+
+/*
+ * Note: The caller must free the returned string.
+ */
+char *
+zfs_strip_partition(char *dev)
+{
+ return (strdup(dev));
+}
+
+int
+zfs_append_partition(char *path, size_t max_len)
+{
+ return (strnlen(path, max_len));
+}
+
+/*
+ * Strip the path from a device name.
+ * On FreeBSD we only want to remove "/dev/" from the beginning of
+ * paths if present.
+ */
+char *
+zfs_strip_path(char *path)
+{
+ if (strncmp(path, _PATH_DEV, sizeof (_PATH_DEV) - 1) == 0)
+ return (path + sizeof (_PATH_DEV) - 1);
+ else
+ return (path);
+}
+
+char *
+zfs_get_underlying_path(const char *dev_name)
+{
+
+ if (dev_name == NULL)
+ return (NULL);
+
+ return (realpath(dev_name, NULL));
+}
+
+boolean_t
+zfs_dev_is_whole_disk(const char *dev_name)
+{
+ int fd;
+
+ fd = g_open(dev_name, 0);
+ if (fd >= 0) {
+ g_close(fd);
+ return (B_TRUE);
+ }
+ return (B_FALSE);
+}
+
+/*
+ * Wait up to timeout_ms for udev to set up the device node. The device is
+ * considered ready when libudev determines it has been initialized, all of
+ * the device links have been verified to exist, and it has been allowed to
+ * settle. At this point the device the device can be accessed reliably.
+ * Depending on the complexity of the udev rules this process could take
+ * several seconds.
+ */
+int
+zpool_label_disk_wait(const char *path, int timeout_ms)
+{
+ int settle_ms = 50;
+ long sleep_ms = 10;
+ hrtime_t start, settle;
+ struct stat64 statbuf;
+
+ start = gethrtime();
+ settle = 0;
+
+ do {
+ errno = 0;
+ if ((stat64(path, &statbuf) == 0) && (errno == 0)) {
+ if (settle == 0)
+ settle = gethrtime();
+ else if (NSEC2MSEC(gethrtime() - settle) >= settle_ms)
+ return (0);
+ } else if (errno != ENOENT) {
+ return (errno);
+ }
+
+ usleep(sleep_ms * MILLISEC);
+ } while (NSEC2MSEC(gethrtime() - start) < timeout_ms);
+
+ return (ENODEV);
+}
+
+/* ARGSUSED */
+boolean_t
+is_mpath_whole_disk(const char *path)
+{
+ return (B_FALSE);
+}
diff --git a/lib/libzutil/os/freebsd/zutil_import_os.c b/lib/libzutil/os/freebsd/zutil_import_os.c
new file mode 100644
index 000000000000..6cb001eac3de
--- /dev/null
+++ b/lib/libzutil/os/freebsd/zutil_import_os.c
@@ -0,0 +1,239 @@
+/*
+ * 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) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2017 by Delphix. All rights reserved.
+ * Copyright 2015 RackTop Systems.
+ * Copyright 2016 Nexenta Systems, Inc.
+ */
+
+/*
+ * Pool import support functions.
+ *
+ * To import a pool, we rely on reading the configuration information from the
+ * ZFS label of each device. If we successfully read the label, then we
+ * organize the configuration information in the following hierarchy:
+ *
+ * pool guid -> toplevel vdev guid -> label txg
+ *
+ * Duplicate entries matching this same tuple will be discarded. Once we have
+ * examined every device, we pick the best label txg config for each toplevel
+ * vdev. We then arrange these toplevel vdevs into a complete pool config, and
+ * update any paths that have changed. Finally, we attempt to import the pool
+ * using our derived config, and record the results.
+ */
+
+#include <aio.h>
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <libintl.h>
+#include <libgen.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/disk.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include <sys/efi_partition.h>
+#include <thread_pool.h>
+#include <libgeom.h>
+
+#include <sys/vdev_impl.h>
+
+#include <libzutil.h>
+
+#include "zutil_import.h"
+
+/*
+ * Update a leaf vdev's persistent device strings
+ *
+ * - only applies for a dedicated leaf vdev (aka whole disk)
+ * - updated during pool create|add|attach|import
+ * - used for matching device matching during auto-{online,expand,replace}
+ * - stored in a leaf disk config label (i.e. alongside 'path' NVP)
+ * - these strings are currently not used in kernel (i.e. for vdev_disk_open)
+ *
+ * On FreeBSD we currently just strip devid and phys_path to avoid confusion.
+ */
+void
+update_vdev_config_dev_strs(nvlist_t *nv)
+{
+ (void) nvlist_remove_all(nv, ZPOOL_CONFIG_DEVID);
+ (void) nvlist_remove_all(nv, ZPOOL_CONFIG_PHYS_PATH);
+}
+
+/*
+ * Do not even look at these devices.
+ */
+static const char * const excluded_devs[] = {
+ "nfslock",
+ "sequencer",
+ "zfs",
+};
+#define EXCLUDED_DIR "/dev/"
+#define EXCLUDED_DIR_LEN 5
+
+void
+zpool_open_func(void *arg)
+{
+ rdsk_node_t *rn = arg;
+ struct stat64 statbuf;
+ nvlist_t *config;
+ size_t i;
+ int num_labels;
+ int fd;
+ off_t mediasize = 0;
+
+ /*
+ * Do not even look at excluded devices.
+ */
+ if (strncmp(rn->rn_name, EXCLUDED_DIR, EXCLUDED_DIR_LEN) == 0) {
+ char *name = rn->rn_name + EXCLUDED_DIR_LEN;
+ for (i = 0; i < nitems(excluded_devs); ++i) {
+ const char *excluded_name = excluded_devs[i];
+ size_t len = strlen(excluded_name);
+ if (strncmp(name, excluded_name, len) == 0) {
+ return;
+ }
+ }
+ }
+
+ /*
+ * O_NONBLOCK so we don't hang trying to open things like serial ports.
+ */
+ if ((fd = open(rn->rn_name, O_RDONLY|O_NONBLOCK)) < 0)
+ return;
+
+ /*
+ * Ignore failed stats.
+ */
+ if (fstat64(fd, &statbuf) != 0)
+ goto out;
+ /*
+ * We only want regular files, character devs and block devs.
+ */
+ if (S_ISREG(statbuf.st_mode)) {
+ /* Check if this file is too small to hold a zpool. */
+ if (statbuf.st_size < SPA_MINDEVSIZE) {
+ goto out;
+ }
+ } else if (S_ISCHR(statbuf.st_mode) || S_ISBLK(statbuf.st_mode)) {
+ /* Check if this device is too small to hold a zpool. */
+ if (ioctl(fd, DIOCGMEDIASIZE, &mediasize) != 0 ||
+ mediasize < SPA_MINDEVSIZE) {
+ goto out;
+ }
+ } else {
+ goto out;
+ }
+
+ if (zpool_read_label(fd, &config, &num_labels) != 0)
+ goto out;
+ if (num_labels == 0) {
+ nvlist_free(config);
+ goto out;
+ }
+
+ rn->rn_config = config;
+ rn->rn_num_labels = num_labels;
+
+ /* TODO: Reuse labelpaths logic from Linux? */
+out:
+ (void) close(fd);
+}
+
+static const char *
+zpool_default_import_path[] = {
+ "/dev"
+};
+
+const char * const *
+zpool_default_search_paths(size_t *count)
+{
+ *count = nitems(zpool_default_import_path);
+ return (zpool_default_import_path);
+}
+
+int
+zpool_find_import_blkid(libpc_handle_t *hdl, pthread_mutex_t *lock,
+ avl_tree_t **slice_cache)
+{
+ char *end, path[MAXPATHLEN];
+ rdsk_node_t *slice;
+ struct gmesh mesh;
+ struct gclass *mp;
+ struct ggeom *gp;
+ struct gprovider *pp;
+ avl_index_t where;
+ size_t pathleft;
+ int error;
+
+ end = stpcpy(path, "/dev/");
+ pathleft = &path[sizeof (path)] - end;
+
+ error = geom_gettree(&mesh);
+ if (error != 0)
+ return (error);
+
+ *slice_cache = zutil_alloc(hdl, sizeof (avl_tree_t));
+ avl_create(*slice_cache, slice_cache_compare, sizeof (rdsk_node_t),
+ offsetof(rdsk_node_t, rn_node));
+
+ LIST_FOREACH(mp, &mesh.lg_class, lg_class) {
+ LIST_FOREACH(gp, &mp->lg_geom, lg_geom) {
+ LIST_FOREACH(pp, &gp->lg_provider, lg_provider) {
+ strlcpy(end, pp->lg_name, pathleft);
+ slice = zutil_alloc(hdl, sizeof (rdsk_node_t));
+ slice->rn_name = zutil_strdup(hdl, path);
+ slice->rn_vdev_guid = 0;
+ slice->rn_lock = lock;
+ slice->rn_avl = *slice_cache;
+ slice->rn_hdl = hdl;
+ slice->rn_labelpaths = B_FALSE;
+ slice->rn_order = IMPORT_ORDER_DEFAULT;
+
+ pthread_mutex_lock(lock);
+ if (avl_find(*slice_cache, slice, &where)) {
+ free(slice->rn_name);
+ free(slice);
+ } else {
+ avl_insert(*slice_cache, slice, where);
+ }
+ pthread_mutex_unlock(lock);
+ }
+ }
+ }
+
+ geom_deletetree(&mesh);
+
+ return (0);
+}
+
+int
+zfs_dev_flush(int fd __unused)
+{
+ return (0);
+}
diff --git a/lib/libzutil/os/linux/zutil_compat.c b/lib/libzutil/os/linux/zutil_compat.c
new file mode 100644
index 000000000000..173ae9cb619c
--- /dev/null
+++ b/lib/libzutil/os/linux/zutil_compat.c
@@ -0,0 +1,30 @@
+/*
+ * 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
+ */
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/zfs_ioctl.h>
+#include <libzutil.h>
+
+int
+zfs_ioctl_fd(int fd, unsigned long request, zfs_cmd_t *zc)
+{
+ return (ioctl(fd, request, zc));
+}
diff --git a/lib/libzutil/os/linux/zutil_device_path_os.c b/lib/libzutil/os/linux/zutil_device_path_os.c
new file mode 100644
index 000000000000..36331fd72bf7
--- /dev/null
+++ b/lib/libzutil/os/linux/zutil_device_path_os.c
@@ -0,0 +1,506 @@
+/*
+ * 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) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
+ */
+
+#include <ctype.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/efi_partition.h>
+
+#ifdef HAVE_LIBUDEV
+#include <libudev.h>
+#endif
+
+#include <libzutil.h>
+
+/*
+ * Append partition suffix to an otherwise fully qualified device path.
+ * This is used to generate the name the full path as its stored in
+ * ZPOOL_CONFIG_PATH for whole disk devices. On success the new length
+ * of 'path' will be returned on error a negative value is returned.
+ */
+int
+zfs_append_partition(char *path, size_t max_len)
+{
+ int len = strlen(path);
+
+ if ((strncmp(path, UDISK_ROOT, strlen(UDISK_ROOT)) == 0) ||
+ (strncmp(path, ZVOL_ROOT, strlen(ZVOL_ROOT)) == 0)) {
+ if (len + 6 >= max_len)
+ return (-1);
+
+ (void) strcat(path, "-part1");
+ len += 6;
+ } else {
+ if (len + 2 >= max_len)
+ return (-1);
+
+ if (isdigit(path[len-1])) {
+ (void) strcat(path, "p1");
+ len += 2;
+ } else {
+ (void) strcat(path, "1");
+ len += 1;
+ }
+ }
+
+ return (len);
+}
+
+/*
+ * Remove partition suffix from a vdev path. Partition suffixes may take three
+ * forms: "-partX", "pX", or "X", where X is a string of digits. The second
+ * case only occurs when the suffix is preceded by a digit, i.e. "md0p0" The
+ * third case only occurs when preceded by a string matching the regular
+ * expression "^([hsv]|xv)d[a-z]+", i.e. a scsi, ide, virtio or xen disk.
+ *
+ * caller must free the returned string
+ */
+char *
+zfs_strip_partition(char *path)
+{
+ char *tmp = strdup(path);
+ char *part = NULL, *d = NULL;
+ if (!tmp)
+ return (NULL);
+
+ if ((part = strstr(tmp, "-part")) && part != tmp) {
+ d = part + 5;
+ } else if ((part = strrchr(tmp, 'p')) &&
+ part > tmp + 1 && isdigit(*(part-1))) {
+ d = part + 1;
+ } else if ((tmp[0] == 'h' || tmp[0] == 's' || tmp[0] == 'v') &&
+ tmp[1] == 'd') {
+ for (d = &tmp[2]; isalpha(*d); part = ++d) { }
+ } else if (strncmp("xvd", tmp, 3) == 0) {
+ for (d = &tmp[3]; isalpha(*d); part = ++d) { }
+ }
+ if (part && d && *d != '\0') {
+ for (; isdigit(*d); d++) { }
+ if (*d == '\0')
+ *part = '\0';
+ }
+
+ return (tmp);
+}
+
+/*
+ * Same as zfs_strip_partition, but allows "/dev/" to be in the pathname
+ *
+ * path: /dev/sda1
+ * returns: /dev/sda
+ *
+ * Returned string must be freed.
+ */
+static char *
+zfs_strip_partition_path(char *path)
+{
+ char *newpath = strdup(path);
+ char *sd_offset;
+ char *new_sd;
+
+ if (!newpath)
+ return (NULL);
+
+ /* Point to "sda1" part of "/dev/sda1" */
+ sd_offset = strrchr(newpath, '/') + 1;
+
+ /* Get our new name "sda" */
+ new_sd = zfs_strip_partition(sd_offset);
+ if (!new_sd) {
+ free(newpath);
+ return (NULL);
+ }
+
+ /* Paste the "sda" where "sda1" was */
+ strlcpy(sd_offset, new_sd, strlen(sd_offset) + 1);
+
+ /* Free temporary "sda" */
+ free(new_sd);
+
+ return (newpath);
+}
+
+/*
+ * Strip the unwanted portion of a device path.
+ */
+char *
+zfs_strip_path(char *path)
+{
+ return (strrchr(path, '/') + 1);
+}
+
+/*
+ * Allocate and return the underlying device name for a device mapper device.
+ * If a device mapper device maps to multiple devices, return the first device.
+ *
+ * For example, dm_name = "/dev/dm-0" could return "/dev/sda". Symlinks to a
+ * DM device (like /dev/disk/by-vdev/A0) are also allowed.
+ *
+ * Returns device name, or NULL on error or no match. If dm_name is not a DM
+ * device then return NULL.
+ *
+ * NOTE: The returned name string must be *freed*.
+ */
+static char *
+dm_get_underlying_path(const char *dm_name)
+{
+ DIR *dp = NULL;
+ struct dirent *ep;
+ char *realp;
+ char *tmp = NULL;
+ char *path = NULL;
+ char *dev_str;
+ int size;
+
+ if (dm_name == NULL)
+ return (NULL);
+
+ /* dm name may be a symlink (like /dev/disk/by-vdev/A0) */
+ realp = realpath(dm_name, NULL);
+ if (realp == NULL)
+ return (NULL);
+
+ /*
+ * If they preface 'dev' with a path (like "/dev") then strip it off.
+ * We just want the 'dm-N' part.
+ */
+ tmp = strrchr(realp, '/');
+ if (tmp != NULL)
+ dev_str = tmp + 1; /* +1 since we want the chr after '/' */
+ else
+ dev_str = tmp;
+
+ size = asprintf(&tmp, "/sys/block/%s/slaves/", dev_str);
+ if (size == -1 || !tmp)
+ goto end;
+
+ dp = opendir(tmp);
+ if (dp == NULL)
+ goto end;
+
+ /*
+ * Return first entry (that isn't itself a directory) in the
+ * directory containing device-mapper dependent (underlying)
+ * devices.
+ */
+ while ((ep = readdir(dp))) {
+ if (ep->d_type != DT_DIR) { /* skip "." and ".." dirs */
+ size = asprintf(&path, "/dev/%s", ep->d_name);
+ break;
+ }
+ }
+
+end:
+ if (dp != NULL)
+ closedir(dp);
+ free(tmp);
+ free(realp);
+ return (path);
+}
+
+/*
+ * Return B_TRUE if device is a device mapper or multipath device.
+ * Return B_FALSE if not.
+ */
+boolean_t
+zfs_dev_is_dm(const char *dev_name)
+{
+
+ char *tmp;
+ tmp = dm_get_underlying_path(dev_name);
+ if (tmp == NULL)
+ return (B_FALSE);
+
+ free(tmp);
+ return (B_TRUE);
+}
+
+/*
+ * By "whole disk" we mean an entire physical disk (something we can
+ * label, toggle the write cache on, etc.) as opposed to the full
+ * capacity of a pseudo-device such as lofi or did. We act as if we
+ * are labeling the disk, which should be a pretty good test of whether
+ * it's a viable device or not. Returns B_TRUE if it is and B_FALSE if
+ * it isn't.
+ */
+boolean_t
+zfs_dev_is_whole_disk(const char *dev_name)
+{
+ struct dk_gpt *label;
+ int fd;
+
+ if ((fd = open(dev_name, O_RDONLY | O_DIRECT)) < 0)
+ return (B_FALSE);
+
+ if (efi_alloc_and_init(fd, EFI_NUMPAR, &label) != 0) {
+ (void) close(fd);
+ return (B_FALSE);
+ }
+
+ efi_free(label);
+ (void) close(fd);
+
+ return (B_TRUE);
+}
+
+/*
+ * Lookup the underlying device for a device name
+ *
+ * Often you'll have a symlink to a device, a partition device,
+ * or a multipath device, and want to look up the underlying device.
+ * This function returns the underlying device name. If the device
+ * name is already the underlying device, then just return the same
+ * name. If the device is a DM device with multiple underlying devices
+ * then return the first one.
+ *
+ * For example:
+ *
+ * 1. /dev/disk/by-id/ata-QEMU_HARDDISK_QM00001 -> ../../sda
+ * dev_name: /dev/disk/by-id/ata-QEMU_HARDDISK_QM00001
+ * returns: /dev/sda
+ *
+ * 2. /dev/mapper/mpatha (made up of /dev/sda and /dev/sdb)
+ * dev_name: /dev/mapper/mpatha
+ * returns: /dev/sda (first device)
+ *
+ * 3. /dev/sda (already the underlying device)
+ * dev_name: /dev/sda
+ * returns: /dev/sda
+ *
+ * 4. /dev/dm-3 (mapped to /dev/sda)
+ * dev_name: /dev/dm-3
+ * returns: /dev/sda
+ *
+ * 5. /dev/disk/by-id/scsi-0QEMU_drive-scsi0-0-0-0-part9 -> ../../sdb9
+ * dev_name: /dev/disk/by-id/scsi-0QEMU_drive-scsi0-0-0-0-part9
+ * returns: /dev/sdb
+ *
+ * 6. /dev/disk/by-uuid/5df030cf-3cd9-46e4-8e99-3ccb462a4e9a -> ../dev/sda2
+ * dev_name: /dev/disk/by-uuid/5df030cf-3cd9-46e4-8e99-3ccb462a4e9a
+ * returns: /dev/sda
+ *
+ * Returns underlying device name, or NULL on error or no match.
+ *
+ * NOTE: The returned name string must be *freed*.
+ */
+char *
+zfs_get_underlying_path(const char *dev_name)
+{
+ char *name = NULL;
+ char *tmp;
+
+ if (dev_name == NULL)
+ return (NULL);
+
+ tmp = dm_get_underlying_path(dev_name);
+
+ /* dev_name not a DM device, so just un-symlinkize it */
+ if (tmp == NULL)
+ tmp = realpath(dev_name, NULL);
+
+ if (tmp != NULL) {
+ name = zfs_strip_partition_path(tmp);
+ free(tmp);
+ }
+
+ return (name);
+}
+
+/*
+ * Given a dev name like "sda", return the full enclosure sysfs path to
+ * the disk. You can also pass in the name with "/dev" prepended
+ * to it (like /dev/sda).
+ *
+ * For example, disk "sda" in enclosure slot 1:
+ * dev: "sda"
+ * returns: "/sys/class/enclosure/1:0:3:0/Slot 1"
+ *
+ * 'dev' must be a non-devicemapper device.
+ *
+ * Returned string must be freed.
+ */
+char *
+zfs_get_enclosure_sysfs_path(const char *dev_name)
+{
+ DIR *dp = NULL;
+ struct dirent *ep;
+ char buf[MAXPATHLEN];
+ char *tmp1 = NULL;
+ char *tmp2 = NULL;
+ char *tmp3 = NULL;
+ char *path = NULL;
+ size_t size;
+ int tmpsize;
+
+ if (dev_name == NULL)
+ return (NULL);
+
+ /* If they preface 'dev' with a path (like "/dev") then strip it off */
+ tmp1 = strrchr(dev_name, '/');
+ if (tmp1 != NULL)
+ dev_name = tmp1 + 1; /* +1 since we want the chr after '/' */
+
+ tmpsize = asprintf(&tmp1, "/sys/block/%s/device", dev_name);
+ if (tmpsize == -1 || tmp1 == NULL) {
+ tmp1 = NULL;
+ goto end;
+ }
+
+ dp = opendir(tmp1);
+ if (dp == NULL) {
+ tmp1 = NULL; /* To make free() at the end a NOP */
+ goto end;
+ }
+
+ /*
+ * Look though all sysfs entries in /sys/block/<dev>/device for
+ * the enclosure symlink.
+ */
+ while ((ep = readdir(dp))) {
+ /* Ignore everything that's not our enclosure_device link */
+ if (strstr(ep->d_name, "enclosure_device") == NULL)
+ continue;
+
+ if (asprintf(&tmp2, "%s/%s", tmp1, ep->d_name) == -1 ||
+ tmp2 == NULL)
+ break;
+
+ size = readlink(tmp2, buf, sizeof (buf));
+
+ /* Did readlink fail or crop the link name? */
+ if (size == -1 || size >= sizeof (buf)) {
+ free(tmp2);
+ tmp2 = NULL; /* To make free() at the end a NOP */
+ break;
+ }
+
+ /*
+ * We got a valid link. readlink() doesn't terminate strings
+ * so we have to do it.
+ */
+ buf[size] = '\0';
+
+ /*
+ * Our link will look like:
+ *
+ * "../../../../port-11:1:2/..STUFF../enclosure/1:0:3:0/SLOT 1"
+ *
+ * We want to grab the "enclosure/1:0:3:0/SLOT 1" part
+ */
+ tmp3 = strstr(buf, "enclosure");
+ if (tmp3 == NULL)
+ break;
+
+ if (asprintf(&path, "/sys/class/%s", tmp3) == -1) {
+ /* If asprintf() fails, 'path' is undefined */
+ path = NULL;
+ break;
+ }
+
+ if (path == NULL)
+ break;
+ }
+
+end:
+ free(tmp2);
+ free(tmp1);
+
+ if (dp != NULL)
+ closedir(dp);
+
+ return (path);
+}
+
+#ifdef HAVE_LIBUDEV
+
+/*
+ * A disk is considered a multipath whole disk when:
+ * DEVNAME key value has "dm-"
+ * DM_NAME key value has "mpath" prefix
+ * DM_UUID key exists
+ * ID_PART_TABLE_TYPE key does not exist or is not gpt
+ */
+static boolean_t
+udev_mpath_whole_disk(struct udev_device *dev)
+{
+ const char *devname, *type, *uuid;
+
+ devname = udev_device_get_property_value(dev, "DEVNAME");
+ type = udev_device_get_property_value(dev, "ID_PART_TABLE_TYPE");
+ uuid = udev_device_get_property_value(dev, "DM_UUID");
+
+ if ((devname != NULL && strncmp(devname, "/dev/dm-", 8) == 0) &&
+ ((type == NULL) || (strcmp(type, "gpt") != 0)) &&
+ (uuid != NULL)) {
+ return (B_TRUE);
+ }
+
+ return (B_FALSE);
+}
+
+/*
+ * Check if a disk is effectively a multipath whole disk
+ */
+boolean_t
+is_mpath_whole_disk(const char *path)
+{
+ struct udev *udev;
+ struct udev_device *dev = NULL;
+ char nodepath[MAXPATHLEN];
+ char *sysname;
+ boolean_t wholedisk = B_FALSE;
+
+ if (realpath(path, nodepath) == NULL)
+ return (B_FALSE);
+ sysname = strrchr(nodepath, '/') + 1;
+ if (strncmp(sysname, "dm-", 3) != 0)
+ return (B_FALSE);
+ if ((udev = udev_new()) == NULL)
+ return (B_FALSE);
+ if ((dev = udev_device_new_from_subsystem_sysname(udev, "block",
+ sysname)) == NULL) {
+ udev_device_unref(dev);
+ return (B_FALSE);
+ }
+
+ wholedisk = udev_mpath_whole_disk(dev);
+
+ udev_device_unref(dev);
+ return (wholedisk);
+}
+
+#else /* HAVE_LIBUDEV */
+
+/* ARGSUSED */
+boolean_t
+is_mpath_whole_disk(const char *path)
+{
+ return (B_FALSE);
+}
+
+#endif /* HAVE_LIBUDEV */
diff --git a/lib/libzutil/os/linux/zutil_import_os.c b/lib/libzutil/os/linux/zutil_import_os.c
new file mode 100644
index 000000000000..a4bf01749da8
--- /dev/null
+++ b/lib/libzutil/os/linux/zutil_import_os.c
@@ -0,0 +1,871 @@
+/*
+ * 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 2015 Nexenta Systems, Inc. All rights reserved.
+ * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2018 by Delphix. All rights reserved.
+ * Copyright 2015 RackTop Systems.
+ * Copyright (c) 2016, Intel Corporation.
+ */
+
+/*
+ * Pool import support functions.
+ *
+ * Used by zpool, ztest, zdb, and zhack to locate importable configs. Since
+ * these commands are expected to run in the global zone, we can assume
+ * that the devices are all readable when called.
+ *
+ * To import a pool, we rely on reading the configuration information from the
+ * ZFS label of each device. If we successfully read the label, then we
+ * organize the configuration information in the following hierarchy:
+ *
+ * pool guid -> toplevel vdev guid -> label txg
+ *
+ * Duplicate entries matching this same tuple will be discarded. Once we have
+ * examined every device, we pick the best label txg config for each toplevel
+ * vdev. We then arrange these toplevel vdevs into a complete pool config, and
+ * update any paths that have changed. Finally, we attempt to import the pool
+ * using our derived config, and record the results.
+ */
+
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <libintl.h>
+#include <libgen.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/dktp/fdisk.h>
+#include <sys/vdev_impl.h>
+#include <sys/fs/zfs.h>
+#include <sys/vdev_impl.h>
+
+#include <thread_pool.h>
+#include <libzutil.h>
+#include <libnvpair.h>
+
+#include "zutil_import.h"
+
+#ifdef HAVE_LIBUDEV
+#include <libudev.h>
+#include <sched.h>
+#endif
+#include <blkid/blkid.h>
+
+#define DEFAULT_IMPORT_PATH_SIZE 9
+#define DEV_BYID_PATH "/dev/disk/by-id/"
+
+static boolean_t
+is_watchdog_dev(char *dev)
+{
+ /* For 'watchdog' dev */
+ if (strcmp(dev, "watchdog") == 0)
+ return (B_TRUE);
+
+ /* For 'watchdog<digit><whatever> */
+ if (strstr(dev, "watchdog") == dev && isdigit(dev[8]))
+ return (B_TRUE);
+
+ return (B_FALSE);
+}
+
+int
+zfs_dev_flush(int fd)
+{
+ return (ioctl(fd, BLKFLSBUF));
+}
+
+void
+zpool_open_func(void *arg)
+{
+ rdsk_node_t *rn = arg;
+ libpc_handle_t *hdl = rn->rn_hdl;
+ struct stat64 statbuf;
+ nvlist_t *config;
+ char *bname, *dupname;
+ uint64_t vdev_guid = 0;
+ int error;
+ int num_labels = 0;
+ int fd;
+
+ /*
+ * Skip devices with well known prefixes there can be side effects
+ * when opening devices which need to be avoided.
+ *
+ * hpet - High Precision Event Timer
+ * watchdog - Watchdog must be closed in a special way.
+ */
+ dupname = zutil_strdup(hdl, rn->rn_name);
+ bname = basename(dupname);
+ error = ((strcmp(bname, "hpet") == 0) || is_watchdog_dev(bname));
+ free(dupname);
+ if (error)
+ return;
+
+ /*
+ * Ignore failed stats. We only want regular files and block devices.
+ */
+ if (stat64(rn->rn_name, &statbuf) != 0 ||
+ (!S_ISREG(statbuf.st_mode) && !S_ISBLK(statbuf.st_mode)))
+ return;
+
+ /*
+ * Preferentially open using O_DIRECT to bypass the block device
+ * cache which may be stale for multipath devices. An EINVAL errno
+ * indicates O_DIRECT is unsupported so fallback to just O_RDONLY.
+ */
+ fd = open(rn->rn_name, O_RDONLY | O_DIRECT);
+ if ((fd < 0) && (errno == EINVAL))
+ fd = open(rn->rn_name, O_RDONLY);
+ if ((fd < 0) && (errno == EACCES))
+ hdl->lpc_open_access_error = B_TRUE;
+ if (fd < 0)
+ return;
+
+ /*
+ * This file is too small to hold a zpool
+ */
+ if (S_ISREG(statbuf.st_mode) && statbuf.st_size < SPA_MINDEVSIZE) {
+ (void) close(fd);
+ return;
+ }
+
+ error = zpool_read_label(fd, &config, &num_labels);
+ if (error != 0) {
+ (void) close(fd);
+ return;
+ }
+
+ if (num_labels == 0) {
+ (void) close(fd);
+ nvlist_free(config);
+ return;
+ }
+
+ /*
+ * Check that the vdev is for the expected guid. Additional entries
+ * are speculatively added based on the paths stored in the labels.
+ * Entries with valid paths but incorrect guids must be removed.
+ */
+ error = nvlist_lookup_uint64(config, ZPOOL_CONFIG_GUID, &vdev_guid);
+ if (error || (rn->rn_vdev_guid && rn->rn_vdev_guid != vdev_guid)) {
+ (void) close(fd);
+ nvlist_free(config);
+ return;
+ }
+
+ (void) close(fd);
+
+ rn->rn_config = config;
+ rn->rn_num_labels = num_labels;
+
+ /*
+ * Add additional entries for paths described by this label.
+ */
+ if (rn->rn_labelpaths) {
+ char *path = NULL;
+ char *devid = NULL;
+ char *env = NULL;
+ rdsk_node_t *slice;
+ avl_index_t where;
+ int timeout;
+ int error;
+
+ if (label_paths(rn->rn_hdl, rn->rn_config, &path, &devid))
+ return;
+
+ env = getenv("ZPOOL_IMPORT_UDEV_TIMEOUT_MS");
+ if ((env == NULL) || sscanf(env, "%d", &timeout) != 1 ||
+ timeout < 0) {
+ timeout = DISK_LABEL_WAIT;
+ }
+
+ /*
+ * Allow devlinks to stabilize so all paths are available.
+ */
+ zpool_label_disk_wait(rn->rn_name, timeout);
+
+ if (path != NULL) {
+ slice = zutil_alloc(hdl, sizeof (rdsk_node_t));
+ slice->rn_name = zutil_strdup(hdl, path);
+ slice->rn_vdev_guid = vdev_guid;
+ slice->rn_avl = rn->rn_avl;
+ slice->rn_hdl = hdl;
+ slice->rn_order = IMPORT_ORDER_PREFERRED_1;
+ slice->rn_labelpaths = B_FALSE;
+ pthread_mutex_lock(rn->rn_lock);
+ if (avl_find(rn->rn_avl, slice, &where)) {
+ pthread_mutex_unlock(rn->rn_lock);
+ free(slice->rn_name);
+ free(slice);
+ } else {
+ avl_insert(rn->rn_avl, slice, where);
+ pthread_mutex_unlock(rn->rn_lock);
+ zpool_open_func(slice);
+ }
+ }
+
+ if (devid != NULL) {
+ slice = zutil_alloc(hdl, sizeof (rdsk_node_t));
+ error = asprintf(&slice->rn_name, "%s%s",
+ DEV_BYID_PATH, devid);
+ if (error == -1) {
+ free(slice);
+ return;
+ }
+
+ slice->rn_vdev_guid = vdev_guid;
+ slice->rn_avl = rn->rn_avl;
+ slice->rn_hdl = hdl;
+ slice->rn_order = IMPORT_ORDER_PREFERRED_2;
+ slice->rn_labelpaths = B_FALSE;
+ pthread_mutex_lock(rn->rn_lock);
+ if (avl_find(rn->rn_avl, slice, &where)) {
+ pthread_mutex_unlock(rn->rn_lock);
+ free(slice->rn_name);
+ free(slice);
+ } else {
+ avl_insert(rn->rn_avl, slice, where);
+ pthread_mutex_unlock(rn->rn_lock);
+ zpool_open_func(slice);
+ }
+ }
+ }
+}
+
+static char *
+zpool_default_import_path[DEFAULT_IMPORT_PATH_SIZE] = {
+ "/dev/disk/by-vdev", /* Custom rules, use first if they exist */
+ "/dev/mapper", /* Use multipath devices before components */
+ "/dev/disk/by-partlabel", /* Single unique entry set by user */
+ "/dev/disk/by-partuuid", /* Generated partition uuid */
+ "/dev/disk/by-label", /* Custom persistent labels */
+ "/dev/disk/by-uuid", /* Single unique entry and persistent */
+ "/dev/disk/by-id", /* May be multiple entries and persistent */
+ "/dev/disk/by-path", /* Encodes physical location and persistent */
+ "/dev" /* UNSAFE device names will change */
+};
+
+const char * const *
+zpool_default_search_paths(size_t *count)
+{
+ *count = DEFAULT_IMPORT_PATH_SIZE;
+ return ((const char * const *)zpool_default_import_path);
+}
+
+/*
+ * Given a full path to a device determine if that device appears in the
+ * import search path. If it does return the first match and store the
+ * index in the passed 'order' variable, otherwise return an error.
+ */
+static int
+zfs_path_order(char *name, int *order)
+{
+ int i = 0, error = ENOENT;
+ char *dir, *env, *envdup;
+
+ env = getenv("ZPOOL_IMPORT_PATH");
+ if (env) {
+ envdup = strdup(env);
+ dir = strtok(envdup, ":");
+ while (dir) {
+ if (strncmp(name, dir, strlen(dir)) == 0) {
+ *order = i;
+ error = 0;
+ break;
+ }
+ dir = strtok(NULL, ":");
+ i++;
+ }
+ free(envdup);
+ } else {
+ for (i = 0; i < DEFAULT_IMPORT_PATH_SIZE; i++) {
+ if (strncmp(name, zpool_default_import_path[i],
+ strlen(zpool_default_import_path[i])) == 0) {
+ *order = i;
+ error = 0;
+ break;
+ }
+ }
+ }
+
+ return (error);
+}
+
+/*
+ * Use libblkid to quickly enumerate all known zfs devices.
+ */
+int
+zpool_find_import_blkid(libpc_handle_t *hdl, pthread_mutex_t *lock,
+ avl_tree_t **slice_cache)
+{
+ rdsk_node_t *slice;
+ blkid_cache cache;
+ blkid_dev_iterate iter;
+ blkid_dev dev;
+ avl_index_t where;
+ int error;
+
+ *slice_cache = NULL;
+
+ error = blkid_get_cache(&cache, NULL);
+ if (error != 0)
+ return (error);
+
+ error = blkid_probe_all_new(cache);
+ if (error != 0) {
+ blkid_put_cache(cache);
+ return (error);
+ }
+
+ iter = blkid_dev_iterate_begin(cache);
+ if (iter == NULL) {
+ blkid_put_cache(cache);
+ return (EINVAL);
+ }
+
+ error = blkid_dev_set_search(iter, "TYPE", "zfs_member");
+ if (error != 0) {
+ blkid_dev_iterate_end(iter);
+ blkid_put_cache(cache);
+ return (error);
+ }
+
+ *slice_cache = zutil_alloc(hdl, sizeof (avl_tree_t));
+ avl_create(*slice_cache, slice_cache_compare, sizeof (rdsk_node_t),
+ offsetof(rdsk_node_t, rn_node));
+
+ while (blkid_dev_next(iter, &dev) == 0) {
+ slice = zutil_alloc(hdl, sizeof (rdsk_node_t));
+ slice->rn_name = zutil_strdup(hdl, blkid_dev_devname(dev));
+ slice->rn_vdev_guid = 0;
+ slice->rn_lock = lock;
+ slice->rn_avl = *slice_cache;
+ slice->rn_hdl = hdl;
+ slice->rn_labelpaths = B_TRUE;
+
+ error = zfs_path_order(slice->rn_name, &slice->rn_order);
+ if (error == 0)
+ slice->rn_order += IMPORT_ORDER_SCAN_OFFSET;
+ else
+ slice->rn_order = IMPORT_ORDER_DEFAULT;
+
+ pthread_mutex_lock(lock);
+ if (avl_find(*slice_cache, slice, &where)) {
+ free(slice->rn_name);
+ free(slice);
+ } else {
+ avl_insert(*slice_cache, slice, where);
+ }
+ pthread_mutex_unlock(lock);
+ }
+
+ blkid_dev_iterate_end(iter);
+ blkid_put_cache(cache);
+
+ return (0);
+}
+
+/*
+ * Linux persistent device strings for vdev labels
+ *
+ * based on libudev for consistency with libudev disk add/remove events
+ */
+
+typedef struct vdev_dev_strs {
+ char vds_devid[128];
+ char vds_devphys[128];
+} vdev_dev_strs_t;
+
+#ifdef HAVE_LIBUDEV
+
+/*
+ * Obtain the persistent device id string (describes what)
+ *
+ * used by ZED vdev matching for auto-{online,expand,replace}
+ */
+int
+zfs_device_get_devid(struct udev_device *dev, char *bufptr, size_t buflen)
+{
+ struct udev_list_entry *entry;
+ const char *bus;
+ char devbyid[MAXPATHLEN];
+
+ /* The bus based by-id path is preferred */
+ bus = udev_device_get_property_value(dev, "ID_BUS");
+
+ if (bus == NULL) {
+ const char *dm_uuid;
+
+ /*
+ * For multipath nodes use the persistent uuid based identifier
+ *
+ * Example: /dev/disk/by-id/dm-uuid-mpath-35000c5006304de3f
+ */
+ dm_uuid = udev_device_get_property_value(dev, "DM_UUID");
+ if (dm_uuid != NULL) {
+ (void) snprintf(bufptr, buflen, "dm-uuid-%s", dm_uuid);
+ return (0);
+ }
+
+ /*
+ * For volumes use the persistent /dev/zvol/dataset identifier
+ */
+ entry = udev_device_get_devlinks_list_entry(dev);
+ while (entry != NULL) {
+ const char *name;
+
+ name = udev_list_entry_get_name(entry);
+ if (strncmp(name, ZVOL_ROOT, strlen(ZVOL_ROOT)) == 0) {
+ (void) strlcpy(bufptr, name, buflen);
+ return (0);
+ }
+ entry = udev_list_entry_get_next(entry);
+ }
+
+ /*
+ * NVME 'by-id' symlinks are similar to bus case
+ */
+ struct udev_device *parent;
+
+ parent = udev_device_get_parent_with_subsystem_devtype(dev,
+ "nvme", NULL);
+ if (parent != NULL)
+ bus = "nvme"; /* continue with bus symlink search */
+ else
+ return (ENODATA);
+ }
+
+ /*
+ * locate the bus specific by-id link
+ */
+ (void) snprintf(devbyid, sizeof (devbyid), "%s%s-", DEV_BYID_PATH, bus);
+ entry = udev_device_get_devlinks_list_entry(dev);
+ while (entry != NULL) {
+ const char *name;
+
+ name = udev_list_entry_get_name(entry);
+ if (strncmp(name, devbyid, strlen(devbyid)) == 0) {
+ name += strlen(DEV_BYID_PATH);
+ (void) strlcpy(bufptr, name, buflen);
+ return (0);
+ }
+ entry = udev_list_entry_get_next(entry);
+ }
+
+ return (ENODATA);
+}
+
+/*
+ * Obtain the persistent physical location string (describes where)
+ *
+ * used by ZED vdev matching for auto-{online,expand,replace}
+ */
+int
+zfs_device_get_physical(struct udev_device *dev, char *bufptr, size_t buflen)
+{
+ const char *physpath = NULL;
+ struct udev_list_entry *entry;
+
+ /*
+ * Normal disks use ID_PATH for their physical path.
+ */
+ physpath = udev_device_get_property_value(dev, "ID_PATH");
+ if (physpath != NULL && strlen(physpath) > 0) {
+ (void) strlcpy(bufptr, physpath, buflen);
+ return (0);
+ }
+
+ /*
+ * Device mapper devices are virtual and don't have a physical
+ * path. For them we use ID_VDEV instead, which is setup via the
+ * /etc/vdev_id.conf file. ID_VDEV provides a persistent path
+ * to a virtual device. If you don't have vdev_id.conf setup,
+ * you cannot use multipath autoreplace with device mapper.
+ */
+ physpath = udev_device_get_property_value(dev, "ID_VDEV");
+ if (physpath != NULL && strlen(physpath) > 0) {
+ (void) strlcpy(bufptr, physpath, buflen);
+ return (0);
+ }
+
+ /*
+ * For ZFS volumes use the persistent /dev/zvol/dataset identifier
+ */
+ entry = udev_device_get_devlinks_list_entry(dev);
+ while (entry != NULL) {
+ physpath = udev_list_entry_get_name(entry);
+ if (strncmp(physpath, ZVOL_ROOT, strlen(ZVOL_ROOT)) == 0) {
+ (void) strlcpy(bufptr, physpath, buflen);
+ return (0);
+ }
+ entry = udev_list_entry_get_next(entry);
+ }
+
+ /*
+ * For all other devices fallback to using the by-uuid name.
+ */
+ entry = udev_device_get_devlinks_list_entry(dev);
+ while (entry != NULL) {
+ physpath = udev_list_entry_get_name(entry);
+ if (strncmp(physpath, "/dev/disk/by-uuid", 17) == 0) {
+ (void) strlcpy(bufptr, physpath, buflen);
+ return (0);
+ }
+ entry = udev_list_entry_get_next(entry);
+ }
+
+ return (ENODATA);
+}
+
+/*
+ * A disk is considered a multipath whole disk when:
+ * DEVNAME key value has "dm-"
+ * DM_NAME key value has "mpath" prefix
+ * DM_UUID key exists
+ * ID_PART_TABLE_TYPE key does not exist or is not gpt
+ */
+static boolean_t
+udev_mpath_whole_disk(struct udev_device *dev)
+{
+ const char *devname, *type, *uuid;
+
+ devname = udev_device_get_property_value(dev, "DEVNAME");
+ type = udev_device_get_property_value(dev, "ID_PART_TABLE_TYPE");
+ uuid = udev_device_get_property_value(dev, "DM_UUID");
+
+ if ((devname != NULL && strncmp(devname, "/dev/dm-", 8) == 0) &&
+ ((type == NULL) || (strcmp(type, "gpt") != 0)) &&
+ (uuid != NULL)) {
+ return (B_TRUE);
+ }
+
+ return (B_FALSE);
+}
+
+static int
+udev_device_is_ready(struct udev_device *dev)
+{
+#ifdef HAVE_LIBUDEV_UDEV_DEVICE_GET_IS_INITIALIZED
+ return (udev_device_get_is_initialized(dev));
+#else
+ /* wait for DEVLINKS property to be initialized */
+ return (udev_device_get_property_value(dev, "DEVLINKS") != NULL);
+#endif
+}
+
+#else
+
+/* ARGSUSED */
+int
+zfs_device_get_devid(struct udev_device *dev, char *bufptr, size_t buflen)
+{
+ return (ENODATA);
+}
+
+/* ARGSUSED */
+int
+zfs_device_get_physical(struct udev_device *dev, char *bufptr, size_t buflen)
+{
+ return (ENODATA);
+}
+
+#endif /* HAVE_LIBUDEV */
+
+/*
+ * Wait up to timeout_ms for udev to set up the device node. The device is
+ * considered ready when libudev determines it has been initialized, all of
+ * the device links have been verified to exist, and it has been allowed to
+ * settle. At this point the device the device can be accessed reliably.
+ * Depending on the complexity of the udev rules this process could take
+ * several seconds.
+ */
+int
+zpool_label_disk_wait(const char *path, int timeout_ms)
+{
+#ifdef HAVE_LIBUDEV
+ struct udev *udev;
+ struct udev_device *dev = NULL;
+ char nodepath[MAXPATHLEN];
+ char *sysname = NULL;
+ int ret = ENODEV;
+ int settle_ms = 50;
+ long sleep_ms = 10;
+ hrtime_t start, settle;
+
+ if ((udev = udev_new()) == NULL)
+ return (ENXIO);
+
+ start = gethrtime();
+ settle = 0;
+
+ do {
+ if (sysname == NULL) {
+ if (realpath(path, nodepath) != NULL) {
+ sysname = strrchr(nodepath, '/') + 1;
+ } else {
+ (void) usleep(sleep_ms * MILLISEC);
+ continue;
+ }
+ }
+
+ dev = udev_device_new_from_subsystem_sysname(udev,
+ "block", sysname);
+ if ((dev != NULL) && udev_device_is_ready(dev)) {
+ struct udev_list_entry *links, *link = NULL;
+
+ ret = 0;
+ links = udev_device_get_devlinks_list_entry(dev);
+
+ udev_list_entry_foreach(link, links) {
+ struct stat64 statbuf;
+ const char *name;
+
+ name = udev_list_entry_get_name(link);
+ errno = 0;
+ if (stat64(name, &statbuf) == 0 && errno == 0)
+ continue;
+
+ settle = 0;
+ ret = ENODEV;
+ break;
+ }
+
+ if (ret == 0) {
+ if (settle == 0) {
+ settle = gethrtime();
+ } else if (NSEC2MSEC(gethrtime() - settle) >=
+ settle_ms) {
+ udev_device_unref(dev);
+ break;
+ }
+ }
+ }
+
+ udev_device_unref(dev);
+ (void) usleep(sleep_ms * MILLISEC);
+
+ } while (NSEC2MSEC(gethrtime() - start) < timeout_ms);
+
+ udev_unref(udev);
+
+ return (ret);
+#else
+ int settle_ms = 50;
+ long sleep_ms = 10;
+ hrtime_t start, settle;
+ struct stat64 statbuf;
+
+ start = gethrtime();
+ settle = 0;
+
+ do {
+ errno = 0;
+ if ((stat64(path, &statbuf) == 0) && (errno == 0)) {
+ if (settle == 0)
+ settle = gethrtime();
+ else if (NSEC2MSEC(gethrtime() - settle) >= settle_ms)
+ return (0);
+ } else if (errno != ENOENT) {
+ return (errno);
+ }
+
+ usleep(sleep_ms * MILLISEC);
+ } while (NSEC2MSEC(gethrtime() - start) < timeout_ms);
+
+ return (ENODEV);
+#endif /* HAVE_LIBUDEV */
+}
+
+/*
+ * Encode the persistent devices strings
+ * used for the vdev disk label
+ */
+static int
+encode_device_strings(const char *path, vdev_dev_strs_t *ds,
+ boolean_t wholedisk)
+{
+#ifdef HAVE_LIBUDEV
+ struct udev *udev;
+ struct udev_device *dev = NULL;
+ char nodepath[MAXPATHLEN];
+ char *sysname;
+ int ret = ENODEV;
+ hrtime_t start;
+
+ if ((udev = udev_new()) == NULL)
+ return (ENXIO);
+
+ /* resolve path to a runtime device node instance */
+ if (realpath(path, nodepath) == NULL)
+ goto no_dev;
+
+ sysname = strrchr(nodepath, '/') + 1;
+
+ /*
+ * Wait up to 3 seconds for udev to set up the device node context
+ */
+ start = gethrtime();
+ do {
+ dev = udev_device_new_from_subsystem_sysname(udev, "block",
+ sysname);
+ if (dev == NULL)
+ goto no_dev;
+ if (udev_device_is_ready(dev))
+ break; /* udev ready */
+
+ udev_device_unref(dev);
+ dev = NULL;
+
+ if (NSEC2MSEC(gethrtime() - start) < 10)
+ (void) sched_yield(); /* yield/busy wait up to 10ms */
+ else
+ (void) usleep(10 * MILLISEC);
+
+ } while (NSEC2MSEC(gethrtime() - start) < (3 * MILLISEC));
+
+ if (dev == NULL)
+ goto no_dev;
+
+ /*
+ * Only whole disks require extra device strings
+ */
+ if (!wholedisk && !udev_mpath_whole_disk(dev))
+ goto no_dev;
+
+ ret = zfs_device_get_devid(dev, ds->vds_devid, sizeof (ds->vds_devid));
+ if (ret != 0)
+ goto no_dev_ref;
+
+ /* physical location string (optional) */
+ if (zfs_device_get_physical(dev, ds->vds_devphys,
+ sizeof (ds->vds_devphys)) != 0) {
+ ds->vds_devphys[0] = '\0'; /* empty string --> not available */
+ }
+
+no_dev_ref:
+ udev_device_unref(dev);
+no_dev:
+ udev_unref(udev);
+
+ return (ret);
+#else
+ return (ENOENT);
+#endif
+}
+
+/*
+ * Update a leaf vdev's persistent device strings
+ *
+ * - only applies for a dedicated leaf vdev (aka whole disk)
+ * - updated during pool create|add|attach|import
+ * - used for matching device matching during auto-{online,expand,replace}
+ * - stored in a leaf disk config label (i.e. alongside 'path' NVP)
+ * - these strings are currently not used in kernel (i.e. for vdev_disk_open)
+ *
+ * single device node example:
+ * devid: 'scsi-MG03SCA300_350000494a8cb3d67-part1'
+ * phys_path: 'pci-0000:04:00.0-sas-0x50000394a8cb3d67-lun-0'
+ *
+ * multipath device node example:
+ * devid: 'dm-uuid-mpath-35000c5006304de3f'
+ *
+ * We also store the enclosure sysfs path for turning on enclosure LEDs
+ * (if applicable):
+ * vdev_enc_sysfs_path: '/sys/class/enclosure/11:0:1:0/SLOT 4'
+ */
+void
+update_vdev_config_dev_strs(nvlist_t *nv)
+{
+ vdev_dev_strs_t vds;
+ char *env, *type, *path;
+ uint64_t wholedisk = 0;
+ char *upath, *spath;
+
+ /*
+ * For the benefit of legacy ZFS implementations, allow
+ * for opting out of devid strings in the vdev label.
+ *
+ * example use:
+ * env ZFS_VDEV_DEVID_OPT_OUT=YES zpool import dozer
+ *
+ * explanation:
+ * Older ZFS on Linux implementations had issues when attempting to
+ * display pool config VDEV names if a "devid" NVP value is present
+ * in the pool's config.
+ *
+ * For example, a pool that originated on illumos platform would
+ * have a devid value in the config and "zpool status" would fail
+ * when listing the config.
+ *
+ * A pool can be stripped of any "devid" values on import or
+ * prevented from adding them on zpool create|add by setting
+ * ZFS_VDEV_DEVID_OPT_OUT.
+ */
+ env = getenv("ZFS_VDEV_DEVID_OPT_OUT");
+ if (env && (strtoul(env, NULL, 0) > 0 ||
+ !strncasecmp(env, "YES", 3) || !strncasecmp(env, "ON", 2))) {
+ (void) nvlist_remove_all(nv, ZPOOL_CONFIG_DEVID);
+ (void) nvlist_remove_all(nv, ZPOOL_CONFIG_PHYS_PATH);
+ return;
+ }
+
+ if (nvlist_lookup_string(nv, ZPOOL_CONFIG_TYPE, &type) != 0 ||
+ strcmp(type, VDEV_TYPE_DISK) != 0) {
+ return;
+ }
+ if (nvlist_lookup_string(nv, ZPOOL_CONFIG_PATH, &path) != 0)
+ return;
+ (void) nvlist_lookup_uint64(nv, ZPOOL_CONFIG_WHOLE_DISK, &wholedisk);
+
+ /*
+ * Update device string values in the config nvlist.
+ */
+ if (encode_device_strings(path, &vds, (boolean_t)wholedisk) == 0) {
+ (void) nvlist_add_string(nv, ZPOOL_CONFIG_DEVID, vds.vds_devid);
+ if (vds.vds_devphys[0] != '\0') {
+ (void) nvlist_add_string(nv, ZPOOL_CONFIG_PHYS_PATH,
+ vds.vds_devphys);
+ }
+
+ /* Add enclosure sysfs path (if disk is in an enclosure). */
+ upath = zfs_get_underlying_path(path);
+ spath = zfs_get_enclosure_sysfs_path(upath);
+ if (spath)
+ nvlist_add_string(nv, ZPOOL_CONFIG_VDEV_ENC_SYSFS_PATH,
+ spath);
+ else
+ nvlist_remove_all(nv, ZPOOL_CONFIG_VDEV_ENC_SYSFS_PATH);
+
+ free(upath);
+ free(spath);
+ } else {
+ /* Clear out any stale entries. */
+ (void) nvlist_remove_all(nv, ZPOOL_CONFIG_DEVID);
+ (void) nvlist_remove_all(nv, ZPOOL_CONFIG_PHYS_PATH);
+ (void) nvlist_remove_all(nv, ZPOOL_CONFIG_VDEV_ENC_SYSFS_PATH);
+ }
+}
diff --git a/lib/libzutil/zutil_device_path.c b/lib/libzutil/zutil_device_path.c
new file mode 100644
index 000000000000..27ca80e5040c
--- /dev/null
+++ b/lib/libzutil/zutil_device_path.c
@@ -0,0 +1,174 @@
+/*
+ * 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) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <libzutil.h>
+
+/*
+ * Given a shorthand device name check if a file by that name exists in any
+ * of the 'zpool_default_import_path' or ZPOOL_IMPORT_PATH directories. If
+ * one is found, store its fully qualified path in the 'path' buffer passed
+ * by the caller and return 0, otherwise return an error.
+ */
+int
+zfs_resolve_shortname(const char *name, char *path, size_t len)
+{
+ int i, error = -1;
+ char *dir, *env, *envdup;
+
+ env = getenv("ZPOOL_IMPORT_PATH");
+ errno = ENOENT;
+
+ if (env) {
+ envdup = strdup(env);
+ dir = strtok(envdup, ":");
+ while (dir && error) {
+ (void) snprintf(path, len, "%s/%s", dir, name);
+ error = access(path, F_OK);
+ dir = strtok(NULL, ":");
+ }
+ free(envdup);
+ } else {
+ const char * const *zpool_default_import_path;
+ size_t count;
+
+ zpool_default_import_path = zpool_default_search_paths(&count);
+
+ for (i = 0; i < count && error < 0; i++) {
+ (void) snprintf(path, len, "%s/%s",
+ zpool_default_import_path[i], name);
+ error = access(path, F_OK);
+ }
+ }
+
+ return (error ? ENOENT : 0);
+}
+
+/*
+ * Given a shorthand device name look for a match against 'cmp_name'. This
+ * is done by checking all prefix expansions using either the default
+ * 'zpool_default_import_paths' or the ZPOOL_IMPORT_PATH environment
+ * variable. Proper partition suffixes will be appended if this is a
+ * whole disk. When a match is found 0 is returned otherwise ENOENT.
+ */
+static int
+zfs_strcmp_shortname(const char *name, const char *cmp_name, int wholedisk)
+{
+ int path_len, cmp_len, i = 0, error = ENOENT;
+ char *dir, *env, *envdup = NULL;
+ char path_name[MAXPATHLEN];
+ const char * const *zpool_default_import_path;
+ size_t count;
+
+ zpool_default_import_path = zpool_default_search_paths(&count);
+
+ cmp_len = strlen(cmp_name);
+ env = getenv("ZPOOL_IMPORT_PATH");
+
+ if (env) {
+ envdup = strdup(env);
+ dir = strtok(envdup, ":");
+ } else {
+ dir = (char *)zpool_default_import_path[i];
+ }
+
+ while (dir) {
+ /* Trim trailing directory slashes from ZPOOL_IMPORT_PATH */
+ if (env) {
+ while (dir[strlen(dir)-1] == '/')
+ dir[strlen(dir)-1] = '\0';
+ }
+
+ path_len = snprintf(path_name, MAXPATHLEN, "%s/%s", dir, name);
+ if (wholedisk)
+ path_len = zfs_append_partition(path_name, MAXPATHLEN);
+
+ if ((path_len == cmp_len) && strcmp(path_name, cmp_name) == 0) {
+ error = 0;
+ break;
+ }
+
+ if (env) {
+ dir = strtok(NULL, ":");
+ } else if (++i < count) {
+ dir = (char *)zpool_default_import_path[i];
+ } else {
+ dir = NULL;
+ }
+ }
+
+ if (env)
+ free(envdup);
+
+ return (error);
+}
+
+/*
+ * Given either a shorthand or fully qualified path name look for a match
+ * against 'cmp'. The passed name will be expanded as needed for comparison
+ * purposes and redundant slashes stripped to ensure an accurate match.
+ */
+int
+zfs_strcmp_pathname(const char *name, const char *cmp, int wholedisk)
+{
+ int path_len, cmp_len;
+ char path_name[MAXPATHLEN];
+ char cmp_name[MAXPATHLEN];
+ char *dir, *dup;
+
+ /* Strip redundant slashes if one exists due to ZPOOL_IMPORT_PATH */
+ memset(cmp_name, 0, MAXPATHLEN);
+ dup = strdup(cmp);
+ dir = strtok(dup, "/");
+ while (dir) {
+ strlcat(cmp_name, "/", sizeof (cmp_name));
+ strlcat(cmp_name, dir, sizeof (cmp_name));
+ dir = strtok(NULL, "/");
+ }
+ free(dup);
+
+ if (name[0] != '/')
+ return (zfs_strcmp_shortname(name, cmp_name, wholedisk));
+
+ (void) strlcpy(path_name, name, MAXPATHLEN);
+ path_len = strlen(path_name);
+ cmp_len = strlen(cmp_name);
+
+ if (wholedisk) {
+ path_len = zfs_append_partition(path_name, MAXPATHLEN);
+ if (path_len == -1)
+ return (ENOMEM);
+ }
+
+ if ((path_len != cmp_len) || strcmp(path_name, cmp_name))
+ return (ENOENT);
+
+ return (0);
+}
diff --git a/lib/libzutil/zutil_import.c b/lib/libzutil/zutil_import.c
new file mode 100644
index 000000000000..b8cdc118b263
--- /dev/null
+++ b/lib/libzutil/zutil_import.c
@@ -0,0 +1,1587 @@
+/*
+ * 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 2015 Nexenta Systems, Inc. All rights reserved.
+ * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2018 by Delphix. All rights reserved.
+ * Copyright 2015 RackTop Systems.
+ * Copyright (c) 2016, Intel Corporation.
+ */
+
+/*
+ * Pool import support functions.
+ *
+ * Used by zpool, ztest, zdb, and zhack to locate importable configs. Since
+ * these commands are expected to run in the global zone, we can assume
+ * that the devices are all readable when called.
+ *
+ * To import a pool, we rely on reading the configuration information from the
+ * ZFS label of each device. If we successfully read the label, then we
+ * organize the configuration information in the following hierarchy:
+ *
+ * pool guid -> toplevel vdev guid -> label txg
+ *
+ * Duplicate entries matching this same tuple will be discarded. Once we have
+ * examined every device, we pick the best label txg config for each toplevel
+ * vdev. We then arrange these toplevel vdevs into a complete pool config, and
+ * update any paths that have changed. Finally, we attempt to import the pool
+ * using our derived config, and record the results.
+ */
+
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <libintl.h>
+#include <libgen.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/dktp/fdisk.h>
+#include <sys/vdev_impl.h>
+#include <sys/fs/zfs.h>
+#include <sys/vdev_impl.h>
+
+#include <thread_pool.h>
+#include <libzutil.h>
+#include <libnvpair.h>
+
+#include "zutil_import.h"
+
+/*PRINTFLIKE2*/
+static void
+zutil_error_aux(libpc_handle_t *hdl, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+
+ (void) vsnprintf(hdl->lpc_desc, sizeof (hdl->lpc_desc), fmt, ap);
+ hdl->lpc_desc_active = B_TRUE;
+
+ va_end(ap);
+}
+
+static void
+zutil_verror(libpc_handle_t *hdl, const char *error, const char *fmt,
+ va_list ap)
+{
+ char action[1024];
+
+ (void) vsnprintf(action, sizeof (action), fmt, ap);
+
+ if (hdl->lpc_desc_active)
+ hdl->lpc_desc_active = B_FALSE;
+ else
+ hdl->lpc_desc[0] = '\0';
+
+ if (hdl->lpc_printerr) {
+ if (hdl->lpc_desc[0] != '\0')
+ error = hdl->lpc_desc;
+
+ (void) fprintf(stderr, "%s: %s\n", action, error);
+ }
+}
+
+/*PRINTFLIKE3*/
+static int
+zutil_error_fmt(libpc_handle_t *hdl, const char *error, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+
+ zutil_verror(hdl, error, fmt, ap);
+
+ va_end(ap);
+
+ return (-1);
+}
+
+static int
+zutil_error(libpc_handle_t *hdl, const char *error, const char *msg)
+{
+ return (zutil_error_fmt(hdl, error, "%s", msg));
+}
+
+static int
+zutil_no_memory(libpc_handle_t *hdl)
+{
+ zutil_error(hdl, EZFS_NOMEM, "internal error");
+ exit(1);
+}
+
+void *
+zutil_alloc(libpc_handle_t *hdl, size_t size)
+{
+ void *data;
+
+ if ((data = calloc(1, size)) == NULL)
+ (void) zutil_no_memory(hdl);
+
+ return (data);
+}
+
+char *
+zutil_strdup(libpc_handle_t *hdl, const char *str)
+{
+ char *ret;
+
+ if ((ret = strdup(str)) == NULL)
+ (void) zutil_no_memory(hdl);
+
+ return (ret);
+}
+
+/*
+ * Intermediate structures used to gather configuration information.
+ */
+typedef struct config_entry {
+ uint64_t ce_txg;
+ nvlist_t *ce_config;
+ struct config_entry *ce_next;
+} config_entry_t;
+
+typedef struct vdev_entry {
+ uint64_t ve_guid;
+ config_entry_t *ve_configs;
+ struct vdev_entry *ve_next;
+} vdev_entry_t;
+
+typedef struct pool_entry {
+ uint64_t pe_guid;
+ vdev_entry_t *pe_vdevs;
+ struct pool_entry *pe_next;
+} pool_entry_t;
+
+typedef struct name_entry {
+ char *ne_name;
+ uint64_t ne_guid;
+ uint64_t ne_order;
+ uint64_t ne_num_labels;
+ struct name_entry *ne_next;
+} name_entry_t;
+
+typedef struct pool_list {
+ pool_entry_t *pools;
+ name_entry_t *names;
+} pool_list_t;
+
+/*
+ * Go through and fix up any path and/or devid information for the given vdev
+ * configuration.
+ */
+static int
+fix_paths(libpc_handle_t *hdl, nvlist_t *nv, name_entry_t *names)
+{
+ nvlist_t **child;
+ uint_t c, children;
+ uint64_t guid;
+ name_entry_t *ne, *best;
+ char *path;
+
+ if (nvlist_lookup_nvlist_array(nv, ZPOOL_CONFIG_CHILDREN,
+ &child, &children) == 0) {
+ for (c = 0; c < children; c++)
+ if (fix_paths(hdl, child[c], names) != 0)
+ return (-1);
+ return (0);
+ }
+
+ /*
+ * This is a leaf (file or disk) vdev. In either case, go through
+ * the name list and see if we find a matching guid. If so, replace
+ * the path and see if we can calculate a new devid.
+ *
+ * There may be multiple names associated with a particular guid, in
+ * which case we have overlapping partitions or multiple paths to the
+ * same disk. In this case we prefer to use the path name which
+ * matches the ZPOOL_CONFIG_PATH. If no matching entry is found we
+ * use the lowest order device which corresponds to the first match
+ * while traversing the ZPOOL_IMPORT_PATH search path.
+ */
+ verify(nvlist_lookup_uint64(nv, ZPOOL_CONFIG_GUID, &guid) == 0);
+ if (nvlist_lookup_string(nv, ZPOOL_CONFIG_PATH, &path) != 0)
+ path = NULL;
+
+ best = NULL;
+ for (ne = names; ne != NULL; ne = ne->ne_next) {
+ if (ne->ne_guid == guid) {
+ if (path == NULL) {
+ best = ne;
+ break;
+ }
+
+ if ((strlen(path) == strlen(ne->ne_name)) &&
+ strncmp(path, ne->ne_name, strlen(path)) == 0) {
+ best = ne;
+ break;
+ }
+
+ if (best == NULL) {
+ best = ne;
+ continue;
+ }
+
+ /* Prefer paths with move vdev labels. */
+ if (ne->ne_num_labels > best->ne_num_labels) {
+ best = ne;
+ continue;
+ }
+
+ /* Prefer paths earlier in the search order. */
+ if (ne->ne_num_labels == best->ne_num_labels &&
+ ne->ne_order < best->ne_order) {
+ best = ne;
+ continue;
+ }
+ }
+ }
+
+ if (best == NULL)
+ return (0);
+
+ if (nvlist_add_string(nv, ZPOOL_CONFIG_PATH, best->ne_name) != 0)
+ return (-1);
+
+ update_vdev_config_dev_strs(nv);
+
+ return (0);
+}
+
+/*
+ * Add the given configuration to the list of known devices.
+ */
+static int
+add_config(libpc_handle_t *hdl, pool_list_t *pl, const char *path,
+ int order, int num_labels, nvlist_t *config)
+{
+ uint64_t pool_guid, vdev_guid, top_guid, txg, state;
+ pool_entry_t *pe;
+ vdev_entry_t *ve;
+ config_entry_t *ce;
+ name_entry_t *ne;
+
+ /*
+ * If this is a hot spare not currently in use or level 2 cache
+ * device, add it to the list of names to translate, but don't do
+ * anything else.
+ */
+ if (nvlist_lookup_uint64(config, ZPOOL_CONFIG_POOL_STATE,
+ &state) == 0 &&
+ (state == POOL_STATE_SPARE || state == POOL_STATE_L2CACHE) &&
+ nvlist_lookup_uint64(config, ZPOOL_CONFIG_GUID, &vdev_guid) == 0) {
+ if ((ne = zutil_alloc(hdl, sizeof (name_entry_t))) == NULL)
+ return (-1);
+
+ if ((ne->ne_name = zutil_strdup(hdl, path)) == NULL) {
+ free(ne);
+ return (-1);
+ }
+ ne->ne_guid = vdev_guid;
+ ne->ne_order = order;
+ ne->ne_num_labels = num_labels;
+ ne->ne_next = pl->names;
+ pl->names = ne;
+
+ return (0);
+ }
+
+ /*
+ * If we have a valid config but cannot read any of these fields, then
+ * it means we have a half-initialized label. In vdev_label_init()
+ * we write a label with txg == 0 so that we can identify the device
+ * in case the user refers to the same disk later on. If we fail to
+ * create the pool, we'll be left with a label in this state
+ * which should not be considered part of a valid pool.
+ */
+ if (nvlist_lookup_uint64(config, ZPOOL_CONFIG_POOL_GUID,
+ &pool_guid) != 0 ||
+ nvlist_lookup_uint64(config, ZPOOL_CONFIG_GUID,
+ &vdev_guid) != 0 ||
+ nvlist_lookup_uint64(config, ZPOOL_CONFIG_TOP_GUID,
+ &top_guid) != 0 ||
+ nvlist_lookup_uint64(config, ZPOOL_CONFIG_POOL_TXG,
+ &txg) != 0 || txg == 0) {
+ return (0);
+ }
+
+ /*
+ * First, see if we know about this pool. If not, then add it to the
+ * list of known pools.
+ */
+ for (pe = pl->pools; pe != NULL; pe = pe->pe_next) {
+ if (pe->pe_guid == pool_guid)
+ break;
+ }
+
+ if (pe == NULL) {
+ if ((pe = zutil_alloc(hdl, sizeof (pool_entry_t))) == NULL) {
+ return (-1);
+ }
+ pe->pe_guid = pool_guid;
+ pe->pe_next = pl->pools;
+ pl->pools = pe;
+ }
+
+ /*
+ * Second, see if we know about this toplevel vdev. Add it if its
+ * missing.
+ */
+ for (ve = pe->pe_vdevs; ve != NULL; ve = ve->ve_next) {
+ if (ve->ve_guid == top_guid)
+ break;
+ }
+
+ if (ve == NULL) {
+ if ((ve = zutil_alloc(hdl, sizeof (vdev_entry_t))) == NULL) {
+ return (-1);
+ }
+ ve->ve_guid = top_guid;
+ ve->ve_next = pe->pe_vdevs;
+ pe->pe_vdevs = ve;
+ }
+
+ /*
+ * Third, see if we have a config with a matching transaction group. If
+ * so, then we do nothing. Otherwise, add it to the list of known
+ * configs.
+ */
+ for (ce = ve->ve_configs; ce != NULL; ce = ce->ce_next) {
+ if (ce->ce_txg == txg)
+ break;
+ }
+
+ if (ce == NULL) {
+ if ((ce = zutil_alloc(hdl, sizeof (config_entry_t))) == NULL) {
+ return (-1);
+ }
+ ce->ce_txg = txg;
+ ce->ce_config = fnvlist_dup(config);
+ ce->ce_next = ve->ve_configs;
+ ve->ve_configs = ce;
+ }
+
+ /*
+ * At this point we've successfully added our config to the list of
+ * known configs. The last thing to do is add the vdev guid -> path
+ * mappings so that we can fix up the configuration as necessary before
+ * doing the import.
+ */
+ if ((ne = zutil_alloc(hdl, sizeof (name_entry_t))) == NULL)
+ return (-1);
+
+ if ((ne->ne_name = zutil_strdup(hdl, path)) == NULL) {
+ free(ne);
+ return (-1);
+ }
+
+ ne->ne_guid = vdev_guid;
+ ne->ne_order = order;
+ ne->ne_num_labels = num_labels;
+ ne->ne_next = pl->names;
+ pl->names = ne;
+
+ return (0);
+}
+
+static int
+zutil_pool_active(libpc_handle_t *hdl, const char *name, uint64_t guid,
+ boolean_t *isactive)
+{
+ ASSERT(hdl->lpc_ops->pco_pool_active != NULL);
+
+ int error = hdl->lpc_ops->pco_pool_active(hdl->lpc_lib_handle, name,
+ guid, isactive);
+
+ return (error);
+}
+
+static nvlist_t *
+zutil_refresh_config(libpc_handle_t *hdl, nvlist_t *tryconfig)
+{
+ ASSERT(hdl->lpc_ops->pco_refresh_config != NULL);
+
+ return (hdl->lpc_ops->pco_refresh_config(hdl->lpc_lib_handle,
+ tryconfig));
+}
+
+/*
+ * Determine if the vdev id is a hole in the namespace.
+ */
+static boolean_t
+vdev_is_hole(uint64_t *hole_array, uint_t holes, uint_t id)
+{
+ int c;
+
+ for (c = 0; c < holes; c++) {
+
+ /* Top-level is a hole */
+ if (hole_array[c] == id)
+ return (B_TRUE);
+ }
+ return (B_FALSE);
+}
+
+/*
+ * Convert our list of pools into the definitive set of configurations. We
+ * start by picking the best config for each toplevel vdev. Once that's done,
+ * we assemble the toplevel vdevs into a full config for the pool. We make a
+ * pass to fix up any incorrect paths, and then add it to the main list to
+ * return to the user.
+ */
+static nvlist_t *
+get_configs(libpc_handle_t *hdl, pool_list_t *pl, boolean_t active_ok,
+ nvlist_t *policy)
+{
+ pool_entry_t *pe;
+ vdev_entry_t *ve;
+ config_entry_t *ce;
+ nvlist_t *ret = NULL, *config = NULL, *tmp = NULL, *nvtop, *nvroot;
+ nvlist_t **spares, **l2cache;
+ uint_t i, nspares, nl2cache;
+ boolean_t config_seen;
+ uint64_t best_txg;
+ char *name, *hostname = NULL;
+ uint64_t guid;
+ uint_t children = 0;
+ nvlist_t **child = NULL;
+ uint_t holes;
+ uint64_t *hole_array, max_id;
+ uint_t c;
+ boolean_t isactive;
+ uint64_t hostid;
+ nvlist_t *nvl;
+ boolean_t valid_top_config = B_FALSE;
+
+ if (nvlist_alloc(&ret, 0, 0) != 0)
+ goto nomem;
+
+ for (pe = pl->pools; pe != NULL; pe = pe->pe_next) {
+ uint64_t id, max_txg = 0;
+
+ if (nvlist_alloc(&config, NV_UNIQUE_NAME, 0) != 0)
+ goto nomem;
+ config_seen = B_FALSE;
+
+ /*
+ * Iterate over all toplevel vdevs. Grab the pool configuration
+ * from the first one we find, and then go through the rest and
+ * add them as necessary to the 'vdevs' member of the config.
+ */
+ for (ve = pe->pe_vdevs; ve != NULL; ve = ve->ve_next) {
+
+ /*
+ * Determine the best configuration for this vdev by
+ * selecting the config with the latest transaction
+ * group.
+ */
+ best_txg = 0;
+ for (ce = ve->ve_configs; ce != NULL;
+ ce = ce->ce_next) {
+
+ if (ce->ce_txg > best_txg) {
+ tmp = ce->ce_config;
+ best_txg = ce->ce_txg;
+ }
+ }
+
+ /*
+ * We rely on the fact that the max txg for the
+ * pool will contain the most up-to-date information
+ * about the valid top-levels in the vdev namespace.
+ */
+ if (best_txg > max_txg) {
+ (void) nvlist_remove(config,
+ ZPOOL_CONFIG_VDEV_CHILDREN,
+ DATA_TYPE_UINT64);
+ (void) nvlist_remove(config,
+ ZPOOL_CONFIG_HOLE_ARRAY,
+ DATA_TYPE_UINT64_ARRAY);
+
+ max_txg = best_txg;
+ hole_array = NULL;
+ holes = 0;
+ max_id = 0;
+ valid_top_config = B_FALSE;
+
+ if (nvlist_lookup_uint64(tmp,
+ ZPOOL_CONFIG_VDEV_CHILDREN, &max_id) == 0) {
+ verify(nvlist_add_uint64(config,
+ ZPOOL_CONFIG_VDEV_CHILDREN,
+ max_id) == 0);
+ valid_top_config = B_TRUE;
+ }
+
+ if (nvlist_lookup_uint64_array(tmp,
+ ZPOOL_CONFIG_HOLE_ARRAY, &hole_array,
+ &holes) == 0) {
+ verify(nvlist_add_uint64_array(config,
+ ZPOOL_CONFIG_HOLE_ARRAY,
+ hole_array, holes) == 0);
+ }
+ }
+
+ if (!config_seen) {
+ /*
+ * Copy the relevant pieces of data to the pool
+ * configuration:
+ *
+ * version
+ * pool guid
+ * name
+ * comment (if available)
+ * pool state
+ * hostid (if available)
+ * hostname (if available)
+ */
+ uint64_t state, version;
+ char *comment = NULL;
+
+ version = fnvlist_lookup_uint64(tmp,
+ ZPOOL_CONFIG_VERSION);
+ fnvlist_add_uint64(config,
+ ZPOOL_CONFIG_VERSION, version);
+ guid = fnvlist_lookup_uint64(tmp,
+ ZPOOL_CONFIG_POOL_GUID);
+ fnvlist_add_uint64(config,
+ ZPOOL_CONFIG_POOL_GUID, guid);
+ name = fnvlist_lookup_string(tmp,
+ ZPOOL_CONFIG_POOL_NAME);
+ fnvlist_add_string(config,
+ ZPOOL_CONFIG_POOL_NAME, name);
+
+ if (nvlist_lookup_string(tmp,
+ ZPOOL_CONFIG_COMMENT, &comment) == 0)
+ fnvlist_add_string(config,
+ ZPOOL_CONFIG_COMMENT, comment);
+
+ state = fnvlist_lookup_uint64(tmp,
+ ZPOOL_CONFIG_POOL_STATE);
+ fnvlist_add_uint64(config,
+ ZPOOL_CONFIG_POOL_STATE, state);
+
+ hostid = 0;
+ if (nvlist_lookup_uint64(tmp,
+ ZPOOL_CONFIG_HOSTID, &hostid) == 0) {
+ fnvlist_add_uint64(config,
+ ZPOOL_CONFIG_HOSTID, hostid);
+ hostname = fnvlist_lookup_string(tmp,
+ ZPOOL_CONFIG_HOSTNAME);
+ fnvlist_add_string(config,
+ ZPOOL_CONFIG_HOSTNAME, hostname);
+ }
+
+ config_seen = B_TRUE;
+ }
+
+ /*
+ * Add this top-level vdev to the child array.
+ */
+ verify(nvlist_lookup_nvlist(tmp,
+ ZPOOL_CONFIG_VDEV_TREE, &nvtop) == 0);
+ verify(nvlist_lookup_uint64(nvtop, ZPOOL_CONFIG_ID,
+ &id) == 0);
+
+ if (id >= children) {
+ nvlist_t **newchild;
+
+ newchild = zutil_alloc(hdl, (id + 1) *
+ sizeof (nvlist_t *));
+ if (newchild == NULL)
+ goto nomem;
+
+ for (c = 0; c < children; c++)
+ newchild[c] = child[c];
+
+ free(child);
+ child = newchild;
+ children = id + 1;
+ }
+ if (nvlist_dup(nvtop, &child[id], 0) != 0)
+ goto nomem;
+
+ }
+
+ /*
+ * If we have information about all the top-levels then
+ * clean up the nvlist which we've constructed. This
+ * means removing any extraneous devices that are
+ * beyond the valid range or adding devices to the end
+ * of our array which appear to be missing.
+ */
+ if (valid_top_config) {
+ if (max_id < children) {
+ for (c = max_id; c < children; c++)
+ nvlist_free(child[c]);
+ children = max_id;
+ } else if (max_id > children) {
+ nvlist_t **newchild;
+
+ newchild = zutil_alloc(hdl, (max_id) *
+ sizeof (nvlist_t *));
+ if (newchild == NULL)
+ goto nomem;
+
+ for (c = 0; c < children; c++)
+ newchild[c] = child[c];
+
+ free(child);
+ child = newchild;
+ children = max_id;
+ }
+ }
+
+ verify(nvlist_lookup_uint64(config, ZPOOL_CONFIG_POOL_GUID,
+ &guid) == 0);
+
+ /*
+ * The vdev namespace may contain holes as a result of
+ * device removal. We must add them back into the vdev
+ * tree before we process any missing devices.
+ */
+ if (holes > 0) {
+ ASSERT(valid_top_config);
+
+ for (c = 0; c < children; c++) {
+ nvlist_t *holey;
+
+ if (child[c] != NULL ||
+ !vdev_is_hole(hole_array, holes, c))
+ continue;
+
+ if (nvlist_alloc(&holey, NV_UNIQUE_NAME,
+ 0) != 0)
+ goto nomem;
+
+ /*
+ * Holes in the namespace are treated as
+ * "hole" top-level vdevs and have a
+ * special flag set on them.
+ */
+ if (nvlist_add_string(holey,
+ ZPOOL_CONFIG_TYPE,
+ VDEV_TYPE_HOLE) != 0 ||
+ nvlist_add_uint64(holey,
+ ZPOOL_CONFIG_ID, c) != 0 ||
+ nvlist_add_uint64(holey,
+ ZPOOL_CONFIG_GUID, 0ULL) != 0) {
+ nvlist_free(holey);
+ goto nomem;
+ }
+ child[c] = holey;
+ }
+ }
+
+ /*
+ * Look for any missing top-level vdevs. If this is the case,
+ * create a faked up 'missing' vdev as a placeholder. We cannot
+ * simply compress the child array, because the kernel performs
+ * certain checks to make sure the vdev IDs match their location
+ * in the configuration.
+ */
+ for (c = 0; c < children; c++) {
+ if (child[c] == NULL) {
+ nvlist_t *missing;
+ if (nvlist_alloc(&missing, NV_UNIQUE_NAME,
+ 0) != 0)
+ goto nomem;
+ if (nvlist_add_string(missing,
+ ZPOOL_CONFIG_TYPE,
+ VDEV_TYPE_MISSING) != 0 ||
+ nvlist_add_uint64(missing,
+ ZPOOL_CONFIG_ID, c) != 0 ||
+ nvlist_add_uint64(missing,
+ ZPOOL_CONFIG_GUID, 0ULL) != 0) {
+ nvlist_free(missing);
+ goto nomem;
+ }
+ child[c] = missing;
+ }
+ }
+
+ /*
+ * Put all of this pool's top-level vdevs into a root vdev.
+ */
+ if (nvlist_alloc(&nvroot, NV_UNIQUE_NAME, 0) != 0)
+ goto nomem;
+ if (nvlist_add_string(nvroot, ZPOOL_CONFIG_TYPE,
+ VDEV_TYPE_ROOT) != 0 ||
+ nvlist_add_uint64(nvroot, ZPOOL_CONFIG_ID, 0ULL) != 0 ||
+ nvlist_add_uint64(nvroot, ZPOOL_CONFIG_GUID, guid) != 0 ||
+ nvlist_add_nvlist_array(nvroot, ZPOOL_CONFIG_CHILDREN,
+ child, children) != 0) {
+ nvlist_free(nvroot);
+ goto nomem;
+ }
+
+ for (c = 0; c < children; c++)
+ nvlist_free(child[c]);
+ free(child);
+ children = 0;
+ child = NULL;
+
+ /*
+ * Go through and fix up any paths and/or devids based on our
+ * known list of vdev GUID -> path mappings.
+ */
+ if (fix_paths(hdl, nvroot, pl->names) != 0) {
+ nvlist_free(nvroot);
+ goto nomem;
+ }
+
+ /*
+ * Add the root vdev to this pool's configuration.
+ */
+ if (nvlist_add_nvlist(config, ZPOOL_CONFIG_VDEV_TREE,
+ nvroot) != 0) {
+ nvlist_free(nvroot);
+ goto nomem;
+ }
+ nvlist_free(nvroot);
+
+ /*
+ * zdb uses this path to report on active pools that were
+ * imported or created using -R.
+ */
+ if (active_ok)
+ goto add_pool;
+
+ /*
+ * Determine if this pool is currently active, in which case we
+ * can't actually import it.
+ */
+ verify(nvlist_lookup_string(config, ZPOOL_CONFIG_POOL_NAME,
+ &name) == 0);
+ verify(nvlist_lookup_uint64(config, ZPOOL_CONFIG_POOL_GUID,
+ &guid) == 0);
+
+ if (zutil_pool_active(hdl, name, guid, &isactive) != 0)
+ goto error;
+
+ if (isactive) {
+ nvlist_free(config);
+ config = NULL;
+ continue;
+ }
+
+ if (policy != NULL) {
+ if (nvlist_add_nvlist(config, ZPOOL_LOAD_POLICY,
+ policy) != 0)
+ goto nomem;
+ }
+
+ if ((nvl = zutil_refresh_config(hdl, config)) == NULL) {
+ nvlist_free(config);
+ config = NULL;
+ continue;
+ }
+
+ nvlist_free(config);
+ config = nvl;
+
+ /*
+ * Go through and update the paths for spares, now that we have
+ * them.
+ */
+ verify(nvlist_lookup_nvlist(config, ZPOOL_CONFIG_VDEV_TREE,
+ &nvroot) == 0);
+ if (nvlist_lookup_nvlist_array(nvroot, ZPOOL_CONFIG_SPARES,
+ &spares, &nspares) == 0) {
+ for (i = 0; i < nspares; i++) {
+ if (fix_paths(hdl, spares[i], pl->names) != 0)
+ goto nomem;
+ }
+ }
+
+ /*
+ * Update the paths for l2cache devices.
+ */
+ if (nvlist_lookup_nvlist_array(nvroot, ZPOOL_CONFIG_L2CACHE,
+ &l2cache, &nl2cache) == 0) {
+ for (i = 0; i < nl2cache; i++) {
+ if (fix_paths(hdl, l2cache[i], pl->names) != 0)
+ goto nomem;
+ }
+ }
+
+ /*
+ * Restore the original information read from the actual label.
+ */
+ (void) nvlist_remove(config, ZPOOL_CONFIG_HOSTID,
+ DATA_TYPE_UINT64);
+ (void) nvlist_remove(config, ZPOOL_CONFIG_HOSTNAME,
+ DATA_TYPE_STRING);
+ if (hostid != 0) {
+ verify(nvlist_add_uint64(config, ZPOOL_CONFIG_HOSTID,
+ hostid) == 0);
+ verify(nvlist_add_string(config, ZPOOL_CONFIG_HOSTNAME,
+ hostname) == 0);
+ }
+
+add_pool:
+ /*
+ * Add this pool to the list of configs.
+ */
+ verify(nvlist_lookup_string(config, ZPOOL_CONFIG_POOL_NAME,
+ &name) == 0);
+
+ if (nvlist_add_nvlist(ret, name, config) != 0)
+ goto nomem;
+
+ nvlist_free(config);
+ config = NULL;
+ }
+
+ return (ret);
+
+nomem:
+ (void) zutil_no_memory(hdl);
+error:
+ nvlist_free(config);
+ nvlist_free(ret);
+ for (c = 0; c < children; c++)
+ nvlist_free(child[c]);
+ free(child);
+
+ return (NULL);
+}
+
+/*
+ * Return the offset of the given label.
+ */
+static uint64_t
+label_offset(uint64_t size, int l)
+{
+ ASSERT(P2PHASE_TYPED(size, sizeof (vdev_label_t), uint64_t) == 0);
+ return (l * sizeof (vdev_label_t) + (l < VDEV_LABELS / 2 ?
+ 0 : size - VDEV_LABELS * sizeof (vdev_label_t)));
+}
+
+/*
+ * Given a file descriptor, read the label information and return an nvlist
+ * describing the configuration, if there is one. The number of valid
+ * labels found will be returned in num_labels when non-NULL.
+ */
+int
+zpool_read_label(int fd, nvlist_t **config, int *num_labels)
+{
+ struct stat64 statbuf;
+ int l, count = 0;
+ vdev_label_t *label;
+ nvlist_t *expected_config = NULL;
+ uint64_t expected_guid = 0, size;
+ int error;
+
+ *config = NULL;
+
+ if (fstat64_blk(fd, &statbuf) == -1)
+ return (0);
+ size = P2ALIGN_TYPED(statbuf.st_size, sizeof (vdev_label_t), uint64_t);
+
+ error = posix_memalign((void **)&label, PAGESIZE, sizeof (*label));
+ if (error)
+ return (-1);
+
+ for (l = 0; l < VDEV_LABELS; l++) {
+ uint64_t state, guid, txg;
+
+ if (pread64(fd, label, sizeof (vdev_label_t),
+ label_offset(size, l)) != sizeof (vdev_label_t))
+ continue;
+
+ if (nvlist_unpack(label->vl_vdev_phys.vp_nvlist,
+ sizeof (label->vl_vdev_phys.vp_nvlist), config, 0) != 0)
+ continue;
+
+ if (nvlist_lookup_uint64(*config, ZPOOL_CONFIG_GUID,
+ &guid) != 0 || guid == 0) {
+ nvlist_free(*config);
+ continue;
+ }
+
+ if (nvlist_lookup_uint64(*config, ZPOOL_CONFIG_POOL_STATE,
+ &state) != 0 || state > POOL_STATE_L2CACHE) {
+ nvlist_free(*config);
+ continue;
+ }
+
+ if (state != POOL_STATE_SPARE && state != POOL_STATE_L2CACHE &&
+ (nvlist_lookup_uint64(*config, ZPOOL_CONFIG_POOL_TXG,
+ &txg) != 0 || txg == 0)) {
+ nvlist_free(*config);
+ continue;
+ }
+
+ if (expected_guid) {
+ if (expected_guid == guid)
+ count++;
+
+ nvlist_free(*config);
+ } else {
+ expected_config = *config;
+ expected_guid = guid;
+ count++;
+ }
+ }
+
+ if (num_labels != NULL)
+ *num_labels = count;
+
+ free(label);
+ *config = expected_config;
+
+ return (0);
+}
+
+/*
+ * Sorted by full path and then vdev guid to allow for multiple entries with
+ * the same full path name. This is required because it's possible to
+ * have multiple block devices with labels that refer to the same
+ * ZPOOL_CONFIG_PATH yet have different vdev guids. In this case both
+ * entries need to be added to the cache. Scenarios where this can occur
+ * include overwritten pool labels, devices which are visible from multiple
+ * hosts and multipath devices.
+ */
+int
+slice_cache_compare(const void *arg1, const void *arg2)
+{
+ const char *nm1 = ((rdsk_node_t *)arg1)->rn_name;
+ const char *nm2 = ((rdsk_node_t *)arg2)->rn_name;
+ uint64_t guid1 = ((rdsk_node_t *)arg1)->rn_vdev_guid;
+ uint64_t guid2 = ((rdsk_node_t *)arg2)->rn_vdev_guid;
+ int rv;
+
+ rv = TREE_ISIGN(strcmp(nm1, nm2));
+ if (rv)
+ return (rv);
+
+ return (TREE_CMP(guid1, guid2));
+}
+
+static int
+label_paths_impl(libpc_handle_t *hdl, nvlist_t *nvroot, uint64_t pool_guid,
+ uint64_t vdev_guid, char **path, char **devid)
+{
+ nvlist_t **child;
+ uint_t c, children;
+ uint64_t guid;
+ char *val;
+ int error;
+
+ if (nvlist_lookup_nvlist_array(nvroot, ZPOOL_CONFIG_CHILDREN,
+ &child, &children) == 0) {
+ for (c = 0; c < children; c++) {
+ error = label_paths_impl(hdl, child[c],
+ pool_guid, vdev_guid, path, devid);
+ if (error)
+ return (error);
+ }
+ return (0);
+ }
+
+ if (nvroot == NULL)
+ return (0);
+
+ error = nvlist_lookup_uint64(nvroot, ZPOOL_CONFIG_GUID, &guid);
+ if ((error != 0) || (guid != vdev_guid))
+ return (0);
+
+ error = nvlist_lookup_string(nvroot, ZPOOL_CONFIG_PATH, &val);
+ if (error == 0)
+ *path = val;
+
+ error = nvlist_lookup_string(nvroot, ZPOOL_CONFIG_DEVID, &val);
+ if (error == 0)
+ *devid = val;
+
+ return (0);
+}
+
+/*
+ * Given a disk label fetch the ZPOOL_CONFIG_PATH and ZPOOL_CONFIG_DEVID
+ * and store these strings as config_path and devid_path respectively.
+ * The returned pointers are only valid as long as label remains valid.
+ */
+int
+label_paths(libpc_handle_t *hdl, nvlist_t *label, char **path, char **devid)
+{
+ nvlist_t *nvroot;
+ uint64_t pool_guid;
+ uint64_t vdev_guid;
+
+ *path = NULL;
+ *devid = NULL;
+
+ if (nvlist_lookup_nvlist(label, ZPOOL_CONFIG_VDEV_TREE, &nvroot) ||
+ nvlist_lookup_uint64(label, ZPOOL_CONFIG_POOL_GUID, &pool_guid) ||
+ nvlist_lookup_uint64(label, ZPOOL_CONFIG_GUID, &vdev_guid))
+ return (ENOENT);
+
+ return (label_paths_impl(hdl, nvroot, pool_guid, vdev_guid, path,
+ devid));
+}
+
+static void
+zpool_find_import_scan_add_slice(libpc_handle_t *hdl, pthread_mutex_t *lock,
+ avl_tree_t *cache, const char *path, const char *name, int order)
+{
+ avl_index_t where;
+ rdsk_node_t *slice;
+
+ slice = zutil_alloc(hdl, sizeof (rdsk_node_t));
+ if (asprintf(&slice->rn_name, "%s/%s", path, name) == -1) {
+ free(slice);
+ return;
+ }
+ slice->rn_vdev_guid = 0;
+ slice->rn_lock = lock;
+ slice->rn_avl = cache;
+ slice->rn_hdl = hdl;
+ slice->rn_order = order + IMPORT_ORDER_SCAN_OFFSET;
+ slice->rn_labelpaths = B_FALSE;
+
+ pthread_mutex_lock(lock);
+ if (avl_find(cache, slice, &where)) {
+ free(slice->rn_name);
+ free(slice);
+ } else {
+ avl_insert(cache, slice, where);
+ }
+ pthread_mutex_unlock(lock);
+}
+
+static int
+zpool_find_import_scan_dir(libpc_handle_t *hdl, pthread_mutex_t *lock,
+ avl_tree_t *cache, const char *dir, int order)
+{
+ int error;
+ char path[MAXPATHLEN];
+ struct dirent64 *dp;
+ DIR *dirp;
+
+ if (realpath(dir, path) == NULL) {
+ error = errno;
+ if (error == ENOENT)
+ return (0);
+
+ zutil_error_aux(hdl, strerror(error));
+ (void) zutil_error_fmt(hdl, EZFS_BADPATH, dgettext(
+ TEXT_DOMAIN, "cannot resolve path '%s'"), dir);
+ return (error);
+ }
+
+ dirp = opendir(path);
+ if (dirp == NULL) {
+ error = errno;
+ zutil_error_aux(hdl, strerror(error));
+ (void) zutil_error_fmt(hdl, EZFS_BADPATH,
+ dgettext(TEXT_DOMAIN, "cannot open '%s'"), path);
+ return (error);
+ }
+
+ while ((dp = readdir64(dirp)) != NULL) {
+ const char *name = dp->d_name;
+ if (name[0] == '.' &&
+ (name[1] == 0 || (name[1] == '.' && name[2] == 0)))
+ continue;
+
+ zpool_find_import_scan_add_slice(hdl, lock, cache, path, name,
+ order);
+ }
+
+ (void) closedir(dirp);
+ return (0);
+}
+
+static int
+zpool_find_import_scan_path(libpc_handle_t *hdl, pthread_mutex_t *lock,
+ avl_tree_t *cache, const char *dir, int order)
+{
+ int error = 0;
+ char path[MAXPATHLEN];
+ char *d, *b;
+ char *dpath, *name;
+
+ /*
+ * Separate the directory part and last part of the
+ * path. We do this so that we can get the realpath of
+ * the directory. We don't get the realpath on the
+ * whole path because if it's a symlink, we want the
+ * path of the symlink not where it points to.
+ */
+ d = zutil_strdup(hdl, dir);
+ b = zutil_strdup(hdl, dir);
+ dpath = dirname(d);
+ name = basename(b);
+
+ if (realpath(dpath, path) == NULL) {
+ error = errno;
+ if (error == ENOENT) {
+ error = 0;
+ goto out;
+ }
+
+ zutil_error_aux(hdl, strerror(error));
+ (void) zutil_error_fmt(hdl, EZFS_BADPATH, dgettext(
+ TEXT_DOMAIN, "cannot resolve path '%s'"), dir);
+ goto out;
+ }
+
+ zpool_find_import_scan_add_slice(hdl, lock, cache, path, name, order);
+
+out:
+ free(b);
+ free(d);
+ return (error);
+}
+
+/*
+ * Scan a list of directories for zfs devices.
+ */
+static int
+zpool_find_import_scan(libpc_handle_t *hdl, pthread_mutex_t *lock,
+ avl_tree_t **slice_cache, const char * const *dir, size_t dirs)
+{
+ avl_tree_t *cache;
+ rdsk_node_t *slice;
+ void *cookie;
+ int i, error;
+
+ *slice_cache = NULL;
+ cache = zutil_alloc(hdl, sizeof (avl_tree_t));
+ avl_create(cache, slice_cache_compare, sizeof (rdsk_node_t),
+ offsetof(rdsk_node_t, rn_node));
+
+ for (i = 0; i < dirs; i++) {
+ struct stat sbuf;
+
+ if (stat(dir[i], &sbuf) != 0) {
+ error = errno;
+ if (error == ENOENT)
+ continue;
+
+ zutil_error_aux(hdl, strerror(error));
+ (void) zutil_error_fmt(hdl, EZFS_BADPATH, dgettext(
+ TEXT_DOMAIN, "cannot resolve path '%s'"), dir[i]);
+ goto error;
+ }
+
+ /*
+ * If dir[i] is a directory, we walk through it and add all
+ * the entries to the cache. If it's not a directory, we just
+ * add it to the cache.
+ */
+ if (S_ISDIR(sbuf.st_mode)) {
+ if ((error = zpool_find_import_scan_dir(hdl, lock,
+ cache, dir[i], i)) != 0)
+ goto error;
+ } else {
+ if ((error = zpool_find_import_scan_path(hdl, lock,
+ cache, dir[i], i)) != 0)
+ goto error;
+ }
+ }
+
+ *slice_cache = cache;
+ return (0);
+
+error:
+ cookie = NULL;
+ while ((slice = avl_destroy_nodes(cache, &cookie)) != NULL) {
+ free(slice->rn_name);
+ free(slice);
+ }
+ free(cache);
+
+ return (error);
+}
+
+/*
+ * Given a list of directories to search, find all pools stored on disk. This
+ * includes partial pools which are not available to import. If no args are
+ * given (argc is 0), then the default directory (/dev/dsk) is searched.
+ * poolname or guid (but not both) are provided by the caller when trying
+ * to import a specific pool.
+ */
+static nvlist_t *
+zpool_find_import_impl(libpc_handle_t *hdl, importargs_t *iarg)
+{
+ nvlist_t *ret = NULL;
+ pool_list_t pools = { 0 };
+ pool_entry_t *pe, *penext;
+ vdev_entry_t *ve, *venext;
+ config_entry_t *ce, *cenext;
+ name_entry_t *ne, *nenext;
+ pthread_mutex_t lock;
+ avl_tree_t *cache;
+ rdsk_node_t *slice;
+ void *cookie;
+ tpool_t *t;
+
+ verify(iarg->poolname == NULL || iarg->guid == 0);
+ pthread_mutex_init(&lock, NULL);
+
+ /*
+ * Locate pool member vdevs by blkid or by directory scanning.
+ * On success a newly allocated AVL tree which is populated with an
+ * entry for each discovered vdev will be returned in the cache.
+ * It's the caller's responsibility to consume and destroy this tree.
+ */
+ if (iarg->scan || iarg->paths != 0) {
+ size_t dirs = iarg->paths;
+ const char * const *dir = (const char * const *)iarg->path;
+
+ if (dirs == 0)
+ dir = zpool_default_search_paths(&dirs);
+
+ if (zpool_find_import_scan(hdl, &lock, &cache, dir, dirs) != 0)
+ return (NULL);
+ } else {
+ if (zpool_find_import_blkid(hdl, &lock, &cache) != 0)
+ return (NULL);
+ }
+
+ /*
+ * Create a thread pool to parallelize the process of reading and
+ * validating labels, a large number of threads can be used due to
+ * minimal contention.
+ */
+ t = tpool_create(1, 2 * sysconf(_SC_NPROCESSORS_ONLN), 0, NULL);
+ for (slice = avl_first(cache); slice;
+ (slice = avl_walk(cache, slice, AVL_AFTER)))
+ (void) tpool_dispatch(t, zpool_open_func, slice);
+
+ tpool_wait(t);
+ tpool_destroy(t);
+
+ /*
+ * Process the cache, filtering out any entries which are not
+ * for the specified pool then adding matching label configs.
+ */
+ cookie = NULL;
+ while ((slice = avl_destroy_nodes(cache, &cookie)) != NULL) {
+ if (slice->rn_config != NULL) {
+ nvlist_t *config = slice->rn_config;
+ boolean_t matched = B_TRUE;
+ boolean_t aux = B_FALSE;
+ int fd;
+
+ /*
+ * Check if it's a spare or l2cache device. If it is,
+ * we need to skip the name and guid check since they
+ * don't exist on aux device label.
+ */
+ if (iarg->poolname != NULL || iarg->guid != 0) {
+ uint64_t state;
+ aux = nvlist_lookup_uint64(config,
+ ZPOOL_CONFIG_POOL_STATE, &state) == 0 &&
+ (state == POOL_STATE_SPARE ||
+ state == POOL_STATE_L2CACHE);
+ }
+
+ if (iarg->poolname != NULL && !aux) {
+ char *pname;
+
+ matched = nvlist_lookup_string(config,
+ ZPOOL_CONFIG_POOL_NAME, &pname) == 0 &&
+ strcmp(iarg->poolname, pname) == 0;
+ } else if (iarg->guid != 0 && !aux) {
+ uint64_t this_guid;
+
+ matched = nvlist_lookup_uint64(config,
+ ZPOOL_CONFIG_POOL_GUID, &this_guid) == 0 &&
+ iarg->guid == this_guid;
+ }
+ if (matched) {
+ /*
+ * Verify all remaining entries can be opened
+ * exclusively. This will prune all underlying
+ * multipath devices which otherwise could
+ * result in the vdev appearing as UNAVAIL.
+ *
+ * Under zdb, this step isn't required and
+ * would prevent a zdb -e of active pools with
+ * no cachefile.
+ */
+ fd = open(slice->rn_name, O_RDONLY | O_EXCL);
+ if (fd >= 0 || iarg->can_be_active) {
+ if (fd >= 0)
+ close(fd);
+ add_config(hdl, &pools,
+ slice->rn_name, slice->rn_order,
+ slice->rn_num_labels, config);
+ }
+ }
+ nvlist_free(config);
+ }
+ free(slice->rn_name);
+ free(slice);
+ }
+ avl_destroy(cache);
+ free(cache);
+ pthread_mutex_destroy(&lock);
+
+ ret = get_configs(hdl, &pools, iarg->can_be_active, iarg->policy);
+
+ for (pe = pools.pools; pe != NULL; pe = penext) {
+ penext = pe->pe_next;
+ for (ve = pe->pe_vdevs; ve != NULL; ve = venext) {
+ venext = ve->ve_next;
+ for (ce = ve->ve_configs; ce != NULL; ce = cenext) {
+ cenext = ce->ce_next;
+ nvlist_free(ce->ce_config);
+ free(ce);
+ }
+ free(ve);
+ }
+ free(pe);
+ }
+
+ for (ne = pools.names; ne != NULL; ne = nenext) {
+ nenext = ne->ne_next;
+ free(ne->ne_name);
+ free(ne);
+ }
+
+ return (ret);
+}
+
+/*
+ * Given a cache file, return the contents as a list of importable pools.
+ * poolname or guid (but not both) are provided by the caller when trying
+ * to import a specific pool.
+ */
+static nvlist_t *
+zpool_find_import_cached(libpc_handle_t *hdl, const char *cachefile,
+ const char *poolname, uint64_t guid)
+{
+ char *buf;
+ int fd;
+ struct stat64 statbuf;
+ nvlist_t *raw, *src, *dst;
+ nvlist_t *pools;
+ nvpair_t *elem;
+ char *name;
+ uint64_t this_guid;
+ boolean_t active;
+
+ verify(poolname == NULL || guid == 0);
+
+ if ((fd = open(cachefile, O_RDONLY)) < 0) {
+ zutil_error_aux(hdl, "%s", strerror(errno));
+ (void) zutil_error(hdl, EZFS_BADCACHE,
+ dgettext(TEXT_DOMAIN, "failed to open cache file"));
+ return (NULL);
+ }
+
+ if (fstat64(fd, &statbuf) != 0) {
+ zutil_error_aux(hdl, "%s", strerror(errno));
+ (void) close(fd);
+ (void) zutil_error(hdl, EZFS_BADCACHE,
+ dgettext(TEXT_DOMAIN, "failed to get size of cache file"));
+ return (NULL);
+ }
+
+ if ((buf = zutil_alloc(hdl, statbuf.st_size)) == NULL) {
+ (void) close(fd);
+ return (NULL);
+ }
+
+ if (read(fd, buf, statbuf.st_size) != statbuf.st_size) {
+ (void) close(fd);
+ free(buf);
+ (void) zutil_error(hdl, EZFS_BADCACHE,
+ dgettext(TEXT_DOMAIN,
+ "failed to read cache file contents"));
+ return (NULL);
+ }
+
+ (void) close(fd);
+
+ if (nvlist_unpack(buf, statbuf.st_size, &raw, 0) != 0) {
+ free(buf);
+ (void) zutil_error(hdl, EZFS_BADCACHE,
+ dgettext(TEXT_DOMAIN,
+ "invalid or corrupt cache file contents"));
+ return (NULL);
+ }
+
+ free(buf);
+
+ /*
+ * Go through and get the current state of the pools and refresh their
+ * state.
+ */
+ if (nvlist_alloc(&pools, 0, 0) != 0) {
+ (void) zutil_no_memory(hdl);
+ nvlist_free(raw);
+ return (NULL);
+ }
+
+ elem = NULL;
+ while ((elem = nvlist_next_nvpair(raw, elem)) != NULL) {
+ src = fnvpair_value_nvlist(elem);
+
+ name = fnvlist_lookup_string(src, ZPOOL_CONFIG_POOL_NAME);
+ if (poolname != NULL && strcmp(poolname, name) != 0)
+ continue;
+
+ this_guid = fnvlist_lookup_uint64(src, ZPOOL_CONFIG_POOL_GUID);
+ if (guid != 0 && guid != this_guid)
+ continue;
+
+ if (zutil_pool_active(hdl, name, this_guid, &active) != 0) {
+ nvlist_free(raw);
+ nvlist_free(pools);
+ return (NULL);
+ }
+
+ if (active)
+ continue;
+
+ if (nvlist_add_string(src, ZPOOL_CONFIG_CACHEFILE,
+ cachefile) != 0) {
+ (void) zutil_no_memory(hdl);
+ nvlist_free(raw);
+ nvlist_free(pools);
+ return (NULL);
+ }
+
+ if ((dst = zutil_refresh_config(hdl, src)) == NULL) {
+ nvlist_free(raw);
+ nvlist_free(pools);
+ return (NULL);
+ }
+
+ if (nvlist_add_nvlist(pools, nvpair_name(elem), dst) != 0) {
+ (void) zutil_no_memory(hdl);
+ nvlist_free(dst);
+ nvlist_free(raw);
+ nvlist_free(pools);
+ return (NULL);
+ }
+ nvlist_free(dst);
+ }
+
+ nvlist_free(raw);
+ return (pools);
+}
+
+nvlist_t *
+zpool_search_import(void *hdl, importargs_t *import,
+ const pool_config_ops_t *pco)
+{
+ libpc_handle_t handle = { 0 };
+ nvlist_t *pools = NULL;
+
+ handle.lpc_lib_handle = hdl;
+ handle.lpc_ops = pco;
+ handle.lpc_printerr = B_TRUE;
+
+ verify(import->poolname == NULL || import->guid == 0);
+
+ if (import->cachefile != NULL)
+ pools = zpool_find_import_cached(&handle, import->cachefile,
+ import->poolname, import->guid);
+ else
+ pools = zpool_find_import_impl(&handle, import);
+
+ if ((pools == NULL || nvlist_empty(pools)) &&
+ handle.lpc_open_access_error && geteuid() != 0) {
+ (void) zutil_error(&handle, EZFS_EACESS, dgettext(TEXT_DOMAIN,
+ "no pools found"));
+ }
+
+ return (pools);
+}
+
+static boolean_t
+pool_match(nvlist_t *cfg, char *tgt)
+{
+ uint64_t v, guid = strtoull(tgt, NULL, 0);
+ char *s;
+
+ if (guid != 0) {
+ if (nvlist_lookup_uint64(cfg, ZPOOL_CONFIG_POOL_GUID, &v) == 0)
+ return (v == guid);
+ } else {
+ if (nvlist_lookup_string(cfg, ZPOOL_CONFIG_POOL_NAME, &s) == 0)
+ return (strcmp(s, tgt) == 0);
+ }
+ return (B_FALSE);
+}
+
+int
+zpool_find_config(void *hdl, const char *target, nvlist_t **configp,
+ importargs_t *args, const pool_config_ops_t *pco)
+{
+ nvlist_t *pools;
+ nvlist_t *match = NULL;
+ nvlist_t *config = NULL;
+ char *name = NULL, *sepp = NULL;
+ char sep = '\0';
+ int count = 0;
+ char *targetdup = strdup(target);
+
+ *configp = NULL;
+
+ if ((sepp = strpbrk(targetdup, "/@")) != NULL) {
+ sep = *sepp;
+ *sepp = '\0';
+ }
+
+ pools = zpool_search_import(hdl, args, pco);
+
+ if (pools != NULL) {
+ nvpair_t *elem = NULL;
+ while ((elem = nvlist_next_nvpair(pools, elem)) != NULL) {
+ VERIFY0(nvpair_value_nvlist(elem, &config));
+ if (pool_match(config, targetdup)) {
+ count++;
+ if (match != NULL) {
+ /* multiple matches found */
+ continue;
+ } else {
+ match = config;
+ name = nvpair_name(elem);
+ }
+ }
+ }
+ }
+
+ if (count == 0) {
+ free(targetdup);
+ return (ENOENT);
+ }
+
+ if (count > 1) {
+ free(targetdup);
+ return (EINVAL);
+ }
+
+ *configp = match;
+ free(targetdup);
+
+ return (0);
+}
diff --git a/lib/libzutil/zutil_import.h b/lib/libzutil/zutil_import.h
new file mode 100644
index 000000000000..0108eb45c5cf
--- /dev/null
+++ b/lib/libzutil/zutil_import.h
@@ -0,0 +1,76 @@
+/*
+ * 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 2015 Nexenta Systems, Inc. All rights reserved.
+ * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2018 by Delphix. All rights reserved.
+ * Copyright 2015 RackTop Systems.
+ * Copyright (c) 2016, Intel Corporation.
+ */
+#ifndef _LIBZUTIL_ZUTIL_IMPORT_H_
+#define _LIBZUTIL_ZUTIL_IMPORT_H_
+
+#define EZFS_BADCACHE "invalid or missing cache file"
+#define EZFS_BADPATH "must be an absolute path"
+#define EZFS_NOMEM "out of memory"
+#define EZFS_EACESS "some devices require root privileges"
+
+#define IMPORT_ORDER_PREFERRED_1 1
+#define IMPORT_ORDER_PREFERRED_2 2
+#define IMPORT_ORDER_SCAN_OFFSET 10
+#define IMPORT_ORDER_DEFAULT 100
+
+typedef struct libpc_handle {
+ boolean_t lpc_printerr;
+ boolean_t lpc_open_access_error;
+ boolean_t lpc_desc_active;
+ char lpc_desc[1024];
+ const pool_config_ops_t *lpc_ops;
+ void *lpc_lib_handle;
+} libpc_handle_t;
+
+
+int label_paths(libpc_handle_t *hdl, nvlist_t *label, char **path,
+ char **devid);
+int zpool_find_import_blkid(libpc_handle_t *hdl, pthread_mutex_t *lock,
+ avl_tree_t **slice_cache);
+
+void * zutil_alloc(libpc_handle_t *hdl, size_t size);
+char *zutil_strdup(libpc_handle_t *hdl, const char *str);
+
+typedef struct rdsk_node {
+ char *rn_name; /* Full path to device */
+ int rn_order; /* Preferred order (low to high) */
+ int rn_num_labels; /* Number of valid labels */
+ uint64_t rn_vdev_guid; /* Expected vdev guid when set */
+ libpc_handle_t *rn_hdl;
+ nvlist_t *rn_config; /* Label config */
+ avl_tree_t *rn_avl;
+ avl_node_t rn_node;
+ pthread_mutex_t *rn_lock;
+ boolean_t rn_labelpaths;
+} rdsk_node_t;
+
+int slice_cache_compare(const void *, const void *);
+
+void zpool_open_func(void *);
+
+#endif /* _LIBZUTIL_ZUTIL_IMPORT_H_ */
diff --git a/lib/libzutil/zutil_nicenum.c b/lib/libzutil/zutil_nicenum.c
new file mode 100644
index 000000000000..306cec3caddf
--- /dev/null
+++ b/lib/libzutil/zutil_nicenum.c
@@ -0,0 +1,172 @@
+/*
+ * 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) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
+ */
+
+#include <ctype.h>
+#include <math.h>
+#include <stdio.h>
+#include <libzutil.h>
+
+/*
+ * Return B_TRUE if "str" is a number string, B_FALSE otherwise.
+ * Works for integer and floating point numbers.
+ */
+boolean_t
+zfs_isnumber(const char *str)
+{
+ for (; *str; str++)
+ if (!(isdigit(*str) || (*str == '.')))
+ return (B_FALSE);
+
+ return (B_TRUE);
+}
+
+/*
+ * Convert a number to an appropriately human-readable output.
+ */
+void
+zfs_nicenum_format(uint64_t num, char *buf, size_t buflen,
+ enum zfs_nicenum_format format)
+{
+ uint64_t n = num;
+ int index = 0;
+ const char *u;
+ const char *units[3][7] = {
+ [ZFS_NICENUM_1024] = {"", "K", "M", "G", "T", "P", "E"},
+ [ZFS_NICENUM_BYTES] = {"B", "K", "M", "G", "T", "P", "E"},
+ [ZFS_NICENUM_TIME] = {"ns", "us", "ms", "s", "?", "?", "?"}
+ };
+
+ const int units_len[] = {[ZFS_NICENUM_1024] = 6,
+ [ZFS_NICENUM_BYTES] = 6,
+ [ZFS_NICENUM_TIME] = 4};
+
+ const int k_unit[] = { [ZFS_NICENUM_1024] = 1024,
+ [ZFS_NICENUM_BYTES] = 1024,
+ [ZFS_NICENUM_TIME] = 1000};
+
+ double val;
+
+ if (format == ZFS_NICENUM_RAW) {
+ snprintf(buf, buflen, "%llu", (u_longlong_t)num);
+ return;
+ } else if (format == ZFS_NICENUM_RAWTIME && num > 0) {
+ snprintf(buf, buflen, "%llu", (u_longlong_t)num);
+ return;
+ } else if (format == ZFS_NICENUM_RAWTIME && num == 0) {
+ snprintf(buf, buflen, "%s", "-");
+ return;
+ }
+
+ while (n >= k_unit[format] && index < units_len[format]) {
+ n /= k_unit[format];
+ index++;
+ }
+
+ u = units[format][index];
+
+ /* Don't print zero latencies since they're invalid */
+ if ((format == ZFS_NICENUM_TIME) && (num == 0)) {
+ (void) snprintf(buf, buflen, "-");
+ } else if ((index == 0) || ((num %
+ (uint64_t)powl(k_unit[format], index)) == 0)) {
+ /*
+ * If this is an even multiple of the base, always display
+ * without any decimal precision.
+ */
+ (void) snprintf(buf, buflen, "%llu%s", (u_longlong_t)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--) {
+ val = (double)num /
+ (uint64_t)powl(k_unit[format], index);
+
+ /*
+ * Don't print floating point values for time. Note,
+ * we use floor() instead of round() here, since
+ * round can result in undesirable results. For
+ * example, if "num" is in the range of
+ * 999500-999999, it will print out "1000us". This
+ * doesn't happen if we use floor().
+ */
+ if (format == ZFS_NICENUM_TIME) {
+ if (snprintf(buf, buflen, "%d%s",
+ (unsigned int) floor(val), u) <= 5)
+ break;
+
+ } else {
+ if (snprintf(buf, buflen, "%.*f%s", i,
+ val, u) <= 5)
+ break;
+ }
+ }
+ }
+}
+
+/*
+ * Convert a number to an appropriately human-readable output.
+ */
+void
+zfs_nicenum(uint64_t num, char *buf, size_t buflen)
+{
+ zfs_nicenum_format(num, buf, buflen, ZFS_NICENUM_1024);
+}
+
+/*
+ * Convert a time to an appropriately human-readable output.
+ * @num: Time in nanoseconds
+ */
+void
+zfs_nicetime(uint64_t num, char *buf, size_t buflen)
+{
+ zfs_nicenum_format(num, buf, buflen, ZFS_NICENUM_TIME);
+}
+
+/*
+ * Print out a raw number with correct column spacing
+ */
+void
+zfs_niceraw(uint64_t num, char *buf, size_t buflen)
+{
+ zfs_nicenum_format(num, buf, buflen, ZFS_NICENUM_RAW);
+}
+
+/*
+ * Convert a number of bytes to an appropriately human-readable output.
+ */
+void
+zfs_nicebytes(uint64_t num, char *buf, size_t buflen)
+{
+ zfs_nicenum_format(num, buf, buflen, ZFS_NICENUM_BYTES);
+}
diff --git a/lib/libzutil/zutil_pool.c b/lib/libzutil/zutil_pool.c
new file mode 100644
index 000000000000..734650f3cffc
--- /dev/null
+++ b/lib/libzutil/zutil_pool.c
@@ -0,0 +1,145 @@
+/*
+ * 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) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/nvpair.h>
+#include <sys/fs/zfs.h>
+
+#include <libzutil.h>
+
+static void
+dump_ddt_stat(const ddt_stat_t *dds, int h)
+{
+ char refcnt[6];
+ char blocks[6], lsize[6], psize[6], dsize[6];
+ char ref_blocks[6], ref_lsize[6], ref_psize[6], ref_dsize[6];
+
+ if (dds == NULL || dds->dds_blocks == 0)
+ return;
+
+ if (h == -1)
+ (void) strcpy(refcnt, "Total");
+ else
+ zfs_nicenum(1ULL << h, refcnt, sizeof (refcnt));
+
+ zfs_nicenum(dds->dds_blocks, blocks, sizeof (blocks));
+ zfs_nicebytes(dds->dds_lsize, lsize, sizeof (lsize));
+ zfs_nicebytes(dds->dds_psize, psize, sizeof (psize));
+ zfs_nicebytes(dds->dds_dsize, dsize, sizeof (dsize));
+ zfs_nicenum(dds->dds_ref_blocks, ref_blocks, sizeof (ref_blocks));
+ zfs_nicebytes(dds->dds_ref_lsize, ref_lsize, sizeof (ref_lsize));
+ zfs_nicebytes(dds->dds_ref_psize, ref_psize, sizeof (ref_psize));
+ zfs_nicebytes(dds->dds_ref_dsize, ref_dsize, sizeof (ref_dsize));
+
+ (void) printf("%6s %6s %5s %5s %5s %6s %5s %5s %5s\n",
+ refcnt,
+ blocks, lsize, psize, dsize,
+ ref_blocks, ref_lsize, ref_psize, ref_dsize);
+}
+
+/*
+ * Print the DDT histogram and the column totals.
+ */
+void
+zpool_dump_ddt(const ddt_stat_t *dds_total, const ddt_histogram_t *ddh)
+{
+ int h;
+
+ (void) printf("\n");
+
+ (void) printf("bucket "
+ " allocated "
+ " referenced \n");
+ (void) printf("______ "
+ "______________________________ "
+ "______________________________\n");
+
+ (void) printf("%6s %6s %5s %5s %5s %6s %5s %5s %5s\n",
+ "refcnt",
+ "blocks", "LSIZE", "PSIZE", "DSIZE",
+ "blocks", "LSIZE", "PSIZE", "DSIZE");
+
+ (void) printf("%6s %6s %5s %5s %5s %6s %5s %5s %5s\n",
+ "------",
+ "------", "-----", "-----", "-----",
+ "------", "-----", "-----", "-----");
+
+ for (h = 0; h < 64; h++)
+ dump_ddt_stat(&ddh->ddh_stat[h], h);
+
+ dump_ddt_stat(dds_total, -1);
+
+ (void) printf("\n");
+}
+
+/*
+ * Process the buffer of nvlists, unpacking and storing each nvlist record
+ * into 'records'. 'leftover' is set to the number of bytes that weren't
+ * processed as there wasn't a complete record.
+ */
+int
+zpool_history_unpack(char *buf, uint64_t bytes_read, uint64_t *leftover,
+ nvlist_t ***records, uint_t *numrecords)
+{
+ uint64_t reclen;
+ nvlist_t *nv;
+ int i;
+ void *tmp;
+
+ while (bytes_read > sizeof (reclen)) {
+
+ /* get length of packed record (stored as little endian) */
+ for (i = 0, reclen = 0; i < sizeof (reclen); i++)
+ reclen += (uint64_t)(((uchar_t *)buf)[i]) << (8*i);
+
+ if (bytes_read < sizeof (reclen) + reclen)
+ break;
+
+ /* unpack record */
+ if (nvlist_unpack(buf + sizeof (reclen), reclen, &nv, 0) != 0)
+ return (ENOMEM);
+ bytes_read -= sizeof (reclen) + reclen;
+ buf += sizeof (reclen) + reclen;
+
+ /* add record to nvlist array */
+ (*numrecords)++;
+ if (ISP2(*numrecords + 1)) {
+ tmp = realloc(*records,
+ *numrecords * 2 * sizeof (nvlist_t *));
+ if (tmp == NULL) {
+ nvlist_free(nv);
+ (*numrecords)--;
+ return (ENOMEM);
+ }
+ *records = tmp;
+ }
+ (*records)[*numrecords - 1] = nv;
+ }
+
+ *leftover = bytes_read;
+ return (0);
+}